diff --git a/FORK.TXT b/FORK.TXT new file mode 100644 index 0000000..ae7ee2c --- /dev/null +++ b/FORK.TXT @@ -0,0 +1,31 @@ +==[Notes on the Fork]== + +Forking a vivid software project is usually a bad thing and there are +only few reasons when this action is considered legitimated. +The most common of those reasons is a licence change of the project - +this is what happened here. +The main author of this great piece of software, Rick Brewster, decided +to change the licence for all further releases of Paint.NET [1] making +this version the last one to licensed under MIT Licence (with exceptions +for Artwork and the GPC library, limiting the whole software to +non-commercial distribution, see src/Resources/Files/License.txt). +Any further releases include the restrictive clause +"You may not modify, adapt, rent, lease, loan, sell, or create +derivative works based upon the Software or any part thereof." +One year has passed since the last free sources of the project had been +released, giving the authors time to change their minds. +Their decision has to be respected, but the OpenSource community still +has to be able to build upon the last version of this powerful tool. + +Main goals of this fork are: + * Linux port (which means porting it to mono basically) + * replacing non-free parts (Artworks, General Polygon Clipper as those + are only usable non-commercially, making this non-free software in + the sense of OSI [2]) + +Of cause, all credits to the initial authors will remain, naturally new +contributors will be credited as well. + + +[1] http://blog.getpaint.net/2009/11/06/a-new-license-for-paintnet-v35/ +[2] http://opensource.org/docs/osd diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..6e4acfa --- /dev/null +++ b/License.txt @@ -0,0 +1 @@ +See src/Resources/Files/License.txt for the license. \ No newline at end of file diff --git a/README.TXT b/README.TXT new file mode 100644 index 0000000..34374da --- /dev/null +++ b/README.TXT @@ -0,0 +1,15 @@ +Paint.NET Source Code +--------------------- +If you're just trying to install and use Paint.NET, then you've downloaded +the wrong package. Just thought we'd let you know. + +For information on building Paint.NET, refer to src/readme.txt + +Directory Layout +---------------- + +extras/ + Contains other stuff related to Paint.NET. + +src/ + All source and other files necessary for building Paint.NET. diff --git a/extras/CodeLab/AssemblyInfo.cs b/extras/CodeLab/AssemblyInfo.cs new file mode 100644 index 0000000..bcf8cd5 --- /dev/null +++ b/extras/CodeLab/AssemblyInfo.cs @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Setup/License.txt for full licensing and attribution details. // +// 2 // +// 1 // +///////////////////////////////////////////////////////////////////////////////// + +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// 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("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// 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 Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/extras/CodeLab/CodeEditor.cs b/extras/CodeLab/CodeEditor.cs new file mode 100644 index 0000000..03ae9f8 --- /dev/null +++ b/extras/CodeLab/CodeEditor.cs @@ -0,0 +1,112 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Setup/License.txt for full licensing and attribution details. // +// 2 // +// 1 // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public class CodeEditor : TextBox + { + private Timer compileTimer; + + public CodeEditor() + { + this.AcceptsReturn = true; + this.AcceptsTab = true; + this.Multiline = true; + this.ScrollBars = ScrollBars.Vertical; + this.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + + this.compileTimer = new Timer(); + this.compileTimer.Interval = 500; + this.compileTimer.Enabled = false; + this.compileTimer.Tick += new EventHandler(compileTimer_Tick); + } + + public event EventHandler CompileTimeHint; + protected virtual void OnCompileTimeHint() + { + if (CompileTimeHint != null) + { + CompileTimeHint(this, EventArgs.Empty); + } + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + if (e.KeyChar == '\r') + { + int count = 0; + int end = this.SelectionStart; + string text = this.Text, indent = "\r\n"; + for (int i = 0; i < end; i++) + { + if (text[i] == '(' || text[i] == '[' || text[i] == '{') + { + count++; + } + if (text[i] == ')' || text[i] == ']' || text[i] == '}') + { + count--; + } + } + + while (count-- > 0) + { + indent += " "; + } + + this.SelectedText = indent; + e.Handled = true; + } + + this.compileTimer.Enabled = false; + this.compileTimer.Enabled = true; + + base.OnKeyPress (e); + } + + public void Highlight(int line, int column) + { + int startIndex = 0; + int endIndex = -1; + int linesPassed = 0; + string txt = this.Text; + + for (int i = 0; i < txt.Length; ++i) + { + if (txt[i] == '\n') + { + linesPassed++; + + if (linesPassed == line - 1) + { + startIndex = i + column; + } + else if (linesPassed == line) + { + endIndex = i - 1; + } + } + } + + if (startIndex > 0 && endIndex > 0) + { + this.Select(startIndex, endIndex - startIndex); + } + } + + private void compileTimer_Tick(object sender, EventArgs e) + { + OnCompileTimeHint(); + this.compileTimer.Enabled = false; + } + } +} diff --git a/extras/CodeLab/CodeLab.cs b/extras/CodeLab/CodeLab.cs new file mode 100644 index 0000000..b822394 --- /dev/null +++ b/extras/CodeLab/CodeLab.cs @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Setup/License.txt for full licensing and attribution details. // +// 2 // +// 1 // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Reflection; +using System.IO; +using Microsoft.CSharp; + +namespace PaintDotNet.Effects +{ + public class CodeLab : Effect + { + public static Image StaticImage + { + get + { + Assembly ourAssembly = Assembly.GetCallingAssembly(); + Stream imageStream = ourAssembly.GetManifestResourceStream("PaintDotNet.Effects.Icons.CodeLab.png"); + Image image = Image.FromStream(imageStream); + return image; + } + } + + public CodeLab() + : base("Code Lab", StaticImage, true) + { + } + + public override EffectConfigDialog CreateConfigDialog() + { + CodeLabConfigDialog secd = new CodeLabConfigDialog(); + return secd; + } + + public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) + { + CodeLabConfigToken sect = (CodeLabConfigToken)parameters; + Effect userEffect = sect.UserScriptObject; + + if (userEffect != null) + { + userEffect.EnvironmentParameters = this.EnvironmentParameters; + + try + { + userEffect.Render(null, dstArgs, srcArgs, rois, startIndex, length); + } + + catch (Exception exc) + { + sect.LastExceptions.Add(exc); + dstArgs.Surface.CopySurface(srcArgs.Surface); + sect.UserScriptObject = null; + } + } + } + } +} diff --git a/extras/CodeLab/CodeLab.csproj b/extras/CodeLab/CodeLab.csproj new file mode 100644 index 0000000..6e606d1 --- /dev/null +++ b/extras/CodeLab/CodeLab.csproj @@ -0,0 +1,144 @@ + + + Local + 8.0.50727 + 2.0 + {6E9302B8-7792-43A4-A4A4-1F5E8F371780} + Debug + AnyCPU + + + + + CodeLab + + + JScript + Grid + IE50 + false + Library + PaintDotNet.Effects + OnBuildSuccess + + + + + + + + + bin\Debug\ + false + 285212672 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + PaintDotNet.Effects + ..\..\src\bin\Release\PaintDotNet.Effects.dll + + + False + ..\..\src\bin\Debug\PaintDotNet.Resources.dll + + + PaintDotNet.SystemLayer + ..\..\src\bin\Release\PaintDotNet.SystemLayer.dll + + + PdnLib + ..\..\src\bin\Release\PdnLib.dll + + + System + + + System.Data + + + System.Drawing + + + System.Windows.Forms + + + System.XML + + + + + Code + + + Component + + + Code + + + Form + + + Code + + + Code + + + CodeLabConfigDialog.cs + + + + + + + + + + echo $(CWD) +mkdir "..\..\..\..\src\bin\$(ConfigurationName)\Effects" +copy "$(TargetFileName)" "..\..\..\..\src\bin\$(ConfigurationName)\Effects\" +echo "$(TargetFileName)" "..\..\..\..\src\bin\$(ConfigurationName)\Effects\" + + \ No newline at end of file diff --git a/extras/CodeLab/CodeLab.sln b/extras/CodeLab/CodeLab.sln new file mode 100644 index 0000000..123422d --- /dev/null +++ b/extras/CodeLab/CodeLab.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeLab", "CodeLab.csproj", "{6E9302B8-7792-43A4-A4A4-1F5E8F371780}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6E9302B8-7792-43A4-A4A4-1F5E8F371780}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E9302B8-7792-43A4-A4A4-1F5E8F371780}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E9302B8-7792-43A4-A4A4-1F5E8F371780}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E9302B8-7792-43A4-A4A4-1F5E8F371780}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extras/CodeLab/CodeLabConfigDialog.cs b/extras/CodeLab/CodeLabConfigDialog.cs new file mode 100644 index 0000000..3e75272 --- /dev/null +++ b/extras/CodeLab/CodeLabConfigDialog.cs @@ -0,0 +1,547 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Setup/License.txt for full licensing and attribution details. // +// 2 // +// 1 // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.CodeDom; +using System.CodeDom.Compiler; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Security.Policy; +using System.Windows.Forms; +using Microsoft.CSharp; + +namespace PaintDotNet.Effects +{ + public class CodeLabConfigDialog : EffectConfigDialog + { + private CodeEditor txtCode; + private System.Windows.Forms.GroupBox grpCode; + private System.Windows.Forms.Button btnOK; + private System.Windows.Forms.Button btnCancel; + private System.Windows.Forms.Button btnBuild; + private System.Windows.Forms.ListBox listErrors; + private CompilerParameters param; + private CompilerResults result; + private Assembly userAssembly; + private System.Windows.Forms.ToolTip toolTips; + private System.ComponentModel.IContainer components; + private Effect userScriptObject; + private const int prependLines = 13; + private System.Windows.Forms.Button btnLoad; + private System.Windows.Forms.Button btnSave; + private System.Windows.Forms.Button btnClear; + private System.Windows.Forms.Timer tmrExceptionCheck; + private System.Windows.Forms.Label lblScriptName; + private System.Windows.Forms.Button btnBuildDLL; + private System.Windows.Forms.TextBox txtScriptName; + private int lineOffset; + private const string prepend = "" + + "using System;\n" + + "using System.Drawing;\n" + + "using PaintDotNet;\n" + + "using PaintDotNet.Effects;\n" + + "namespace PaintDotNet.Effects\n" + + "{\n" + + "[EffectCategory(EffectCategory.Effect)]\n" + + "public class UserScript : Effect\n" + + "{\n" + + "public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length)" + + "{" + + "for (int i = startIndex; i < startIndex + length; ++i)" + + "{" + + "Render(dstArgs.Surface, srcArgs.Surface, rois[i]);\n" + + "}\n" + + "}\n" + + "public UserScript()"; + private const string append = "}\n}"; + private CSharpCodeProvider cscp = new CSharpCodeProvider(); + + public CodeLabConfigDialog() + { + param = new CompilerParameters(); + param.GenerateInMemory = true; + param.IncludeDebugInformation = true; + param.GenerateExecutable = false; + param.GenerateInMemory = true; + param.CompilerOptions = param.CompilerOptions + " /unsafe /optimize"; + + string basePath = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().CodeBase.Substring(8)); + + // param.OutputAssembly = "UserScript"; + param.ReferencedAssemblies.Add("System.dll"); + param.ReferencedAssemblies.Add("System.Drawing.dll"); + param.ReferencedAssemblies.Add("System.Windows.Forms.dll"); + param.ReferencedAssemblies.Add(Path.Combine(basePath, "PdnLib.dll")); + param.ReferencedAssemblies.Add(Path.Combine(basePath, "PaintDotNet.Effects.dll")); + param.ReferencedAssemblies.Add(Path.Combine(basePath, "PaintDotNet.Data.dll")); + param.ReferencedAssemblies.Add(Path.Combine(basePath, "PaintDotNet.SystemLayer.dll")); + param.ReferencedAssemblies.Add(Path.Combine(basePath, "Effects\\CodeLab.dll")); + + userAssembly = null; + + InitializeComponent(); + + this.FormBorderStyle = FormBorderStyle.Sizable; + this.AutoScaleMode = AutoScaleMode.Dpi; + + ResetScript(); + + lineOffset = CountOccurances(prepend, '\n'); + + btnBuild_Click(null, EventArgs.Empty); + } + + private int CountOccurances(string str, char compare) + { + int count = 0; + + foreach (char c in str) + { + if (c == compare) + { + ++count; + } + } + + return count; + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.txtCode = new PaintDotNet.Effects.CodeEditor(); + this.grpCode = new System.Windows.Forms.GroupBox(); + this.btnOK = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.btnBuild = new System.Windows.Forms.Button(); + this.listErrors = new System.Windows.Forms.ListBox(); + this.toolTips = new System.Windows.Forms.ToolTip(this.components); + this.btnLoad = new System.Windows.Forms.Button(); + this.btnSave = new System.Windows.Forms.Button(); + this.btnClear = new System.Windows.Forms.Button(); + this.tmrExceptionCheck = new System.Windows.Forms.Timer(this.components); + this.btnBuildDLL = new System.Windows.Forms.Button(); + this.txtScriptName = new System.Windows.Forms.TextBox(); + this.lblScriptName = new System.Windows.Forms.Label(); + this.grpCode.SuspendLayout(); + this.SuspendLayout(); + // + // txtCode + // + this.txtCode.AcceptsReturn = true; + this.txtCode.AcceptsTab = true; + this.txtCode.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtCode.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtCode.HideSelection = false; + this.txtCode.Location = new System.Drawing.Point(8, 16); + this.txtCode.Multiline = true; + this.txtCode.Name = "txtCode"; + this.txtCode.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtCode.Size = new System.Drawing.Size(510, 262); + this.txtCode.TabIndex = 0; + this.txtCode.CompileTimeHint += new System.EventHandler(this.txtCode_CompileTimeHint); + // + // grpCode + // + this.grpCode.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.grpCode.Controls.Add(this.txtCode); + this.grpCode.Location = new System.Drawing.Point(8, 32); + this.grpCode.Name = "grpCode"; + this.grpCode.Size = new System.Drawing.Size(528, 286); + this.grpCode.TabIndex = 2; + this.grpCode.TabStop = false; + this.grpCode.Text = "C# Code:"; + // + // btnOK + // + this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; + this.btnOK.Location = new System.Drawing.Point(473, 326); + this.btnOK.Name = "btnOK"; + this.btnOK.Size = new System.Drawing.Size(64, 24); + this.btnOK.TabIndex = 7; + this.btnOK.Text = "&OK"; + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.btnCancel.Location = new System.Drawing.Point(473, 358); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(64, 24); + this.btnCancel.TabIndex = 8; + this.btnCancel.Text = "&Cancel"; + // + // btnBuild + // + this.btnBuild.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnBuild.Location = new System.Drawing.Point(473, 8); + this.btnBuild.Name = "btnBuild"; + this.btnBuild.Size = new System.Drawing.Size(64, 23); + this.btnBuild.TabIndex = 6; + this.btnBuild.Text = "&Build"; + this.btnBuild.Click += new System.EventHandler(this.btnBuild_Click); + // + // listErrors + // + this.listErrors.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.listErrors.Font = new System.Drawing.Font("Courier New", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.listErrors.ItemHeight = 16; + this.listErrors.Items.AddRange(new object[] { + "Click \'Build\'"}); + this.listErrors.Location = new System.Drawing.Point(8, 328); + this.listErrors.Name = "listErrors"; + this.listErrors.Size = new System.Drawing.Size(456, 52); + this.listErrors.TabIndex = 0; + this.listErrors.SelectedIndexChanged += new System.EventHandler(this.listErrors_SelectedIndexChanged); + // + // btnLoad + // + this.btnLoad.Location = new System.Drawing.Point(232, 8); + this.btnLoad.Name = "btnLoad"; + this.btnLoad.Size = new System.Drawing.Size(80, 23); + this.btnLoad.TabIndex = 3; + this.btnLoad.Text = "&Load Source"; + this.btnLoad.Click += new System.EventHandler(this.btnLoad_Click); + // + // btnSave + // + this.btnSave.Location = new System.Drawing.Point(144, 8); + this.btnSave.Name = "btnSave"; + this.btnSave.Size = new System.Drawing.Size(80, 23); + this.btnSave.TabIndex = 2; + this.btnSave.Text = "&Save Source"; + this.btnSave.Click += new System.EventHandler(this.btnSave_Click); + // + // btnClear + // + this.btnClear.Location = new System.Drawing.Point(320, 8); + this.btnClear.Name = "btnClear"; + this.btnClear.Size = new System.Drawing.Size(48, 23); + this.btnClear.TabIndex = 4; + this.btnClear.Text = "C&lear"; + this.btnClear.Click += new System.EventHandler(this.btnClear_Click); + // + // tmrExceptionCheck + // + this.tmrExceptionCheck.Enabled = true; + this.tmrExceptionCheck.Tick += new System.EventHandler(this.tmrExceptionCheck_Tick); + // + // btnBuildDLL + // + this.btnBuildDLL.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnBuildDLL.Location = new System.Drawing.Point(393, 8); + this.btnBuildDLL.Name = "btnBuildDLL"; + this.btnBuildDLL.Size = new System.Drawing.Size(72, 23); + this.btnBuildDLL.TabIndex = 5; + this.btnBuildDLL.Text = "Make &DLL"; + this.btnBuildDLL.Click += new System.EventHandler(this.btnSaveDLL_Click); + // + // txtScriptName + // + this.txtScriptName.Location = new System.Drawing.Point(48, 8); + this.txtScriptName.Name = "txtScriptName"; + this.txtScriptName.Size = new System.Drawing.Size(88, 20); + this.txtScriptName.TabIndex = 1; + this.txtScriptName.Text = "MyCode"; + // + // lblScriptName + // + this.lblScriptName.Location = new System.Drawing.Point(8, 8); + this.lblScriptName.Name = "lblScriptName"; + this.lblScriptName.Size = new System.Drawing.Size(40, 20); + this.lblScriptName.TabIndex = 8; + this.lblScriptName.Text = "Name:"; + this.lblScriptName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // CodeLabConfigDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.ClientSize = new System.Drawing.Size(544, 389); + this.Controls.Add(this.lblScriptName); + this.Controls.Add(this.txtScriptName); + this.Controls.Add(this.btnBuildDLL); + this.Controls.Add(this.btnSave); + this.Controls.Add(this.btnLoad); + this.Controls.Add(this.btnOK); + this.Controls.Add(this.grpCode); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnBuild); + this.Controls.Add(this.btnClear); + this.Controls.Add(this.listErrors); + this.MinimumSize = new System.Drawing.Size(535, 200); + this.AcceptButton = this.btnOK; + this.CancelButton = this.btnCancel; + this.Name = "CodeLabConfigDialog"; + this.Text = "Code Lab (Alpha)"; + this.Controls.SetChildIndex(this.listErrors, 0); + this.Controls.SetChildIndex(this.btnClear, 0); + this.Controls.SetChildIndex(this.btnBuild, 0); + this.Controls.SetChildIndex(this.btnCancel, 0); + this.Controls.SetChildIndex(this.grpCode, 0); + this.Controls.SetChildIndex(this.btnOK, 0); + this.Controls.SetChildIndex(this.btnLoad, 0); + this.Controls.SetChildIndex(this.btnSave, 0); + this.Controls.SetChildIndex(this.btnBuildDLL, 0); + this.Controls.SetChildIndex(this.txtScriptName, 0); + this.Controls.SetChildIndex(this.lblScriptName, 0); + this.grpCode.ResumeLayout(false); + this.grpCode.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + private void listErrors_SelectedIndexChanged(object sender, System.EventArgs e) + { + if (listErrors.SelectedIndex >= 0) + { + CompilerErrorWrapper errw = listErrors.SelectedItem as CompilerErrorWrapper; + if (errw != null) + { + txtCode.Highlight(errw.CompilerError.Line - lineOffset, errw.CompilerError.Column); + } + toolTips.SetToolTip(listErrors, listErrors.SelectedItem.ToString()); + } + } + + private void btnBuild_Click(object sender, System.EventArgs e) + { + Build(false); + } + + protected override void InitTokenFromDialog() + { + CodeLabConfigToken sect = (CodeLabConfigToken)theEffectToken; + sect.UserCode = txtCode.Text; + sect.UserScriptObject = this.userScriptObject; + sect.ScriptName = txtScriptName.Text; + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + CodeLabConfigToken sect = (CodeLabConfigToken)effectToken; + + txtScriptName.Text = sect.ScriptName; + if (sect != null) + { + txtCode.Text = sect.UserCode; + } + } + + protected override void InitialInitToken() + { + CodeLabConfigToken sect = new CodeLabConfigToken(); + sect.UserCode = "void Render(Surface dst, Surface src, Rectangle rect)\r\n{\r\n for(int y = rect.Top; y < rect.Bottom; y++)\r\n {\r\n for (int x = rect.Left; x < rect.Right; x++)\r\n {\r\n }\r\n }\r\n}"; + sect.UserScriptObject = null; + sect.ScriptName = "MyScript"; + theEffectToken = sect; + } + + private bool Build(bool toDll) + { + bool retVal = false; + listErrors.Items.Clear(); + try + { + string prepend2 = " : base(\"" + txtScriptName.Text + "\", null) {}"; + if (toDll) + { + string oldargs = param.CompilerOptions; + + Uri location = new Uri(System.Reflection.Assembly.GetEntryAssembly().CodeBase); + string fullPath = Uri.UnescapeDataString(System.IO.Path.GetDirectoryName(location.AbsolutePath)); + + fullPath = Path.Combine(fullPath, "Effects"); + fullPath = Path.Combine(fullPath, txtScriptName.Text); + fullPath = Path.ChangeExtension(fullPath, ".dll"); + + param.CompilerOptions = param.CompilerOptions + " /debug- /target:library /out:\"" + fullPath + "\""; + cscp.CompileAssemblyFromSource(param, prepend + prepend2 + txtCode.Text + append); + + param.CompilerOptions = oldargs; + + } + else + { + userScriptObject = null; + result = cscp.CompileAssemblyFromSource(param, prepend + prepend2 + txtCode.Text + append); + } + + if (result.Errors.HasErrors) + { + foreach (CompilerError err in result.Errors) + { + CompilerErrorWrapper cew = new CompilerErrorWrapper(); + cew.CompilerError = err; + listErrors.Items.Add(cew); + } + } + else if (!toDll) + { + userAssembly = result.CompiledAssembly; + + foreach (Type type in userAssembly.GetTypes()) + { + if (type.IsSubclassOf(typeof(Effect)) && !type.IsAbstract) + { + userScriptObject = (Effect)type.GetConstructor(Type.EmptyTypes).Invoke(new object[] { }); + } + } + retVal = (userScriptObject != null); + } + else + { + retVal = true; + } + } + catch (Exception exc) + { + userScriptObject = null; + listErrors.Items.Add("Internal Error: " + exc.ToString()); + } + if (!toDll) + { + FinishTokenUpdate(); + } + return retVal; + } + + private bool ResetScript() + { + InitialInitToken(); + InitDialogFromToken(); + FinishTokenUpdate(); + return true; + } + + private bool SaveScript() + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.Title = "Save User Script"; + sfd.DefaultExt = ".cs"; + sfd.Filter = "C# Code Files (*.CS)|*.cs"; + sfd.OverwritePrompt = true; + sfd.AddExtension = true; + sfd.FileName = txtScriptName.Text + ".cs"; + + if (sfd.ShowDialog() == DialogResult.OK) + { + try + { + StreamWriter sw = new StreamWriter(sfd.FileName); + + sw.Write(txtCode.Text); + + sw.Close(); + return true; + } + catch + { + return false; + } + } + else + { + return false; + } + } + + private bool LoadScript() + { + OpenFileDialog ofd = new OpenFileDialog(); + ofd.Title = "Load User Script"; + ofd.DefaultExt = ".cs"; + ofd.Filter = "C# Code Files (*.CS)|*.cs"; + ofd.DefaultExt = ".cs"; + ofd.Multiselect = false; + + if (ofd.ShowDialog() == DialogResult.OK) + { + try + { + StreamReader sr = new StreamReader(ofd.FileName); + + txtCode.Text = sr.ReadToEnd(); + + sr.Close(); + return true; + } + catch + { + return false; + } + } + else + { + return false; + } + } + + private void btnSave_Click(object sender, System.EventArgs e) + { + SaveScript(); + } + + private void btnLoad_Click(object sender, System.EventArgs e) + { + LoadScript(); + this.btnBuild.PerformClick(); + } + + private void btnClear_Click(object sender, System.EventArgs e) + { + DialogResult dr = MessageBox.Show(this, "Do you want to save your current script?", "Script Editor", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); + switch (dr) + { + case DialogResult.Yes: + if (SaveScript()) + { + ResetScript(); + } + break; + case DialogResult.No: + ResetScript(); + break; + case DialogResult.Cancel: + break; + } + } + + private void tmrExceptionCheck_Tick(object sender, System.EventArgs e) + { + CodeLabConfigToken sect = (CodeLabConfigToken)theEffectToken; + + if (sect.LastExceptions.Count > 0) + { + Exception exc = sect.LastExceptions[0]; + sect.LastExceptions.Clear(); + listErrors.Items.Add(exc.ToString()); + } + + } + + private void btnSaveDLL_Click(object sender, System.EventArgs e) + { + Build(true); + } + + private void txtCode_CompileTimeHint(object sender, EventArgs e) + { + this.btnBuild.PerformClick(); + } + } +} diff --git a/extras/CodeLab/CodeLabConfigDialog.resx b/extras/CodeLab/CodeLabConfigDialog.resx new file mode 100644 index 0000000..d89e7bf --- /dev/null +++ b/extras/CodeLab/CodeLabConfigDialog.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 107, 17 + + \ No newline at end of file diff --git a/extras/CodeLab/CodeLabConfigToken.cs b/extras/CodeLab/CodeLabConfigToken.cs new file mode 100644 index 0000000..0b44d3c --- /dev/null +++ b/extras/CodeLab/CodeLabConfigToken.cs @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Setup/License.txt for full licensing and attribution details. // +// 2 // +// 1 // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; + +namespace PaintDotNet.Effects +{ + public class CodeLabConfigToken : EffectConfigToken + { + public Effect UserScriptObject; + public string UserCode; + public List LastExceptions; + public string ScriptName; + + public CodeLabConfigToken() : base() + { + UserScriptObject = null; + UserCode = ""; + LastExceptions = new List(); + ScriptName = "MyScript"; + } + + public override object Clone() + { + CodeLabConfigToken sect = new CodeLabConfigToken(); + sect.UserCode = this.UserCode; + sect.UserScriptObject = this.UserScriptObject; + sect.LastExceptions = this.LastExceptions; //Reference copy INTENDED. + sect.ScriptName = this.ScriptName; + return sect; + } + } +} diff --git a/extras/CodeLab/CompilerErrorWrapper.cs b/extras/CodeLab/CompilerErrorWrapper.cs new file mode 100644 index 0000000..114c127 --- /dev/null +++ b/extras/CodeLab/CompilerErrorWrapper.cs @@ -0,0 +1,38 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Setup/License.txt for full licensing and attribution details. // +// 2 // +// 1 // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.CodeDom.Compiler; + +namespace PaintDotNet.Effects +{ + /// + /// Container for a CompilerError object, overrides ToString to a more readable form. + /// + public class CompilerErrorWrapper + { + public CompilerError CompilerError = null; + + public override string ToString() + { + if (this.CompilerError == null) + { + throw new ArgumentNullException("inner", "inner may not be null"); + } + + return (this.CompilerError.IsWarning ? "Warning" : "Error") + + " at line " + + this.CompilerError.Line + + ": " + + this.CompilerError.ErrorText + + " (" + this.CompilerError.ErrorNumber + ")"; + } + + } +} diff --git a/extras/CodeLab/Icons/CodeLab.png b/extras/CodeLab/Icons/CodeLab.png new file mode 100644 index 0000000..dcd8f26 Binary files /dev/null and b/extras/CodeLab/Icons/CodeLab.png differ diff --git a/extras/HDPhoto/HDPhoto.sln b/extras/HDPhoto/HDPhoto.sln new file mode 100644 index 0000000..29268f8 --- /dev/null +++ b/extras/HDPhoto/HDPhoto.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HDPhoto", "HDPhoto\HDPhoto.csproj", "{41C86DA4-1C00-4052-A2FF-CFAB30A927CB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41C86DA4-1C00-4052-A2FF-CFAB30A927CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41C86DA4-1C00-4052-A2FF-CFAB30A927CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41C86DA4-1C00-4052-A2FF-CFAB30A927CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41C86DA4-1C00-4052-A2FF-CFAB30A927CB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extras/HDPhoto/HDPhoto/AssemblyInfo.cs b/extras/HDPhoto/HDPhoto/AssemblyInfo.cs new file mode 100644 index 0000000..9b8dc12 --- /dev/null +++ b/extras/HDPhoto/HDPhoto/AssemblyInfo.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("HDPhoto Plugin for Paint.NET")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("Paint.NET Team")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2007 dotPDN LLC, Rick Brewster. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("0.4.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: Dependency("PaintDotNet.Core", LoadHint.Always)] +[assembly: Dependency("PaintDotNet.Data", LoadHint.Always)] +[assembly: Dependency("PaintDotNet.SystemLayer", LoadHint.Always)] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: Dependency("WindowsBase", LoadHint.Always)] +[assembly: Dependency("PresentationCore", LoadHint.Always)] +[assembly: ComVisibleAttribute(false)] diff --git a/extras/HDPhoto/HDPhoto/HDPhoto.csproj b/extras/HDPhoto/HDPhoto/HDPhoto.csproj new file mode 100644 index 0000000..54f064a --- /dev/null +++ b/extras/HDPhoto/HDPhoto/HDPhoto.csproj @@ -0,0 +1,100 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {41C86DA4-1C00-4052-A2FF-CFAB30A927CB} + Library + Properties + PaintDotNet.Data + HDPhoto + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + C:\Program Files\Paint.NET\PaintDotNet.Base.dll + + + False + C:\Program Files\Paint.NET\PaintDotNet.Core.dll + + + False + C:\Program Files\Paint.NET\PaintDotNet.Data.dll + + + False + C:\Program Files\Paint.NET\PaintDotNet.Resources.dll + + + False + C:\Program Files\Paint.NET\PaintDotNet.SystemLayer.dll + + + + + + + + + + + + + + + + UserControl + + + True + True + Strings.resx + + + + + + Designer + HDPhotoSaveConfigWidget.cs + + + Designer + ResXFileCodeGenerator + Strings.Designer.cs + + + + + + + + + + + + \ No newline at end of file diff --git a/extras/HDPhoto/HDPhoto/HDPhotoFileType.cs b/extras/HDPhoto/HDPhoto/HDPhotoFileType.cs new file mode 100644 index 0000000..54f3bc0 --- /dev/null +++ b/extras/HDPhoto/HDPhoto/HDPhotoFileType.cs @@ -0,0 +1,425 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Data; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace PaintDotNet.Data +{ + public sealed class HDPhotoFileType + : FileType + { + public HDPhotoFileType() + : base(Strings.HDPhotoFileType_Name, + FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving, + new string[] { ".wdp", ".hdp" }) + { + } + + public override SaveConfigWidget CreateSaveConfigWidget() + { + return new HDPhotoSaveConfigWidget(); + } + + public override bool IsReflexive(SaveConfigToken token) + { + return false; + } + + protected override SaveConfigToken OnCreateDefaultSaveConfigToken() + { + return new HDPhotoSaveConfigToken(90, 32); + } + + protected override Document OnLoad(Stream input) + { + Document document; + + // WIC does not support MTA, so we must marshal this stuff to another thread + // that is then guaranteed to be STA. + switch (Thread.CurrentThread.GetApartmentState()) + { + case ApartmentState.Unknown: + case ApartmentState.MTA: + OnLoadArgs ola = new OnLoadArgs(); + ola.input = input; + ola.output = null; + + ParameterizedThreadStart pts = new ParameterizedThreadStart(OnLoadThreadProc); + Thread staThread = new Thread(pts); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Start(ola); + staThread.Join(); + + if (ola.exception != null) + { + throw new ApplicationException("OnLoadImpl() threw an exception", ola.exception); + } + + document = ola.output; + break; + + case ApartmentState.STA: + document = OnLoadImpl(input); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return document; + } + + private sealed class OnLoadArgs + { + public Stream input; + public Document output; + + public Exception exception; + } + + private void OnLoadThreadProc(object context) + { + OnLoadArgs ola = (OnLoadArgs)context; + + try + { + ola.output = OnLoadImpl(ola.input); + } + + catch (Exception ex) + { + ola.exception = ex; + } + } + + private Document OnLoadImpl(Stream input) + { + WmpBitmapDecoder wbd = new WmpBitmapDecoder(input, BitmapCreateOptions.None, BitmapCacheOption.None); + BitmapFrame frame0 = wbd.Frames[0]; + + Document output = new Document(frame0.PixelWidth, frame0.PixelHeight); + output.DpuUnit = MeasurementUnit.Inch; + output.DpuX = frame0.DpiX; + output.DpuY = frame0.DpiY; + + BitmapLayer layer = Layer.CreateBackgroundLayer(output.Width, output.Height); + MemoryBlock memoryBlock = layer.Surface.Scan0; + IntPtr scan0 = memoryBlock.Pointer; + + FormatConvertedBitmap fcb = new FormatConvertedBitmap(frame0, System.Windows.Media.PixelFormats.Bgra32, null, 0); + + fcb.CopyPixels(Int32Rect.Empty, scan0, (int)memoryBlock.Length, layer.Surface.Stride); + output.Layers.Add(layer); + + BitmapMetadata hdMetadata = (BitmapMetadata)frame0.Metadata; + CopyMetadataTo(output.Metadata, hdMetadata); + + // WPF doesn't give us an IDisposable implementation on its types + Utility.GCFullCollect(); + + return output; + } + + private void CopyStringTagTo(BitmapMetadata dst, string dstPropertyName, Metadata src, ExifTagID srcTagID) + { + PropertyItem[] pis = src.GetExifValues(srcTagID); + + if (pis.Length > 0) + { + PropertyInfo pi = dst.GetType().GetProperty(dstPropertyName); + string piValue = Exif.DecodeAsciiValue(pis[0]); + + try + { + pi.SetValue(dst, piValue, null); + } + + catch (Exception) + { + // *shrug* + } + } + } + + private void CopyMetadataTo(BitmapMetadata dst, Metadata src) + { + // ApplicationName + CopyStringTagTo(dst, "ApplicationName", src, ExifTagID.Software); + + // Author + PropertyItem[] authorsPI = src.GetExifValues(ExifTagID.Artist); + if (authorsPI.Length > 0) + { + List authors = new List(); + foreach (PropertyItem pi in authorsPI) + { + string author = Exif.DecodeAsciiValue(pi); + authors.Add(author); + } + ReadOnlyCollection authorsRO = new ReadOnlyCollection(authors); + dst.Author = authorsRO; + } + + CopyStringTagTo(dst, "CameraManufacturer", src, ExifTagID.Make); + CopyStringTagTo(dst, "CameraModel", src, ExifTagID.Model); + CopyStringTagTo(dst, "Copyright", src, ExifTagID.Copyright); + CopyStringTagTo(dst, "Title", src, ExifTagID.ImageDescription); + + PropertyItem[] dateTimePis = src.GetExifValues(ExifTagID.DateTime); + if (dateTimePis.Length > 0) + { + string dateTime = Exif.DecodeAsciiValue(dateTimePis[0]); + + try + { + dst.DateTaken = dateTime; + } + + catch (Exception) + { + try + { + string newDateTime = FixDateTimeString(dateTime); + dst.DateTaken = newDateTime; + } + + catch (Exception) + { + // *shrug* + } + } + } + } + + private string FixDateTimeString(string brokenDateTime) + { + // It may be in the form of YYYY:MM:YY HH:MM:SS + // But we need those first two :'s to be -'s + StringBuilder fixedDateTime = new StringBuilder(brokenDateTime); + + for (int i = 0; i < Math.Min(brokenDateTime.Length, 10); ++i) + { + if (fixedDateTime[i] == ':') + { + fixedDateTime[i] = '-'; + } + } + + return fixedDateTime.ToString(); + } + + private void CopyMetadataTo(Metadata dst, BitmapMetadata src) + { + dst.AddExifValues(new PropertyItem[1] { Exif.CreateAscii(ExifTagID.Software, src.ApplicationName) }); + + ReadOnlyCollection authors = src.Author; + if (authors != null) + { + + List piAuthors = new List(); + foreach (string author in authors) + { + PropertyItem piAuthor = Exif.CreateAscii(ExifTagID.Artist, author); + piAuthors.Add(piAuthor); + } + + dst.AddExifValues(piAuthors.ToArray()); + } + + dst.AddExifValues(new PropertyItem[1] { Exif.CreateAscii(ExifTagID.Make, src.CameraManufacturer) }); + dst.AddExifValues(new PropertyItem[1] { Exif.CreateAscii(ExifTagID.Model, src.CameraModel) }); + dst.AddExifValues(new PropertyItem[1] { Exif.CreateAscii(ExifTagID.Copyright, src.Copyright) }); + dst.AddExifValues(new PropertyItem[1] { Exif.CreateAscii(ExifTagID.DateTime, src.DateTaken) }); + dst.AddExifValues(new PropertyItem[1] { Exif.CreateAscii(ExifTagID.ImageDescription, src.Title) }); + } + + protected override void OnSave( + Document input, + Stream output, + SaveConfigToken token, + Surface scratchSurface, + ProgressEventHandler callback) + { + switch (Thread.CurrentThread.GetApartmentState()) + { + // WIC does not support MTA, so we must marshal this stuff to another thread that is guaranteed to be STA. + case ApartmentState.Unknown: + case ApartmentState.MTA: + ParameterizedThreadStart pts = new ParameterizedThreadStart(OnSaveThreadProc); + + OnSaveArgs osa = new OnSaveArgs(); + osa.input = input; + osa.output = output; + osa.token = token; + osa.scratchSurface = scratchSurface; + osa.callback = callback; + + Thread staThread = new Thread(pts); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Start(osa); + staThread.Join(); + + if (osa.exception != null) + { + throw new ApplicationException("OnSaveImpl() threw an exception", osa.exception); + } + break; + + case ApartmentState.STA: + OnSaveImpl(input, output, token, scratchSurface, callback); + break; + + default: + throw new InvalidOperationException(); + } + } + + private sealed class OnSaveArgs + { + public Document input; + public Stream output; + public SaveConfigToken token; + public Surface scratchSurface; + public ProgressEventHandler callback; + + public Exception exception; + } + + private void OnSaveThreadProc(object context) + { + OnSaveArgs osa = (OnSaveArgs)context; + + try + { + OnSave(osa); + } + + catch (Exception ex) + { + osa.exception = ex; + } + } + + private void OnSave(OnSaveArgs args) + { + OnSaveImpl(args.input, args.output, args.token, args.scratchSurface, args.callback); + } + + private void OnSaveImpl( + Document input, + Stream output, + SaveConfigToken token, + Surface scratchSurface, + ProgressEventHandler callback) + { + HDPhotoSaveConfigToken hdToken = token as HDPhotoSaveConfigToken; + WmpBitmapEncoder wbe = new WmpBitmapEncoder(); + + using (RenderArgs ra = new RenderArgs(scratchSurface)) + { + input.Render(ra, true); + } + + MemoryBlock block = scratchSurface.Scan0; + IntPtr scan0 = block.Pointer; + + double dpiX; + double dpiY; + + switch (input.DpuUnit) + { + case MeasurementUnit.Centimeter: + dpiX = Document.DotsPerCmToDotsPerInch(input.DpuX); + dpiY = Document.DotsPerCmToDotsPerInch(input.DpuY); + break; + + case MeasurementUnit.Inch: + dpiX = input.DpuX; + dpiY = input.DpuY; + break; + + case MeasurementUnit.Pixel: + dpiX = Document.GetDefaultDpu(MeasurementUnit.Inch); + dpiY = Document.GetDefaultDpu(MeasurementUnit.Inch); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + BitmapSource bitmapSource = BitmapFrame.Create( + scratchSurface.Width, + scratchSurface.Height, + dpiX, + dpiY, + System.Windows.Media.PixelFormats.Bgra32, + null, + scan0, + (int)block.Length, // TODO: does not support >2GB images + scratchSurface.Stride); + + FormatConvertedBitmap fcBitmap = new FormatConvertedBitmap( + bitmapSource, + hdToken.BitDepth == 24 ? PixelFormats.Bgr24 : PixelFormats.Bgra32, + null, + 0); + + BitmapFrame outputFrame0 = BitmapFrame.Create(fcBitmap); + + wbe.Frames.Add(outputFrame0); + wbe.ImageQualityLevel = (float)hdToken.Quality / 100.0f; + + string tempFileName = FileSystem.GetTempFileName(); + + FileStream tempFileOut = new FileStream(tempFileName, FileMode.Create, FileAccess.Write, FileShare.Read); + wbe.Save(tempFileOut); + tempFileOut.Close(); + tempFileOut = null; + + FileStream tempFileIn = new FileStream(tempFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + WmpBitmapDecoder wbd = new WmpBitmapDecoder(tempFileIn, BitmapCreateOptions.None, BitmapCacheOption.None); + BitmapFrame ioFrame0 = wbd.Frames[0]; + InPlaceBitmapMetadataWriter metadata2 = ioFrame0.CreateInPlaceBitmapMetadataWriter(); + CopyMetadataTo(metadata2, input.Metadata); + tempFileIn.Close(); + tempFileIn = null; + + FileStream tempFileIn2 = new FileStream(tempFileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read); + Utility.CopyStream(tempFileIn2, output); + tempFileIn2.Close(); + tempFileIn2 = null; + + try + { + File.Delete(tempFileName); + } + + catch (Exception) + { + } + + // WPF doesn't give us an IDisposable implementation on its types + Utility.GCFullCollect(); + } + } +} \ No newline at end of file diff --git a/extras/HDPhoto/HDPhoto/HDPhotoFileTypeFactory.cs b/extras/HDPhoto/HDPhoto/HDPhotoFileTypeFactory.cs new file mode 100644 index 0000000..c2c3b16 --- /dev/null +++ b/extras/HDPhoto/HDPhoto/HDPhotoFileTypeFactory.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Data; +using System; + +namespace PaintDotNet.Data +{ + public sealed class HDPhotoFileTypeFactory + : IFileTypeFactory + { + public FileType[] GetFileTypeInstances() + { + Version minVersion = new Version(3, 20); + Version maxVersion = new Version(3, 65536); + + if (PdnInfo.GetVersion() >= minVersion && PdnInfo.GetVersion() < maxVersion) + { + return new FileType[] { new HDPhotoFileType() }; + } + else + { + return new FileType[0]; + } + } + } +} \ No newline at end of file diff --git a/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigToken.cs b/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigToken.cs new file mode 100644 index 0000000..3fa82db --- /dev/null +++ b/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigToken.cs @@ -0,0 +1,78 @@ +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.Data +{ + [Serializable] + public sealed class HDPhotoSaveConfigToken + : SaveConfigToken + { + private int quality; + public int Quality + { + get + { + return this.quality; + } + + set + { + if (value < 0 || value > 100) + { + throw new ArgumentOutOfRangeException("quality must be in the range [0, 100]"); + } + + this.quality = value; + } + } + + private int bitDepth; + public int BitDepth + { + get + { + return this.bitDepth; + } + + set + { + if (value == 24 || value == 32) + { + this.bitDepth = value; + } + else + { + throw new ArgumentOutOfRangeException("BitDepth may only be 24 or 32"); + } + } + } + + public HDPhotoSaveConfigToken(int quality, int bitDepth) + { + this.quality = quality; + this.bitDepth = bitDepth; + } + + public override void Validate() + { + if (this.quality < 0 || this.quality > 100) + { + throw new ArgumentOutOfRangeException("quality must be in the range [0, 100]"); + } + + if (!(this.bitDepth == 24 || this.bitDepth == 32)) + { + throw new ArgumentOutOfRangeException("BitDepth may only be 24 or 32"); + } + + base.Validate(); + } + + public override object Clone() + { + return new HDPhotoSaveConfigToken(this.quality, this.bitDepth); + } + } +} diff --git a/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigWidget.cs b/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigWidget.cs new file mode 100644 index 0000000..9cca585 --- /dev/null +++ b/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigWidget.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Data +{ + public sealed class HDPhotoSaveConfigWidget + : SaveConfigWidget + { + private System.Windows.Forms.TrackBar qualitySlider; + private System.Windows.Forms.Label qualityLabel; + private System.Windows.Forms.NumericUpDown qualityUpDown; + private Panel panel1; + private RadioButton bpp32RB; + private RadioButton bpp24RB; + private HeaderLabel bppHeader; + private System.ComponentModel.IContainer components = null; + + public HDPhotoSaveConfigWidget() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + this.qualityLabel.Text = Strings.HDPhotoSaveConfigWidget_QualitySlider_Text; + this.bppHeader.Text = Strings.HDPhotoSaveConfigWidget_BppHeader_Text; + } + + protected override void InitFileType() + { + this.fileType = new HDPhotoFileType(); + } + + protected override void InitTokenFromWidget() + { + ((HDPhotoSaveConfigToken)this.Token).Quality = this.qualitySlider.Value; + ((HDPhotoSaveConfigToken)this.Token).BitDepth = (this.bpp24RB.Checked ? 24 : 32); + } + + protected override void InitWidgetFromToken(SaveConfigToken token) + { + HDPhotoSaveConfigToken hdToken = (HDPhotoSaveConfigToken)token; + this.qualitySlider.Value = hdToken.Quality; + this.bpp24RB.Checked = (hdToken.BitDepth == 24); + this.bpp32RB.Checked = (hdToken.BitDepth == 32); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.qualitySlider = new System.Windows.Forms.TrackBar(); + this.qualityLabel = new System.Windows.Forms.Label(); + this.qualityUpDown = new System.Windows.Forms.NumericUpDown(); + this.panel1 = new System.Windows.Forms.Panel(); + this.bpp32RB = new System.Windows.Forms.RadioButton(); + this.bpp24RB = new System.Windows.Forms.RadioButton(); + this.bppHeader = new PaintDotNet.HeaderLabel(); + ((System.ComponentModel.ISupportInitialize)(this.qualitySlider)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.qualityUpDown)).BeginInit(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // qualitySlider + // + this.qualitySlider.Location = new System.Drawing.Point(0, 24); + this.qualitySlider.Maximum = 100; + this.qualitySlider.Minimum = 1; + this.qualitySlider.Name = "qualitySlider"; + this.qualitySlider.Size = new System.Drawing.Size(180, 45); + this.qualitySlider.TabIndex = 1; + this.qualitySlider.TickFrequency = 10; + this.qualitySlider.Value = 1; + this.qualitySlider.ValueChanged += new System.EventHandler(this.QualitySlider_ValueChanged); + // + // qualityLabel + // + this.qualityLabel.AutoSize = true; + this.qualityLabel.Location = new System.Drawing.Point(4, 3); + this.qualityLabel.Name = "qualityLabel"; + this.qualityLabel.Size = new System.Drawing.Size(39, 13); + this.qualityLabel.TabIndex = 1; + this.qualityLabel.Text = "Quality"; + // + // qualityUpDown + // + this.qualityUpDown.Location = new System.Drawing.Point(115, 0); + this.qualityUpDown.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.qualityUpDown.Name = "qualityUpDown"; + this.qualityUpDown.Size = new System.Drawing.Size(56, 20); + this.qualityUpDown.TabIndex = 0; + this.qualityUpDown.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.qualityUpDown.Enter += new System.EventHandler(this.QualityUpDown_Enter); + this.qualityUpDown.ValueChanged += new System.EventHandler(this.QualityUpDown_ValueChanged); + this.qualityUpDown.Leave += new System.EventHandler(this.QualityUpDown_Leave); + // + // panel1 + // + this.panel1.Controls.Add(this.bpp32RB); + this.panel1.Controls.Add(this.bpp24RB); + this.panel1.Location = new System.Drawing.Point(0, 87); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(180, 59); + this.panel1.TabIndex = 2; + // + // bpp32RB + // + this.bpp32RB.AutoSize = true; + this.bpp32RB.Location = new System.Drawing.Point(10, 28); + this.bpp32RB.Name = "bpp32RB"; + this.bpp32RB.Size = new System.Drawing.Size(51, 17); + this.bpp32RB.TabIndex = 1; + this.bpp32RB.TabStop = true; + this.bpp32RB.Text = "32-bit"; + this.bpp32RB.UseVisualStyleBackColor = true; + this.bpp32RB.CheckedChanged += new System.EventHandler(this.bpp32RB_CheckedChanged); + // + // bpp24RB + // + this.bpp24RB.AutoSize = true; + this.bpp24RB.Location = new System.Drawing.Point(10, 4); + this.bpp24RB.Name = "bpp24RB"; + this.bpp24RB.Size = new System.Drawing.Size(51, 17); + this.bpp24RB.TabIndex = 0; + this.bpp24RB.TabStop = true; + this.bpp24RB.Text = "24-bit"; + this.bpp24RB.UseVisualStyleBackColor = true; + this.bpp24RB.CheckedChanged += new System.EventHandler(this.bpp24RB_CheckedChanged); + // + // bppHeader + // + this.bppHeader.Location = new System.Drawing.Point(4, 67); + this.bppHeader.Name = "bppHeader"; + this.bppHeader.RightMargin = 0; + this.bppHeader.Size = new System.Drawing.Size(173, 14); + this.bppHeader.TabIndex = 3; + this.bppHeader.TabStop = false; + this.bppHeader.Text = "Bit-depth"; + // + // HDPhotoSaveConfigWidget + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.Controls.Add(this.bppHeader); + this.Controls.Add(this.panel1); + this.Controls.Add(this.qualityUpDown); + this.Controls.Add(this.qualityLabel); + this.Controls.Add(this.qualitySlider); + this.Name = "HDPhotoSaveConfigWidget"; + this.Size = new System.Drawing.Size(180, 148); + ((System.ComponentModel.ISupportInitialize)(this.qualitySlider)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.qualityUpDown)).EndInit(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + #endregion + + private void QualitySlider_ValueChanged(object sender, System.EventArgs e) + { + if (this.qualityUpDown.Value != (decimal)this.qualitySlider.Value) + { + this.qualityUpDown.Value = (decimal)this.qualitySlider.Value; + } + + UpdateToken(); + } + + private void QualityUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (this.qualitySlider.Value != (int)this.qualityUpDown.Value) + { + this.qualitySlider.Value = (int)this.qualityUpDown.Value; + } + } + + private void QualityUpDown_Leave(object sender, System.EventArgs e) + { + QualityUpDown_ValueChanged(sender, e); + } + + private void QualityUpDown_Enter(object sender, System.EventArgs e) + { + qualityUpDown.Select(0, qualityUpDown.Text.Length); + } + + private void bpp24RB_CheckedChanged(object sender, EventArgs e) + { + UpdateToken(); + } + + private void bpp32RB_CheckedChanged(object sender, EventArgs e) + { + UpdateToken(); + } + } +} + diff --git a/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigWidget.resx b/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigWidget.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/extras/HDPhoto/HDPhoto/HDPhotoSaveConfigWidget.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/extras/HDPhoto/HDPhoto/Strings.Designer.cs b/extras/HDPhoto/HDPhoto/Strings.Designer.cs new file mode 100644 index 0000000..d56a740 --- /dev/null +++ b/extras/HDPhoto/HDPhoto/Strings.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.312 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PaintDotNet.Data { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PaintDotNet.Data.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to HD Photo (BETA). + /// + internal static string HDPhotoFileType_Name { + get { + return ResourceManager.GetString("HDPhotoFileType.Name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bit-depth. + /// + internal static string HDPhotoSaveConfigWidget_BppHeader_Text { + get { + return ResourceManager.GetString("HDPhotoSaveConfigWidget.BppHeader.Text", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Quality. + /// + internal static string HDPhotoSaveConfigWidget_QualitySlider_Text { + get { + return ResourceManager.GetString("HDPhotoSaveConfigWidget.QualitySlider.Text", resourceCulture); + } + } + } +} diff --git a/extras/HDPhoto/HDPhoto/Strings.resx b/extras/HDPhoto/HDPhoto/Strings.resx new file mode 100644 index 0000000..86e39ec --- /dev/null +++ b/extras/HDPhoto/HDPhoto/Strings.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + HD Photo (BETA) + + + Bit-depth + + + Quality + + \ No newline at end of file diff --git a/extras/PdnBench/App.ico b/extras/PdnBench/App.ico new file mode 100644 index 0000000..3a5525f Binary files /dev/null and b/extras/PdnBench/App.ico differ diff --git a/extras/PdnBench/AssemblyInfo.cs b/extras/PdnBench/AssemblyInfo.cs new file mode 100644 index 0000000..2a9c34b --- /dev/null +++ b/extras/PdnBench/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// 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("")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// 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 Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("3.22.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] diff --git a/extras/PdnBench/Benchmark.cs b/extras/PdnBench/Benchmark.cs new file mode 100644 index 0000000..1ab1407 --- /dev/null +++ b/extras/PdnBench/Benchmark.cs @@ -0,0 +1,66 @@ +using PaintDotNet.SystemLayer; +using System; + + + +namespace PdnBench +{ + /// + /// Base class that runs a benchmark + /// + public abstract class Benchmark + : IDisposable + { + private string name; + private Timing timing; + + public string Name + { + get + { + return this.name; + } + } + + protected virtual void OnBeforeExecute() + { + } + + protected abstract void OnExecute(); + + protected virtual void OnAfterExecute() + { + } + + public TimeSpan Execute() + { + OnBeforeExecute(); + ulong start = timing.GetTickCount(); + OnExecute(); + ulong end = timing.GetTickCount(); + OnAfterExecute(); + return new TimeSpan(0, 0, 0, 0, (int)(end - start)); + } + + public Benchmark(string name) + { + this.name = name; + timing = new Timing(); + } + + ~Benchmark() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + } +} diff --git a/extras/PdnBench/CompositionBenchmark.cs b/extras/PdnBench/CompositionBenchmark.cs new file mode 100644 index 0000000..6f1efd1 --- /dev/null +++ b/extras/PdnBench/CompositionBenchmark.cs @@ -0,0 +1,47 @@ +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PdnBench +{ + public delegate void SetLayerInfoDelegate(int layerIndex, Layer layer); + + public class CompositionBenchmark + : Benchmark + { + public const int Iterations = 30; + + private Document composeMe; + private Surface dstSurface; + private SetLayerInfoDelegate sliDelegate; + + protected override void OnBeforeExecute() + { + for (int i = 0; i < this.composeMe.Layers.Count; ++i) + { + this.sliDelegate(i, (Layer)this.composeMe.Layers[i]); + } + + base.OnBeforeExecute(); + } + + protected override void OnExecute() + { + for (int i = 0; i < Iterations; ++i) + { + composeMe.Invalidate(); + composeMe.Update(new RenderArgs(this.dstSurface)); + } + } + + public CompositionBenchmark(string name, Document composeMe, + Surface dstSurface, SetLayerInfoDelegate sliDelegate) + : base(name) + { + this.composeMe = composeMe; + this.dstSurface = dstSurface; + this.sliDelegate = sliDelegate; + } + } +} diff --git a/extras/PdnBench/EffectBenchmark.cs b/extras/PdnBench/EffectBenchmark.cs new file mode 100644 index 0000000..6f1df06 --- /dev/null +++ b/extras/PdnBench/EffectBenchmark.cs @@ -0,0 +1,81 @@ +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; + +namespace PdnBench +{ + /// + /// Summary description for EffectBenchmark. + /// + public class EffectBenchmark + : Benchmark + { + private Effect effect; + private EffectConfigToken token; + private Surface image; + + private Surface dst; + private PdnRegion region; + + private int iterations; + public int Iterations + { + get + { + return this.iterations; + } + } + + protected override void OnBeforeExecute() + { + this.dst = image.Clone(); + this.region = new PdnRegion(dst.Bounds); + } + + protected sealed override void OnExecute() + { + for (int i = 0; i < this.iterations; ++i) + { + EffectConfigToken localToken; + + if (this.token == null) + { + localToken = null; + } + else + { + localToken = (EffectConfigToken)this.token.Clone(); + } + + RenderArgs srcArgs = new RenderArgs(image); + RenderArgs dstArgs = new RenderArgs(dst); + + BackgroundEffectRenderer ber = new BackgroundEffectRenderer(effect, localToken, dstArgs, srcArgs, region, + 25 * Processor.LogicalCpuCount, Processor.LogicalCpuCount); + + ber.Start(); + ber.Join(); + + ber.Dispose(); + ber = null; + } + } + + protected override void OnAfterExecute() + { + region.Dispose(); + dst.Dispose(); + } + + public EffectBenchmark(string name, int iterations, Effect effect, EffectConfigToken token, Surface image) + : base(name + " (" + iterations + "x)") + { + this.effect = effect; + this.token = token; + this.image = image; + this.iterations = iterations; + } + } +} diff --git a/extras/PdnBench/GradientBenchmark.cs b/extras/PdnBench/GradientBenchmark.cs new file mode 100644 index 0000000..f973bd2 --- /dev/null +++ b/extras/PdnBench/GradientBenchmark.cs @@ -0,0 +1,84 @@ +using PaintDotNet; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Threading; + +namespace PdnBench +{ + public class GradientBenchmark + : Benchmark + { + private Surface surface; + private GradientRenderer renderer; + private PaintDotNet.Threading.ThreadPool threadPool; + private Rectangle[] rois; + private int iterations; + + public int Iterations + { + get + { + return this.iterations; + } + } + + private void RenderThreadProc(object indexObj) + { + int index = (int)indexObj; + this.renderer.Render(this.surface, this.rois, index, 1); + } + + protected override void OnBeforeExecute() + { + this.renderer.StartColor = ColorBgra.Black; + this.renderer.EndColor = ColorBgra.FromBgra(255, 128, 64, 64); + this.renderer.StartPoint = new PointF(surface.Width / 2, surface.Height / 2); + this.renderer.EndPoint = new PointF(0, 0); + this.renderer.AlphaBlending = true; + this.renderer.AlphaOnly = false; + + this.renderer.BeforeRender(); + + this.rois = new Rectangle[Processor.LogicalCpuCount]; + Utility.SplitRectangle(this.surface.Bounds, rois); + + this.threadPool = new PaintDotNet.Threading.ThreadPool(Processor.LogicalCpuCount, false); + + base.OnBeforeExecute(); + } + + protected override void OnExecute() + { + WaitCallback wc = new WaitCallback(RenderThreadProc); + + for (int n = 0; n < this.iterations; ++n) + { + for (int i = 0; i < this.rois.Length; ++i) + { + object iObj = BoxedConstants.GetInt32(i); + this.threadPool.QueueUserWorkItem(wc, iObj); + } + } + + this.threadPool.Drain(); + } + + protected override void OnAfterExecute() + { + this.renderer.AfterRender(); + this.threadPool = null; + base.OnAfterExecute(); + } + + public GradientBenchmark(string name, Surface surface, GradientRenderer renderer, int iterations) + : base(name) + { + this.surface = surface; + this.renderer = renderer; + this.iterations = iterations; + } + } +} diff --git a/extras/PdnBench/PdnBench.csproj b/extras/PdnBench/PdnBench.csproj new file mode 100644 index 0000000..997412b --- /dev/null +++ b/extras/PdnBench/PdnBench.csproj @@ -0,0 +1,220 @@ + + + true + bin\x86\Debug\ + DEBUG;TRACE + 285212672 + full + x86 + true + GlobalSuppressions.cs + prompt + + + bin\x86\Release\ + TRACE + 285212672 + true + + + x86 + true + GlobalSuppressions.cs + prompt + + + bin\x64\Release\ + TRACE + 285212672 + true + + + x64 + true + GlobalSuppressions.cs + prompt + + + Local + 9.0.21022 + 2.0 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F} + Debug + AnyCPU + App.ico + + + PdnBench + + + JScript + Grid + IE50 + false + Exe + PdnBench + OnBuildSuccess + + + + + + + 2.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + AnyCPU + + + true + bin\x64\Debug\ + DEBUG;TRACE + 285212672 + full + x64 + true + GlobalSuppressions.cs + prompt + + + + False + ..\..\src\bin\Release\PaintDotNet.Base.dll + + + False + ..\..\src\bin\Release\PaintDotNet.Core.dll + + + False + ..\..\src\bin\Release\PaintDotNet.Data.dll + + + False + ..\..\src\bin\Release\PaintDotNet.Effects.dll + + + False + ..\..\src\bin\Release\PaintDotNet.Resources.dll + + + False + ..\..\src\bin\Release\PaintDotNet.SystemLayer.dll + + + System + + + + System.Drawing + + + + + + + + + Code + + + Code + + + + Code + + + + Code + + + Code + + + + + + + + False + .NET Framework 2.0 %28x86%29 + true + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + + + + + copy $(TargetPath) $(ProjectDir)\..\..\src\$(OutDir) + + \ No newline at end of file diff --git a/extras/PdnBench/PdnBench.sln b/extras/PdnBench/PdnBench.sln new file mode 100644 index 0000000..4059198 --- /dev/null +++ b/extras/PdnBench/PdnBench.sln @@ -0,0 +1,31 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdnBench", "PdnBench.csproj", "{8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Debug|x64.ActiveCfg = Debug|x64 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Debug|x64.Build.0 = Debug|x64 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Debug|x86.ActiveCfg = Debug|x86 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Debug|x86.Build.0 = Debug|x86 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Release|Any CPU.Build.0 = Release|Any CPU + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Release|x64.ActiveCfg = Release|x64 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Release|x64.Build.0 = Release|x64 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Release|x86.ActiveCfg = Release|x86 + {8D3BCCE5-9DFC-4A53-8307-74E124DB4B6F}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extras/PdnBench/ResizeBenchmark.cs b/extras/PdnBench/ResizeBenchmark.cs new file mode 100644 index 0000000..75883c6 --- /dev/null +++ b/extras/PdnBench/ResizeBenchmark.cs @@ -0,0 +1,117 @@ +using PaintDotNet; +using PaintDotNet.SystemLayer; +using PaintDotNet.Threading; +using System; +using System.Drawing; +using System.Threading; + + +namespace PdnBench +{ + /// + /// Summary description for ResizeBenchmark. + /// + public class ResizeBenchmark + : Benchmark + { + private Surface src; + private Surface dst; + private PaintDotNet.Threading.ThreadPool threadPool; + private Rectangle[] rects; + + private sealed class FitSurfaceContext + { + private Surface dstSurface; + private Surface srcSurface; + private Rectangle[] dstRois; + private ResamplingAlgorithm algorithm; + + public Surface DstSurface + { + get + { + return dstSurface; + } + } + + public Surface SrcSurface + { + get + { + return srcSurface; + } + } + + public Rectangle[] DstRois + { + get + { + return dstRois; + } + } + + public ResamplingAlgorithm Algorithm + { + get + { + return algorithm; + } + } + + public event Procedure RenderedRect; + private void OnRenderedRect() + { + if (RenderedRect != null) + { + RenderedRect(); + } + } + + public void FitSurface(object context) + { + int index = (int)context; + dstSurface.FitSurface(algorithm, srcSurface, dstRois[index]); + } + + public FitSurfaceContext(Surface dstSurface, Surface srcSurface, Rectangle[] dstRois, ResamplingAlgorithm algorithm) + { + this.dstSurface = dstSurface; + this.srcSurface = srcSurface; + this.dstRois = dstRois; + this.algorithm = algorithm; + } + } + + protected override void OnBeforeExecute() + { + this.threadPool = new PaintDotNet.Threading.ThreadPool(); + rects = new Rectangle[Processor.LogicalCpuCount]; + Utility.SplitRectangle(this.dst.Bounds, rects); + base.OnBeforeExecute(); + } + + protected override void OnExecute() + { + FitSurfaceContext fsc = new FitSurfaceContext(this.dst, this.src, rects, ResamplingAlgorithm.Bicubic); + for (int i = 0; i < this.rects.Length; ++i) + { + this.threadPool.QueueUserWorkItem(new WaitCallback(fsc.FitSurface), (object)i); + } + + this.threadPool.Drain(); + } + + protected override void OnAfterExecute() + { + this.threadPool = null; + base.OnAfterExecute (); + } + + public ResizeBenchmark(string name, Surface src, Surface dst) + : base(name) + { + this.src = src; + this.dst = dst; + } + } +} diff --git a/extras/PdnBench/Startup.cs b/extras/PdnBench/Startup.cs new file mode 100644 index 0000000..f50ff40 --- /dev/null +++ b/extras/PdnBench/Startup.cs @@ -0,0 +1,619 @@ +#define EFFECTS +#define RESIZE +#define GRADIENT +#define COMPOSITION +#define TRANSFORM +#define BLIT + +using Microsoft.Win32; +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace PdnBench +{ + class Startup + { + // various benchmark wide settings + static bool useTsvOutput = false; + const string defaultImageName = "PdnBench.cat.jpg"; + static string benchmarkImageName = defaultImageName; + + static void PrintHelp() + { + Console.WriteLine("PdnBench command line arguments:"); + Console.WriteLine(" /? : show this help"); + Console.WriteLine(" /image : use an alternate image for benchmarking"); + Console.WriteLine(" /proc : set number of 'logical' processors to use"); + Console.WriteLine(" /tsv : output in tab-separated-value format, easy to import into excel"); + Console.WriteLine(); + } + + static int GetCpuSpeed() + { + int mhz = -1; + string keyName = @"HARDWARE\DESCRIPTION\System\CentralProcessor\0"; + string valueName = @"~MHz"; + + try + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName, false)) + { + if (key != null) + { + object value = key.GetValue(valueName); + mhz = (int)value; + } + } + } + + catch (Exception) + { + mhz = -1; + } + + return mhz; + } + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main(string[] args) + { + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "/proc": + if (i + 1 == args.Length) + { + Console.WriteLine("Use /proc to specify number of processors"); + return; + } + + int numProcs; + + if (Int32.TryParse(args[i + 1], out numProcs)) + { + // only increment i if successful b/c we're going to continue the run + // with the default # of processors and don't want to automatically + // eat the next parameter. + ++i; + Processor.LogicalCpuCount = numProcs; + } + else + { + Console.WriteLine("You must specify a integer for /proc , continuing with default"); + } + break; + + case "/image": + if (i + 1 == args.Length) + { + Console.WriteLine("Use /image to specify a file to perform benchmark with"); + return; + } + + ++i; + benchmarkImageName = args[i]; + + if (!System.IO.File.Exists(benchmarkImageName)) + { + Console.WriteLine("Specified image doesn't exist"); + return; + } + break; + + case "/tsv": + useTsvOutput = true; + break; + + case "/?": + PrintHelp(); + return; + + default: + break; + } + } + + //Processor.LogicalCpuCount = 1; + Console.WriteLine("PdnBench v" + PdnInfo.GetVersion()); + Console.WriteLine("Running in " + (8 * Marshal.SizeOf(typeof(IntPtr))) + "-bit mode on Windows " + + Environment.OSVersion.Version.ToString() + " " + + OS.Revision + (OS.Revision.Length > 0 ? " " : string.Empty) + + OS.Type + " " + + Processor.NativeArchitecture.ToString().ToLower()); + + Console.WriteLine("Processor: " + Processor.LogicalCpuCount + "x \"" + Processor.CpuName + "\" @ ~" + GetCpuSpeed() + " MHz"); + Console.WriteLine("Memory: " + ((Memory.TotalPhysicalBytes / 1024) / 1024) + " MB"); + Console.WriteLine(); + + Console.WriteLine("Using " + Processor.LogicalCpuCount + " threads."); + + ArrayList benchmarks = new ArrayList(); + + Document document; + + Console.Write("Loading image ... "); + + Stream imageStream = null; + try + { + imageStream = (defaultImageName == benchmarkImageName) ? + Assembly.GetExecutingAssembly().GetManifestResourceStream(benchmarkImageName) : + new FileStream(benchmarkImageName, FileMode.Open); + + JpegFileType jft = new JpegFileType(); + document = jft.Load(imageStream); + } + + finally + { + if (imageStream != null) + { + imageStream.Dispose(); + } + } + + Console.WriteLine("(" + document.Width + " x " + document.Height + ") done"); + + Surface surface = ((BitmapLayer)document.Layers[0]).Surface; + + Surface dst = new Surface(surface.Width * 4, surface.Height * 4); + +#if EFFECTS + for (double i = 0; i < (2 * Math.PI); i += 70.0 * ((2 * Math.PI) / 360.0)) + { + benchmarks.Add( + new EffectBenchmark("Rotate/Zoom at " + ((i * 180.0) / Math.PI).ToString("F2") + " degrees", + 3, + new PaintDotNet.Effects.RotateZoomEffect(), + new PaintDotNet.Effects.RotateZoomEffectConfigToken( + true, + (float)(Math.PI * 0.3f), + (float)((Math.PI * -0.4) + i), + 50, + 0.5f, + new PointF(-0.2f, 0.3f), + false, + true), + surface)); + } + + for (int i = 1; i <= 4; i += 3) + { + for (int j = 10; j < 100; j += 75) + { + OilPaintingEffect e = new OilPaintingEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[OilPaintingEffect.PropertyNames.BrushSize].Value = i; + props[OilPaintingEffect.PropertyNames.Coarseness].Value = j; + + benchmarks.Add( + new EffectBenchmark( + "Oil Painting, brush size = " + i + ", coarseness = " + j, + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + } + + for (int i = 2; i <= 200; i += i) + { + GaussianBlurEffect e = new GaussianBlurEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[GaussianBlurEffect.PropertyNames.Radius].Value = i; + + benchmarks.Add( + new EffectBenchmark( + "Gaussian Blur with radius of " + i, + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + for (int i = 1; i <= 4; i += 3) + { + SharpenEffect e = new SharpenEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[SharpenEffect.PropertyNames.Amount].Value = i; + + benchmarks.Add( + new EffectBenchmark( + "Sharpen with value of " + i, + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + for (int i = 81; i >= 5; i /= 3) + { + CloudsEffect e = new CloudsEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props["Scale"].Value = 50; + props["Power"].Value = (double)i / 100.0; + props["Seed"].Value = 12345 % 255; + props["BlendOp"].Value = typeof(UserBlendOps.NormalBlendOp); + + benchmarks.Add( + new EffectBenchmark( + "Clouds, roughness = " + i, + 2, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + for (int i = 4; i <= 64; i *= 4) + { + MedianEffect e = new MedianEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[MedianEffect.PropertyNames.Radius].Value = i; + props[MedianEffect.PropertyNames.Percentile].Value = 50; + + benchmarks.Add( + new EffectBenchmark( + "Median, radius " + i, + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + for (int i = 4; i <= 64; i *= 4) + { + UnfocusEffect e = new UnfocusEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[UnfocusEffect.PropertyNames.Radius].Value = i; + + benchmarks.Add( + new EffectBenchmark( + "Unfocus, radius " + i, + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + { + MotionBlurEffect e = new MotionBlurEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[MotionBlurEffect.PropertyNames.Angle].Value = 0.0; + props[MotionBlurEffect.PropertyNames.Distance].Value = 15; + props[MotionBlurEffect.PropertyNames.Centered].Value = true; + + benchmarks.Add( + new EffectBenchmark( + "Motion Blur, Horizontal", + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + { + MotionBlurEffect e = new MotionBlurEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + props[MotionBlurEffect.PropertyNames.Angle].Value = 90.0; + props[MotionBlurEffect.PropertyNames.Distance].Value = 15; + props[MotionBlurEffect.PropertyNames.Centered].Value = true; + + benchmarks.Add( + new EffectBenchmark( + "Motion Blur, Vertical", + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + { + ReduceNoiseEffect e = new ReduceNoiseEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + + benchmarks.Add( + new EffectBenchmark( + "Reduce Noise (3x)", + 3, + e, + new PropertyBasedEffectConfigToken(props), + dst)); + } + + { + MandelbrotFractalEffect e = new MandelbrotFractalEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + + benchmarks.Add( + new EffectBenchmark( + "Mandelbrot Fractal", + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + + { + JuliaFractalEffect e = new JuliaFractalEffect(); + PropertyCollection props = e.CreatePropertyCollection(); + + benchmarks.Add( + new EffectBenchmark( + "Julia Fractal", + 1, + e, + new PropertyBasedEffectConfigToken(props), + surface)); + } + +#endif + +#if RESIZE + // Resize benchmarks + for (int i = 1; i < 8; i += 2) + { + int newWidth = i * (dst.Width / 8); + int newHeight = i * (dst.Height / 8); + + Surface dstWindow = dst.CreateWindow(new Rectangle(0, 0, newWidth, newHeight)); + benchmarks.Add(new ResizeBenchmark("Resize from " + surface.Width + "x" + surface.Height + " to " + newWidth + "x" + newHeight, surface, dstWindow)); + benchmarks.Add(new ResizeBenchmark("Resize from " + newWidth + "x" + newHeight + " to " + surface.Width + "x" + surface.Height, dstWindow, surface)); + } +#endif + +#if GRADIENT + // Gradient benchmarks + benchmarks.Add(new GradientBenchmark( + "Linear reflected gradient @ " + dst.Width + "x" + dst.Height + " (5x)", + dst, + new GradientRenderers.LinearReflected(false, new UserBlendOps.NormalBlendOp()), + 2)); + + benchmarks.Add(new GradientBenchmark( + "Conical gradient @ " + dst.Width + "x" + dst.Height + " (5x)", + dst, + new GradientRenderers.Conical(false, new UserBlendOps.NormalBlendOp()), + 2)); + + benchmarks.Add(new GradientBenchmark( + "Radial gradient @ " + dst.Width + "x" + dst.Height + " (5x)", + dst, + new GradientRenderers.Radial(false, new UserBlendOps.NormalBlendOp()), + 2)); +#endif + +#if COMPOSITION + // Composition benchmarks + Document doc1 = new Document(surface.Size); + BitmapLayer layer1 = Layer.CreateBackgroundLayer(doc1.Width, doc1.Height); + layer1.Surface.CopySurface(surface); + doc1.Layers.Add(layer1); + doc1.Layers.Add(layer1.Clone()); + doc1.Layers.Add(layer1.Clone()); + doc1.Layers.Add(layer1.Clone()); + + benchmarks.Add(new CompositionBenchmark("Compositing one layer, Normal blend mode, 255 opacity (" + CompositionBenchmark.Iterations + "x)", + doc1, + surface, + delegate(int layerIndex, Layer layer) + { + if (layerIndex == 0) + { + layer.Visible = true; + layer.Opacity = 255; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp()); + } + else + { + layer.Visible = false; + } + })); + + benchmarks.Add(new CompositionBenchmark("Compositing one layer, Normal blend mode, 128 opacity (" + CompositionBenchmark.Iterations + "x)", + doc1, + surface, + delegate(int layerIndex, Layer layer) + { + if (layerIndex == 0) + { + layer.Visible = true; + layer.Opacity = 128; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp()); + } + else + { + layer.Visible = false; + } + })); + + benchmarks.Add(new CompositionBenchmark("Compositing four layers, Normal blend mode, 255 opacity (" + CompositionBenchmark.Iterations + "x)", + doc1, + surface, + delegate(int layerIndex, Layer layer) + { + layer.Visible = true; + layer.Opacity = 255; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp()); + })); + + benchmarks.Add(new CompositionBenchmark("Compositing four layers, Normal blend mode, 255 (layer 0) and 128 (layer 1-3) opacity (" + CompositionBenchmark.Iterations + "x)", doc1, surface, + delegate(int layerIndex, Layer layer) + { + layer.Visible = true; + layer.Opacity = 128; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp()); + })); + + benchmarks.Add(new CompositionBenchmark("Compositing four layers, Normal blend mode, 128 opacity (" + CompositionBenchmark.Iterations + "x)", doc1, surface, + delegate(int layerIndex, Layer layer) + { + layer.Visible = true; + layer.Opacity = 128; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp()); + })); + + benchmarks.Add(new CompositionBenchmark("Compositing three layers, Normal+Multiply+Overlay blending, 150+255+170 opacity (" + CompositionBenchmark.Iterations + "x)", doc1, surface, + delegate(int layerIndex, Layer layer) + { + if (layerIndex == 0) + { + layer.Visible = true; + layer.Opacity = 150; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.NormalBlendOp()); + } + else if (layerIndex == 1) + { + layer.Visible = true; + layer.Opacity = 255; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.MultiplyBlendOp()); + } + else if (layerIndex == 2) + { + layer.Visible = true; + layer.Opacity = 170; + ((BitmapLayer)layer).SetBlendOp(new UserBlendOps.OverlayBlendOp()); + } + else + { + layer.Visible = false; + } + })); +#endif + +#if TRANSFORM + // Transform benchmarks + Matrix m = new Matrix(); + m.Reset(); + + MaskedSurface msSimple = new MaskedSurface(surface, new PdnRegion(surface.Bounds)); // simple masked surface + + PdnRegion complexRegion = new PdnRegion(surface.Bounds); + + // cut 4 holes in region 1 to form a complex clipping surface + for (int x = -1; x < 3; ++x) + { + for (int y = -1; y < 3; ++y) + { + int left = (1 + (x * 3)) * (surface.Width / 6); + int top = (1 + (x * 3)) * (surface.Height / 6); + int right = (2 + (x * 3)) * (surface.Width / 6); + int bottom = (2 + (x * 3)) * (surface.Height / 6); + + Rectangle rect = Rectangle.FromLTRB(left, top, right, bottom); + PdnGraphicsPath path = new PdnGraphicsPath(); + path.AddEllipse(rect); + complexRegion.Exclude(path); + } + } + + MaskedSurface msComplex = new MaskedSurface(surface, complexRegion); + + benchmarks.Add(new TransformBenchmark("Transform simple surface, no transform, nearest neighbor resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m, + false)); + + benchmarks.Add(new TransformBenchmark("Transform complex surface, no transform, nearest neighbor resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m, + false)); + + benchmarks.Add(new TransformBenchmark("Transform simple surface, no transform, bilinear resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m, + true)); + + benchmarks.Add(new TransformBenchmark("Transform complex surface, no transform, bilinear resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m, + true)); + + Matrix m2 = m.Clone(); + m2.RotateAt(45.0f, new PointF(surface.Width / 2, surface.Height / 2)); + + benchmarks.Add(new TransformBenchmark("Transform simple surface, 45 deg. rotation about center, bilinear resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m2, + true)); + + benchmarks.Add(new TransformBenchmark("Transform complex surface, 45 deg. rotation about center, bilinear resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m2, + true)); + + Matrix m3 = m.Clone(); + m3.Scale(0.5f, 0.75f); + + benchmarks.Add(new TransformBenchmark("Transform simple surface, 50% x-scaling 75% y-scaling, bilinear resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m3, + true)); + + benchmarks.Add(new TransformBenchmark("Transform complex surface, 50% x-scaling 75% y-scaling, bilinear resampling (" + TransformBenchmark.Iterations + "x)", + surface, + msSimple, + m3, + true)); +#endif + +#if BLIT + // Blit benchmarks + benchmarks.Add(new ZoomOutBlitBenchmark("Zoom out, rotated grid multisampling, 66% (" + ZoomOutBlitBenchmark.IterationCount + "x)", + surface, + dst, + new Size((surface.Width * 2) / 3, (surface.Height * 2) / 3))); + + benchmarks.Add(new ZoomOutBlitBenchmark("Zoom out, rotated grid multisampling, 28% (" + ZoomOutBlitBenchmark.IterationCount + "x)", + surface, + dst, + new Size((surface.Width * 28) / 100, (surface.Height * 28) / 100))); + + benchmarks.Add(new ZoomOneToOneBlitBenchmark("Zoom 1:1, straight blit (" + ZoomOneToOneBlitBenchmark.IterationCount + "x)", + surface, + dst.CreateWindow(new Rectangle(0, 0, surface.Width, surface.Height)))); +#endif + + // Run benchmarks! + Timing timing = new Timing(); + ulong start = timing.GetTickCount(); + + foreach (Benchmark benchmark in benchmarks) + { + Console.Write(benchmark.Name + (useTsvOutput ? "\t" : " ... ")); + TimeSpan timeSpan = benchmark.Execute(); + Console.WriteLine(" " + timeSpan.TotalMilliseconds.ToString() + (useTsvOutput ? "\t" : "") + " milliseconds"); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + ulong end = timing.GetTickCount(); + + Console.WriteLine(); + Console.WriteLine("Total time: " + (useTsvOutput ? "\t" : "") + (end - start).ToString() + (useTsvOutput ? "\t" : "") + " milliseconds"); + Console.WriteLine(); + } + } +} diff --git a/extras/PdnBench/TransformBenchmark.cs b/extras/PdnBench/TransformBenchmark.cs new file mode 100644 index 0000000..498b399 --- /dev/null +++ b/extras/PdnBench/TransformBenchmark.cs @@ -0,0 +1,36 @@ +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Text; + +namespace PdnBench +{ + class TransformBenchmark + : Benchmark + { + public const int Iterations = 30; + private Surface dst; + private MaskedSurface src; + private Matrix transform; + private bool highQuality; + + protected override void OnExecute() + { + for (int i = 0; i < Iterations; ++i) + { + this.src.Draw(this.dst, this.transform, this.highQuality ? ResamplingAlgorithm.Bilinear : ResamplingAlgorithm.NearestNeighbor); + } + } + + public TransformBenchmark(string name, Surface dst, MaskedSurface src, Matrix transform, bool highQuality) + : base(name) + { + this.dst = dst; + this.src = src; + this.transform = transform.Clone(); + this.highQuality = highQuality; + } + } +} diff --git a/extras/PdnBench/ZoomOneToOneBlitBenchmark.cs b/extras/PdnBench/ZoomOneToOneBlitBenchmark.cs new file mode 100644 index 0000000..7e90228 --- /dev/null +++ b/extras/PdnBench/ZoomOneToOneBlitBenchmark.cs @@ -0,0 +1,79 @@ +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace PdnBench +{ + public class ZoomOneToOneBlitBenchmark + : Benchmark + { + public const int IterationCount = 1000; + + private Surface source; + private Surface dst; + private Rectangle[] blitRects; + private Surface[] blitWindows; + private PaintDotNet.Threading.ThreadPool threadPool; + + protected override void OnBeforeExecute() + { + Rectangle blitRect = new Rectangle(0, 0, source.Width, source.Height); + + this.blitRects = new Rectangle[PaintDotNet.SystemLayer.Processor.LogicalCpuCount]; + Utility.SplitRectangle(blitRect, this.blitRects); + + this.blitWindows = new Surface[this.blitRects.Length]; + for (int i = 0; i < blitRects.Length; ++i) + { + blitWindows[i] = this.dst.CreateWindow(this.blitRects[i]); + } + + this.threadPool = new PaintDotNet.Threading.ThreadPool(); + + base.OnBeforeExecute(); + } + + private void Render(object indexObj) + { + int index = (int)indexObj; + SurfaceBoxBaseRenderer.RenderOneToOne(this.blitWindows[index], this.source, this.blitRects[index].Location); + } + + protected override void OnExecute() + { + System.Threading.WaitCallback renderDelegate = new System.Threading.WaitCallback(Render); + + for (int i = 0; i < IterationCount; ++i) + { + for (int j = 0; j < this.blitRects.Length; ++j) + { + object jObj = BoxedConstants.GetInt32(j); + this.threadPool.QueueUserWorkItem(renderDelegate, jObj); + } + + this.threadPool.Drain(); + } + } + + protected override void OnAfterExecute() + { + for (int i = 0; i < this.blitWindows.Length; ++i) + { + this.blitWindows[i].Dispose(); + this.blitWindows[i] = null; + } + + this.threadPool = null; + base.OnAfterExecute(); + } + + public ZoomOneToOneBlitBenchmark(string name, Surface source, Surface dst) + : base(name) + { + this.source = source; + this.dst = dst; + } + } +} diff --git a/extras/PdnBench/ZoomOutBlitBenchmark.cs b/extras/PdnBench/ZoomOutBlitBenchmark.cs new file mode 100644 index 0000000..bea73d3 --- /dev/null +++ b/extras/PdnBench/ZoomOutBlitBenchmark.cs @@ -0,0 +1,82 @@ +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace PdnBench +{ + public class ZoomOutBlitBenchmark + : Benchmark + { + public const int IterationCount = 1000; + + private Surface source; + private Surface dst; + private Size blitSize; + private Rectangle[] blitRects; + private Surface[] blitWindows; + private PaintDotNet.Threading.ThreadPool threadPool; + + protected override void OnBeforeExecute() + { + Rectangle blitRect = new Rectangle(0, 0, blitSize.Width, blitSize.Height); + + this.blitRects = new Rectangle[PaintDotNet.SystemLayer.Processor.LogicalCpuCount]; + Utility.SplitRectangle(blitRect, this.blitRects); + + this.blitWindows = new Surface[this.blitRects.Length]; + for (int i = 0; i < blitRects.Length; ++i) + { + blitWindows[i] = this.dst.CreateWindow(this.blitRects[i]); + } + + this.threadPool = new PaintDotNet.Threading.ThreadPool(); + + base.OnBeforeExecute(); + } + + private void Render(object indexObj) + { + int index = (int)indexObj; + SurfaceBoxBaseRenderer.RenderZoomOutRotatedGridMultisampling(this.blitWindows[index], this.source, + this.blitRects[index].Location, this.blitSize); + } + + protected override void OnExecute() + { + System.Threading.WaitCallback renderDelegate = new System.Threading.WaitCallback(Render); + + for (int i = 0; i < IterationCount; ++i) + { + for (int j = 0; j < this.blitRects.Length; ++j) + { + object jObj = BoxedConstants.GetInt32(j); + this.threadPool.QueueUserWorkItem(renderDelegate, jObj); + } + + this.threadPool.Drain(); + } + } + + protected override void OnAfterExecute() + { + for (int i = 0; i < this.blitWindows.Length; ++i) + { + this.blitWindows[i].Dispose(); + this.blitWindows[i] = null; + } + + this.threadPool = null; + base.OnAfterExecute(); + } + + public ZoomOutBlitBenchmark(string name, Surface source, Surface dst, Size blitSize) + : base(name) + { + this.source = source; + this.dst = dst; + this.blitSize = blitSize; + } + } +} diff --git a/extras/PdnBench/cat.jpg b/extras/PdnBench/cat.jpg new file mode 100644 index 0000000..8aa21ca Binary files /dev/null and b/extras/PdnBench/cat.jpg differ diff --git a/extras/PdnBench/readme.txt b/extras/PdnBench/readme.txt new file mode 100644 index 0000000..657993c --- /dev/null +++ b/extras/PdnBench/readme.txt @@ -0,0 +1,21 @@ +PdnBench, v2.6 Release +---------------------- +This is a command-line utility that runs through several benchmarks that +exercise various aspects of Paint.NET. Every benchmark is multithreaded, and +takes advantage of multiprocessor or multicore systems. + +To use, copy the executables to the directory where Paint.NET is installed, +and then run it from the command-line. + +Use "pdnbench /?" for a list of command-line parameters. + +PdnBench.exe will run in 32-bit mode on 32-bit systems, or 64-bit on 64-bit +systems (must have a 64-bit CPU and a 64-bit OS). + +PdnBench_32bitOnly.exe is the same program but it will run in 32-bit mode even +on 64-bit systems. This is useful for comparing 32-bit and 64-bit performance. + +The source code is available as part of the main Paint.NET source code +distribution, which is available at the main website: + + http://www.eecs.wsu.edu/paint.net \ No newline at end of file diff --git a/extras/ResxCheck/ResxCheck.sln b/extras/ResxCheck/ResxCheck.sln new file mode 100644 index 0000000..b4d8cc5 --- /dev/null +++ b/extras/ResxCheck/ResxCheck.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResxCheck", "ResxCheck\ResxCheck.csproj", "{D4EEE8A0-F37A-43A4-A80A-6F90D4E9206B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D4EEE8A0-F37A-43A4-A80A-6F90D4E9206B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4EEE8A0-F37A-43A4-A80A-6F90D4E9206B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4EEE8A0-F37A-43A4-A80A-6F90D4E9206B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4EEE8A0-F37A-43A4-A80A-6F90D4E9206B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extras/ResxCheck/ResxCheck/Program.cs b/extras/ResxCheck/ResxCheck/Program.cs new file mode 100644 index 0000000..6e4ac52 --- /dev/null +++ b/extras/ResxCheck/ResxCheck/Program.cs @@ -0,0 +1,386 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Resources; +using System.Text; +using System.Threading; +using System.Xml.Linq; +using System.Xml.XPath; + +[assembly: AssemblyTitle("ResxCheck")] +[assembly: AssemblyProduct("ResxCheck")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC")] +[assembly: AssemblyVersion("3.30.*")] +[assembly: AssemblyFileVersion("3.30.0.0")] + +namespace ResxCheck +{ + public enum Warning + { + Duplicate, + Missing, + ExtraFormatTags, + MalformedFormat, + Extra, + } + + public static class Extensions + { + public static IEnumerable DoForEach(this IEnumerable source, Action f) + { + foreach (T item in source) + { + f(item); + yield return item; + } + } + + public static void Execute(this IEnumerable list) + { + foreach (var item in list) + { + // Nothing. Hopefully you have a Do() clause in there. + } + } + } + + class Program + { + static void PrintHeader(TextWriter output) + { + output.WriteLine("ResxCheck v{0}", Assembly.GetExecutingAssembly().GetName().Version); + output.WriteLine("Copyright (C) 2008 dotPDN LLC, http://www.dotpdn.com/"); + output.WriteLine(); + } + + static void PrintUsage(TextWriter output) + { + string ourName = Path.GetFileName(Assembly.GetExecutingAssembly().CodeBase); + + output.WriteLine("Usage:"); + output.WriteLine(" {0} [[mui1.resx] [mui2.resx] [mui3.resx] ... [muiN.resx]]", ourName); + output.WriteLine(); + output.WriteLine("base.resx should be the original resx that is supplied by the developer or design team."); + output.WriteLine(); + output.WriteLine("mui1.resx through muiN.resx should be the translated resx files based off of base.resx."); + output.WriteLine("You may specify as many mui resx files as you would like to check."); + output.WriteLine("(You can also not specify any, and then only base.resx will be checked)"); + output.WriteLine("TIP: You can specify a wildcard, such as *.resx"); + output.WriteLine(); + output.WriteLine("This program will check for:"); + output.WriteLine(" * base.resx must not have any string defined more than once"); + output.WriteLine(" * base.resx must not have any strings with incorrect formatting tags, e.g. having a { but no closing }, or vice versa"); + output.WriteLine(); + output.WriteLine("If any mui.resx files are specified, then these rules will also be checked:"); + output.WriteLine(" * mui.resx must not have any string defined more than once"); + output.WriteLine(" * mui.resx must have all the strings that base.resx defines"); + output.WriteLine(" * mui.resx must not have any strings defined that are not defined in base.resx"); + output.WriteLine(" * mui.resx must not have any strings with incorrect formatting tags, e.g. having a { but no closing }, or vice versa"); + output.WriteLine(" * mui.resx must not have any additional formatting tags, e.g. {2}"); + output.WriteLine(); + output.WriteLine("Examples:"); + output.WriteLine(); + output.WriteLine(" {0} strings.resx Strings.DE.resx String.IT.resx String.JP.resx", ourName); + output.WriteLine(" This will use strings.resx as the 'base', and then check the DE, IT, and JP translations to ensure they pass the constraints and rules described above."); + output.WriteLine(); + output.WriteLine(" {0} strings.resx translations\\*.resx", ourName); + output.WriteLine(" This will use strings.resx as the 'base', and then all of the RESX files found in the translations directory will be validated against it."); + } + + delegate void WarnFn(Warning reason, string extraFormat, params object[] extraArgs); + + static void Warn(TextWriter output, string name, Warning reason, string extraFormat, params object[] extraArgs) + { + string reasonText; + + switch (reason) + { + case Warning.Duplicate: + reasonText = "duplicate string name"; + break; + + case Warning.Extra: + reasonText = "extra string"; + break; + + case Warning.ExtraFormatTags: + reasonText = "extra format tags"; + break; + + case Warning.MalformedFormat: + reasonText = "invalid format"; + break; + + case Warning.Missing: + reasonText = "missing string"; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + output.WriteLine( + "{0}: {1}{2}", + name, + reasonText, + string.IsNullOrEmpty(extraFormat) ? + "" : + string.Format(": {0}", string.Format(extraFormat, extraArgs))); + } + + static void AnalyzeSingle(WarnFn warnFn, IEnumerable> grouping) + { + // Verify that all strings can have their formatting requirements satisified + grouping.SelectMany(item => item.Select(val => new KeyValuePair(item.Key, val))) + .Where(kvPair => CountFormatArgs(kvPair.Value, 100) == -1) + .DoForEach(kvPair => warnFn(Warning.MalformedFormat, "'{0}' = '{1}'", kvPair.Key, kvPair.Value)) + .Execute(); + + // Verify there are no strings defined more than once. + grouping.Where(item => item.Take(2).Count() > 1) + .SelectMany(item => item.Select(val => new KeyValuePair(item.Key, val))) + .DoForEach(val => warnFn(Warning.Duplicate, "'{0}' = '{1}'", val.Key, val.Value)) + .Execute(); + } + + static int CountFormatArgs(string formatString) + { + return CountFormatArgs(formatString, 1000); + } + + static int CountFormatArgs(string formatString, int max) + { + bool isError; + List argsList = new List(); + + do + { + string[] args = argsList.ToArray(); + + try + { + string.Format(formatString, args); + isError = false; + } + + catch (Exception) + { + isError = true; + } + + argsList.Add("x"); + } while (isError && (max == -1 || argsList.Count <= max)); + + int count = argsList.Count - 1; + + if (count == max) + { + return -1; + } + else + { + return argsList.Count - 1; + } + } + + static void AnalyzeMuiWithBase( + WarnFn baseWarnFn, + IEnumerable> baseList, + WarnFn muiWarnFn, + IEnumerable> muiList) + { + AnalyzeSingle(muiWarnFn, muiList); + + var baseDict = baseList.ToDictionary(v => v.Key, v => v.First()); + + var muiDict = muiList.ToDictionary(v => v.Key, v => v.First()); + + // Verify that mui has everything that base defines + baseDict.Keys.Except(muiDict.Keys) + .DoForEach(key => muiWarnFn(Warning.Missing, key)) + .Execute(); + + // Verify that mui doesn't have any extra entries + muiDict.Keys.Except(baseDict.Keys) + .DoForEach(key => muiWarnFn(Warning.Extra, "'{0}' = '{1}'", key, muiDict[key])) + .Execute(); + + // Verify that the formatting of the strings works. So if base defines a string + // with {0}, {1} then that must be in the mui string as well. + // To do this, we convert baseList and muiList to a lookup from key -> val1,val2 + // Then, we filter to only the items where calling string.Format(val1) raises an exception + // Then, for each of these we determine how many formatting parameters val1 requires, + // and then make sure that val2 does not throw an exception when formatted with that many. + baseDict.Keys + .Where(key => muiDict.ContainsKey(key)) + .ToDictionary(key => key, key => new { Base = baseDict[key], Mui = muiDict[key] }) + .Where(item => CountFormatArgs(item.Value.Base) != -1) + .Where(item => CountFormatArgs(item.Value.Mui) > CountFormatArgs(item.Value.Base)) + .Select(item => item.Key) + .DoForEach(key => muiWarnFn(Warning.ExtraFormatTags, "'{0}' = '{1}'", key, muiDict[key])) + .Execute(); + } + + static IEnumerable> FromResX(string resxFileName) + { + XDocument xDoc = XDocument.Load(resxFileName); + + var query = from xe in xDoc.XPathSelectElements("/root/data") + let attributes = xe.Attributes() + let name = (from attribute in attributes + where attribute.Name.LocalName == "name" + select attribute.Value) + let elements = xe.Elements() + let value = (from element in elements + where element.Name.LocalName == "value" + select element.Value) + select new KeyValuePair(name.First(), value.First()); + + return query; + } + + static T Eval(Func f, T valueIfError) + { + T value; + + try + { + value = f(); + return value; + } + + catch (Exception) + { + return valueIfError; + } + } + + static int Main(string[] args) + { + PrintHeader(Console.Out); + + if (args.Length < 1) + { + PrintUsage(Console.Out); + return 1; + } + + DateTime startTime = DateTime.Now; + Console.WriteLine("--- Start @ {0}", startTime.ToLongTimeString()); + + string dir = Environment.CurrentDirectory; + + string baseName = args[0]; + string basePathName = Path.GetFullPath(baseName); + string baseDir = Path.GetDirectoryName(basePathName); + string baseFileName = Path.GetFileName(basePathName); + string baseFileNameNoExt = Path.GetFileNameWithoutExtension(baseFileName); + var baseEnum = FromResX(basePathName); + var baseGrouping = baseEnum.GroupBy(item => item.Key, item => item.Value); + + bool anyErrors = false; + + WarnFn baseWarnFn = new WarnFn( + (reason, format, formatArgs) => + { + anyErrors = true; + Warn(Console.Out, baseFileName, reason, format, formatArgs); + }); + + List waitActions = new List(); + Action addWaitAction = a => { waitActions.Add(a); }; + + ManualResetEvent e0 = new ManualResetEvent(false); + + ThreadPool.QueueUserWorkItem(ignored => + { + Console.WriteLine("Analyzing base {0} ...", baseFileName); + + try + { + AnalyzeSingle(baseWarnFn, baseGrouping); + } + + catch (Exception ex) + { + Console.WriteLine("{0} : {1}", baseFileName, ex); + } + + finally + { + e0.Set(); + } + }); + + addWaitAction(() => e0.WaitOne()); + + var muiNames = args.Skip(1) + .SelectMany(spec => Eval(() => Directory.GetFiles(dir, spec), new string[0])); + + foreach (string muiName in muiNames) + { + string muiPathName = Path.GetFullPath(muiName); + string muiDir = Path.GetDirectoryName(muiPathName); + string muiFileName = Path.GetFileName(muiPathName); + string muiFileNameNoExt = Path.GetFileNameWithoutExtension(muiFileName); + + ManualResetEvent eN = new ManualResetEvent(false); + + ThreadPool.QueueUserWorkItem(ignored => + { + try + { + WarnFn muiNWarnFn = new WarnFn( + (reason, format, formatArgs) => + { + anyErrors = true; + Warn(Console.Out, muiFileName, reason, format, formatArgs); + }); + + Console.WriteLine("Analyzing mui {0} ...", muiFileName); + + var muiEnum = FromResX(muiPathName); + var muiGrouping = muiEnum.GroupBy(item => item.Key, item => item.Value); + + AnalyzeMuiWithBase(baseWarnFn, baseGrouping, muiNWarnFn, muiGrouping); + } + + catch (Exception ex) + { + Console.WriteLine("{0} : {1}", muiFileName, ex); + } + + finally + { + eN.Set(); + } + }); + + addWaitAction(() => eN.WaitOne()); + } + + foreach (Action waitAction in waitActions) + { + waitAction(); + } + + DateTime endTime = DateTime.Now; + + Console.WriteLine( + "--- End @ {0} ({1} ms), processed {2} resx files", + endTime.ToLongTimeString(), + (endTime - startTime).TotalMilliseconds, + 1 + muiNames.Count()); + + Console.WriteLine("There were{0} errors", anyErrors ? "" : " no"); + + return anyErrors ? 1 : 0; + // pause + //Console.Read(); + } + } +} diff --git a/extras/ResxCheck/ResxCheck/ResxCheck.csproj b/extras/ResxCheck/ResxCheck/ResxCheck.csproj new file mode 100644 index 0000000..50c314b --- /dev/null +++ b/extras/ResxCheck/ResxCheck/ResxCheck.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {D4EEE8A0-F37A-43A4-A80A-6F90D4E9206B} + Exe + Properties + ResxCheck + ResxCheck + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + 3.5 + + + 3.5 + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AboutDialog.cs b/src/AboutDialog.cs new file mode 100644 index 0000000..5713dc2 --- /dev/null +++ b/src/AboutDialog.cs @@ -0,0 +1,173 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class AboutDialog + : PdnBaseForm + { + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label creditsLabel; + private System.Windows.Forms.RichTextBox richCreditsBox; + private System.Windows.Forms.TextBox copyrightLabel; + private Label versionLabel; + private PdnBanner pdnBanner; + + public AboutDialog() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.richCreditsBox.BackColor = SystemColors.Window; + + string textFormat = PdnResources.GetString("AboutDialog.Text.Format"); + this.Text = string.Format(textFormat, PdnInfo.GetBareProductName()); + + this.pdnBanner.BannerText = string.Empty;// PdnInfo.GetFriendlyVersionString(); + this.richCreditsBox.LoadFile(PdnResources.GetResourceStream("Files.AboutCredits.rtf"), RichTextBoxStreamType.RichText); + this.copyrightLabel.Text = PdnInfo.GetCopyrightString(); + + this.Icon = PdnInfo.AppIcon; + + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.okButton.Location = new Point((this.ClientSize.Width - this.okButton.Width) / 2, this.okButton.Top); + + this.creditsLabel.Text = PdnResources.GetString("AboutDialog.CreditsLabel.Text"); + + Font bannerFont = this.pdnBanner.BannerFont; + Font newBannerFont = Utility.CreateFont(bannerFont.Name, 8.0f, bannerFont.Style); + this.pdnBanner.BannerFont = newBannerFont; + newBannerFont.Dispose(); + bannerFont.Dispose(); + + this.versionLabel.Text = PdnInfo.GetFullAppName(); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.okButton = new System.Windows.Forms.Button(); + this.creditsLabel = new System.Windows.Forms.Label(); + this.richCreditsBox = new System.Windows.Forms.RichTextBox(); + this.copyrightLabel = new System.Windows.Forms.TextBox(); + this.pdnBanner = new PaintDotNet.PdnBanner(); + this.versionLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.okButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(139, 346); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + // + // creditsLabel + // + this.creditsLabel.Location = new System.Drawing.Point(7, 132); + this.creditsLabel.Name = "creditsLabel"; + this.creditsLabel.Size = new System.Drawing.Size(200, 16); + this.creditsLabel.TabIndex = 5; + // + // richCreditsBox + // + this.richCreditsBox.CausesValidation = false; + this.richCreditsBox.Location = new System.Drawing.Point(10, 153); + this.richCreditsBox.Name = "richCreditsBox"; + this.richCreditsBox.ReadOnly = true; + this.richCreditsBox.Size = new System.Drawing.Size(476, 187); + this.richCreditsBox.TabIndex = 6; + this.richCreditsBox.Text = ""; + this.richCreditsBox.LinkClicked += new System.Windows.Forms.LinkClickedEventHandler(this.RichCreditsBox_LinkClicked); + // + // copyrightLabel + // + this.copyrightLabel.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.copyrightLabel.Location = new System.Drawing.Point(10, 95); + this.copyrightLabel.Multiline = true; + this.copyrightLabel.Name = "copyrightLabel"; + this.copyrightLabel.ReadOnly = true; + this.copyrightLabel.Size = new System.Drawing.Size(481, 36); + this.copyrightLabel.TabIndex = 4; + // + // pdnBanner + // + this.pdnBanner.BannerFont = new System.Drawing.Font("Tahoma", 10F); + this.pdnBanner.BannerText = "headingText"; + this.pdnBanner.Location = new System.Drawing.Point(0, 0); + this.pdnBanner.Name = "pdnBanner"; + this.pdnBanner.Size = new System.Drawing.Size(495, 71); + this.pdnBanner.TabIndex = 7; + // + // versionLabel + // + this.versionLabel.AutoSize = true; + this.versionLabel.Location = new System.Drawing.Point(7, 77); + this.versionLabel.Name = "versionLabel"; + this.versionLabel.Size = new System.Drawing.Size(0, 13); + this.versionLabel.TabIndex = 8; + // + // AboutDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.okButton; + this.ClientSize = new System.Drawing.Size(495, 375); + this.Controls.Add(this.versionLabel); + this.Controls.Add(this.copyrightLabel); + this.Controls.Add(this.richCreditsBox); + this.Controls.Add(this.creditsLabel); + this.Controls.Add(this.pdnBanner); + this.Controls.Add(this.okButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Location = new System.Drawing.Point(0, 0); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "AboutDialog"; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.pdnBanner, 0); + this.Controls.SetChildIndex(this.creditsLabel, 0); + this.Controls.SetChildIndex(this.richCreditsBox, 0); + this.Controls.SetChildIndex(this.copyrightLabel, 0); + this.Controls.SetChildIndex(this.versionLabel, 0); + this.ResumeLayout(false); + this.PerformLayout(); + + } + #endregion + + private void RichCreditsBox_LinkClicked(object sender, System.Windows.Forms.LinkClickedEventArgs e) + { + if (null != e.LinkText && e.LinkText.StartsWith("http://")) + { + PdnInfo.OpenUrl(this, e.LinkText); + } + } + } +} diff --git a/src/AboutDialog.resx b/src/AboutDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ActionFlags.cs b/src/ActionFlags.cs new file mode 100644 index 0000000..e5071e4 --- /dev/null +++ b/src/ActionFlags.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + [Flags] + internal enum ActionFlags + { + None = 0, + KeepToolActive = 1, + Cancellable = 2, + ReportsProgress = 4, + } +} diff --git a/src/Actions/AcquireFromScannerOrCameraAction.cs b/src/Actions/AcquireFromScannerOrCameraAction.cs new file mode 100644 index 0000000..52ff212 --- /dev/null +++ b/src/Actions/AcquireFromScannerOrCameraAction.cs @@ -0,0 +1,138 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.IO; + +namespace PaintDotNet.Actions +{ + internal sealed class AcquireFromScannerOrCameraAction + : AppWorkspaceAction + { + public override void PerformAction(AppWorkspace appWorkspace) + { + if (!ScanningAndPrinting.CanScan) + { + Utility.ShowWiaError(appWorkspace); + return; + } + + string tempName = Path.ChangeExtension(SystemLayer.FileSystem.GetTempFileName(), ".bmp"); + ScanResult result; + + try + { + result = ScanningAndPrinting.Scan(appWorkspace, tempName); + } + + // If there was an exception, let's assume the user has already received an error dialog, + // either from Windows or from the WIA UI, and let's /not/ present another error dialog. + catch (Exception) + { + result = ScanResult.UserCancelled; + } + + if (result == ScanResult.Success) + { + string errorText = null; + + try + { + Image image; + + try + { + image = PdnResources.LoadImage(tempName); + } + + catch (FileNotFoundException) + { + errorText = PdnResources.GetString("LoadImage.Error.FileNotFoundException"); + throw; + } + + catch (OutOfMemoryException) + { + errorText = PdnResources.GetString("LoadImage.Error.OutOfMemoryException"); + throw; + } + + Document document; + + try + { + document = Document.FromImage(image); + } + + catch (OutOfMemoryException) + { + errorText = PdnResources.GetString("LoadImage.Error.OutOfMemoryException"); + throw; + } + + finally + { + image.Dispose(); + image = null; + } + + DocumentWorkspace dw = appWorkspace.AddNewDocumentWorkspace(); + + try + { + dw.Document = document; + } + + catch (OutOfMemoryException) + { + errorText = PdnResources.GetString("LoadImage.Error.OutOfMemoryException"); + throw; + } + + document = null; + dw.SetDocumentSaveOptions(null, null, null); + dw.History.ClearAll(); + + HistoryMemento newHA = new NullHistoryMemento( + PdnResources.GetString("AcquireImageAction.Name"), + PdnResources.GetImageResource("Icons.MenuLayersAddNewLayerIcon.png")); + + dw.History.PushNewMemento(newHA); + + appWorkspace.ActiveDocumentWorkspace = dw; + + // Try to delete the temp file but don't worry if we can't + try + { + File.Delete(tempName); + } + + catch + { + } + } + + catch (Exception) + { + if (errorText != null) + { + Utility.ErrorBox(appWorkspace, errorText); + } + else + { + throw; + } + } + } + } + } +} diff --git a/src/Actions/CanvasSizeAction.cs b/src/Actions/CanvasSizeAction.cs new file mode 100644 index 0000000..947d72b --- /dev/null +++ b/src/Actions/CanvasSizeAction.cs @@ -0,0 +1,286 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + /// + /// There are two ways to use this action: + /// 1. Through the normal "PerformAction" interface provided through DoucmentAction + /// 2. Through the ResizeCanvas static method + /// + // TODO: split in to Action and Function + internal sealed class CanvasSizeAction + : DocumentWorkspaceAction + { + public static string StaticName + { + get + { + return PdnResources.GetString("CanvasSizeAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuImageCanvasSizeIcon.png"); + } + } + + public static BitmapLayer ResizeLayer(BitmapLayer layer, Size newSize, AnchorEdge anchor, ColorBgra background) + { + BitmapLayer newLayer = new BitmapLayer(newSize.Width, newSize.Height); + + // Background + new UnaryPixelOps.Constant(background).Apply(newLayer.Surface, newLayer.Surface.Bounds); + + // non-background = clear the alpha channel (see-through) + if (!layer.IsBackground) + { + new UnaryPixelOps.SetAlphaChannel(0).Apply(newLayer.Surface, newLayer.Surface.Bounds); + } + + int topY = 0; + int leftX = 0; + int rightX = newSize.Width - layer.Width; + int bottomY = newSize.Height - layer.Height; + int middleX = (newSize.Width - layer.Width) / 2; + int middleY = (newSize.Height - layer.Height) / 2; + + int x = 0; + int y = 0; + + #region choose x,y from AnchorEdge + switch (anchor) + { + case AnchorEdge.TopLeft: + x = leftX; + y = topY; + break; + + case AnchorEdge.Top: + x = middleX; + y = topY; + break; + + case AnchorEdge.TopRight: + x = rightX; + y = topY; + break; + + case AnchorEdge.Left: + x = leftX; + y = middleY; + break; + + case AnchorEdge.Middle: + x = middleX; + y = middleY; + break; + + case AnchorEdge.Right: + x = rightX; + y = middleY; + break; + + case AnchorEdge.BottomLeft: + x = leftX; + y = bottomY; + break; + + case AnchorEdge.Bottom: + x = middleX; + y = bottomY; + break; + + case AnchorEdge.BottomRight: + x = rightX; + y = bottomY; + break; + } + #endregion + + newLayer.Surface.CopySurface(layer.Surface, new Point(x, y)); + newLayer.LoadProperties(layer.SaveProperties()); + return newLayer; + } + + public static Document ResizeDocument(Document document, Size newSize, AnchorEdge edge, ColorBgra background) + { + Document newDoc = new Document(newSize.Width, newSize.Height); + newDoc.ReplaceMetaDataFrom(document); + + for (int i = 0; i < document.Layers.Count; ++i) + { + Layer layer = (Layer)document.Layers[i]; + + if (layer is BitmapLayer) + { + Layer newLayer; + + try + { + newLayer = ResizeLayer((BitmapLayer)layer, newSize, edge, background); + } + + catch (OutOfMemoryException) + { + newDoc.Dispose(); + throw; + } + + newDoc.Layers.Add(newLayer); + } + else + { + throw new InvalidOperationException("Canvas Size does not support Layers that are not BitmapLayers"); + } + } + + return newDoc; + } + + // returns null to indicate user cancelled, or if initialNewSize = newSize that the user requested, + // or if there was an error (out of memory) + public static Document ResizeDocument(IWin32Window parent, + Document document, + Size initialNewSize, + AnchorEdge initialAnchor, + ColorBgra background, + bool loadAndSaveMaintainAspect, + bool saveAnchor) + { + using (CanvasSizeDialog csd = new CanvasSizeDialog()) + { + bool maintainAspect; + + if (loadAndSaveMaintainAspect) + { + maintainAspect = Settings.CurrentUser.GetBoolean(SettingNames.LastMaintainAspectRatioCS, false); + } + else + { + maintainAspect = false; + } + + csd.OriginalSize = document.Size; + csd.OriginalDpuUnit = document.DpuUnit; + csd.OriginalDpu = document.DpuX; + csd.ImageWidth = initialNewSize.Width; + csd.ImageHeight = initialNewSize.Height; + csd.LayerCount = document.Layers.Count; + csd.AnchorEdge = initialAnchor; + csd.Units = csd.OriginalDpuUnit; + csd.Resolution = document.DpuX; + csd.Units = SettingNames.GetLastNonPixelUnits(); + csd.ConstrainToAspect = maintainAspect; + + DialogResult result = csd.ShowDialog(parent); + Size newSize = new Size(csd.ImageWidth, csd.ImageHeight); + MeasurementUnit newDpuUnit = csd.Units; + double newDpu = csd.Resolution; + + // If they cancelled, get out + if (result == DialogResult.Cancel) + { + return null; + } + + // If they clicked OK, then we save the aspect checkbox, and maybe the anchor + if (loadAndSaveMaintainAspect) + { + Settings.CurrentUser.SetBoolean(SettingNames.LastMaintainAspectRatioCS, csd.ConstrainToAspect); + } + + if (saveAnchor) + { + Settings.CurrentUser.SetString(SettingNames.LastCanvasSizeAnchorEdge, csd.AnchorEdge.ToString()); + } + + if (newSize == document.Size && newDpuUnit == document.DpuUnit && newDpu == document.DpuX) + { + return null; + } + + try + { + Utility.GCFullCollect(); + Document newDoc = ResizeDocument(document, newSize, csd.AnchorEdge, background); + newDoc.DpuUnit = newDpuUnit; + newDoc.DpuX = newDpu; + newDoc.DpuY = newDpu; + return newDoc; + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(parent, PdnResources.GetString("CanvasSizeAction.ResizeDocument.OutOfMemory")); + return null; + } + + catch + { + return null; + } + } + } + + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + AnchorEdge initialEdge = SettingNames.GetLastCanvasSizeAnchorEdge(); + + Document newDoc = ResizeDocument( + documentWorkspace.FindForm(), + documentWorkspace.Document, + documentWorkspace.Document.Size, + initialEdge, + documentWorkspace.AppWorkspace.AppEnvironment.SecondaryColor, + true, + true); + + if (newDoc != null) + { + using (new PushNullToolMode(documentWorkspace)) + { + if (newDoc.DpuUnit != MeasurementUnit.Pixel) + { + Settings.CurrentUser.SetString(SettingNames.LastNonPixelUnits, newDoc.DpuUnit.ToString()); + + if (documentWorkspace.AppWorkspace.Units != MeasurementUnit.Pixel) + { + documentWorkspace.AppWorkspace.Units = newDoc.DpuUnit; + } + } + + ReplaceDocumentHistoryMemento rdha = new ReplaceDocumentHistoryMemento(StaticName, StaticImage, documentWorkspace); + documentWorkspace.Document = newDoc; + return rdha; + } + } + else + { + return null; + } + } + + public CanvasSizeAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because opening this dialog does not necessitate + // refreshing the tool. This is handled by PerformAction() as appropriate. + } + } +} diff --git a/src/Actions/ClearHistoryAction.cs b/src/Actions/ClearHistoryAction.cs new file mode 100644 index 0000000..21cd6a3 --- /dev/null +++ b/src/Actions/ClearHistoryAction.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class ClearHistoryAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + if (DialogResult.Yes == Utility.AskYesNo(documentWorkspace, + PdnResources.GetString("ClearHistory.Confirmation"))) + { + documentWorkspace.History.ClearAll(); + + documentWorkspace.History.PushNewMemento(new NullHistoryMemento( + PdnResources.GetString("ClearHistory.HistoryMementoName"), + PdnResources.GetImageResource("Icons.MenuLayersDeleteLayerIcon.png"))); + } + + return null; + } + + public ClearHistoryAction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/Actions/ClearMruListAction.cs b/src/Actions/ClearMruListAction.cs new file mode 100644 index 0000000..c0091be --- /dev/null +++ b/src/Actions/ClearMruListAction.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class ClearMruListAction + : AppWorkspaceAction + { + public override void PerformAction(AppWorkspace appWorkspace) + { + string question = PdnResources.GetString("ClearOpenRecentList.Dialog.Text"); + DialogResult result = Utility.AskYesNo(appWorkspace, question); + + if (result == DialogResult.Yes) + { + appWorkspace.MostRecentFiles.Clear(); + appWorkspace.MostRecentFiles.SaveMruList(); + } + } + + public ClearMruListAction() + { + } + } +} diff --git a/src/Actions/CloseAllWorkspacesAction.cs b/src/Actions/CloseAllWorkspacesAction.cs new file mode 100644 index 0000000..29c018a --- /dev/null +++ b/src/Actions/CloseAllWorkspacesAction.cs @@ -0,0 +1,164 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class CloseAllWorkspacesAction + : AppWorkspaceAction + { + private bool cancelled; + + public bool Cancelled + { + get + { + return this.cancelled; + } + } + + public override void PerformAction(AppWorkspace appWorkspace) + { + DocumentWorkspace originalDW = appWorkspace.ActiveDocumentWorkspace; + + int oldLatency = 10; + + try + { + oldLatency = appWorkspace.Widgets.DocumentStrip.ThumbnailUpdateLatency; + appWorkspace.Widgets.DocumentStrip.ThumbnailUpdateLatency = 0; + } + + catch (NullReferenceException) + { + // See bug #2544 + } + + List unsavedDocs = new List(); + foreach (DocumentWorkspace dw in appWorkspace.DocumentWorkspaces) + { + if (dw.Document != null && dw.Document.Dirty) + { + unsavedDocs.Add(dw); + } + } + + if (unsavedDocs.Count == 1) + { + CloseWorkspaceAction cwa = new CloseWorkspaceAction(unsavedDocs[0]); + cwa.PerformAction(appWorkspace); + this.cancelled = cwa.Cancelled; + } + else if (unsavedDocs.Count > 1) + { + using (UnsavedChangesDialog dialog = new UnsavedChangesDialog()) + { + dialog.DocumentClicked += (s, e2) => { appWorkspace.ActiveDocumentWorkspace = e2.Data; }; + + dialog.Documents = unsavedDocs.ToArray(); + + if (appWorkspace.ActiveDocumentWorkspace.Document.Dirty) + { + dialog.SelectedDocument = appWorkspace.ActiveDocumentWorkspace; + } + + Form mainForm = appWorkspace.FindForm(); + if (mainForm != null) + { + PdnBaseForm asPDF = mainForm as PdnBaseForm; + + if (asPDF != null) + { + asPDF.RestoreWindow(); + } + } + + DialogResult dr = Utility.ShowDialog(dialog, appWorkspace); + + switch (dr) + { + case DialogResult.Yes: + { + foreach (DocumentWorkspace dw in unsavedDocs) + { + appWorkspace.ActiveDocumentWorkspace = dw; + bool result = dw.DoSave(); + + if (result) + { + appWorkspace.RemoveDocumentWorkspace(dw); + } + else + { + this.cancelled = true; + break; + } + } + } + break; + + case DialogResult.No: + this.cancelled = false; + break; + + case DialogResult.Cancel: + this.cancelled = true; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + } + + try + { + appWorkspace.Widgets.DocumentStrip.ThumbnailUpdateLatency = oldLatency; + } + + catch (NullReferenceException) + { + // See bug #2544 + } + + if (this.cancelled) + { + if (appWorkspace.ActiveDocumentWorkspace != originalDW && + !originalDW.IsDisposed) + { + appWorkspace.ActiveDocumentWorkspace = originalDW; + } + } + else + { + UI.SuspendControlPainting(appWorkspace); + + foreach (DocumentWorkspace dw in appWorkspace.DocumentWorkspaces) + { + appWorkspace.RemoveDocumentWorkspace(dw); + } + + UI.ResumeControlPainting(appWorkspace); + appWorkspace.Invalidate(true); + } + } + + public CloseAllWorkspacesAction() + { + this.cancelled = false; + } + } +} diff --git a/src/Actions/CloseWorkspaceAction.cs b/src/Actions/CloseWorkspaceAction.cs new file mode 100644 index 0000000..866cd6e --- /dev/null +++ b/src/Actions/CloseWorkspaceAction.cs @@ -0,0 +1,179 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class CloseWorkspaceAction + : AppWorkspaceAction + { + private DocumentWorkspace closeMe; + private bool cancelled; + + public bool Cancelled + { + get + { + return this.cancelled; + } + } + + public override void PerformAction(AppWorkspace appWorkspace) + { + if (appWorkspace == null) + { + throw new ArgumentNullException("appWorkspace"); + } + + DocumentWorkspace dw; + + if (this.closeMe == null) + { + dw = appWorkspace.ActiveDocumentWorkspace; + } + else + { + dw = this.closeMe; + } + + if (dw != null) + { + if (dw.Document == null) + { + appWorkspace.RemoveDocumentWorkspace(dw); + } + else if (!dw.Document.Dirty) + { + appWorkspace.RemoveDocumentWorkspace(dw); + } + else + { + appWorkspace.ActiveDocumentWorkspace = dw; + + TaskButton saveTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuFileSaveIcon.png").Reference, + PdnResources.GetString("CloseWorkspaceAction.SaveButton.ActionText"), + PdnResources.GetString("CloseWorkspaceAction.SaveButton.ExplanationText")); + + TaskButton dontSaveTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuFileCloseIcon.png").Reference, + PdnResources.GetString("CloseWorkspaceAction.DontSaveButton.ActionText"), + PdnResources.GetString("CloseWorkspaceAction.DontSaveButton.ExplanationText")); + + TaskButton cancelTB = new TaskButton( + PdnResources.GetImageResource("Icons.CancelIcon.png").Reference, + PdnResources.GetString("CloseWorkspaceAction.CancelButton.ActionText"), + PdnResources.GetString("CloseWorkspaceAction.CancelButton.ExplanationText")); + + string title = PdnResources.GetString("CloseWorkspaceAction.Title"); + string introTextFormat = PdnResources.GetString("CloseWorkspaceAction.IntroText.Format"); + string introText = string.Format(introTextFormat, dw.GetFriendlyName()); + + Image thumb = appWorkspace.GetDocumentWorkspaceThumbnail(dw); + + if (thumb == null) + { + thumb = new Bitmap(32, 32); + } + + Bitmap taskImage = new Bitmap(thumb.Width + 2, thumb.Height + 2, PixelFormat.Format32bppArgb); + + using (Graphics g = Graphics.FromImage(taskImage)) + { + g.Clear(Color.Transparent); + + g.DrawImage( + thumb, + new Rectangle(1, 1, thumb.Width, thumb.Height), + new Rectangle(0, 0, thumb.Width, thumb.Height), + GraphicsUnit.Pixel); + + Utility.DrawDropShadow1px(g, new Rectangle(0, 0, taskImage.Width, taskImage.Height)); + } + + Form mainForm = appWorkspace.FindForm(); + if (mainForm != null) + { + PdnBaseForm asPDF = mainForm as PdnBaseForm; + + if (asPDF != null) + { + asPDF.RestoreWindow(); + } + } + + Icon warningIcon; + ImageResource warningIconImageRes = PdnResources.GetImageResource("Icons.WarningIcon.png"); + + if (warningIconImageRes != null) + { + Image warningIconImage = warningIconImageRes.Reference; + warningIcon = Utility.ImageToIcon(warningIconImage, false); + } + else + { + warningIcon = null; + } + + TaskButton clickedTB = TaskDialog.Show( + appWorkspace, + warningIcon, + title, + taskImage, + false, + introText, + new TaskButton[] { saveTB, dontSaveTB, cancelTB }, + saveTB, + cancelTB, + 340); + + if (clickedTB == saveTB) + { + if (dw.DoSave()) + { + this.cancelled = false; + appWorkspace.RemoveDocumentWorkspace(dw); + } + else + { + this.cancelled = true; + } + } + else if (clickedTB == dontSaveTB) + { + this.cancelled = false; + appWorkspace.RemoveDocumentWorkspace(dw); + } + else + { + this.cancelled = true; + } + } + } + + Utility.GCFullCollect(); + } + + public CloseWorkspaceAction() + : this(null) + { + } + + public CloseWorkspaceAction(DocumentWorkspace closeMe) + { + this.closeMe = closeMe; + this.cancelled = false; + } + } +} diff --git a/src/Actions/CopyToClipboardAction.cs b/src/Actions/CopyToClipboardAction.cs new file mode 100644 index 0000000..2fd2b95 --- /dev/null +++ b/src/Actions/CopyToClipboardAction.cs @@ -0,0 +1,137 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class CopyToClipboardAction + { + private DocumentWorkspace documentWorkspace; + + public bool PerformAction() + { + bool success = true; + + if (this.documentWorkspace.Selection.IsEmpty || + !(this.documentWorkspace.ActiveLayer is BitmapLayer)) + { + return false; + } + + try + { + using (new WaitCursorChanger(this.documentWorkspace)) + { + Utility.GCFullCollect(); + PdnRegion selectionRegion = this.documentWorkspace.Selection.CreateRegion(); + PdnGraphicsPath selectionOutline = this.documentWorkspace.Selection.CreatePath(); + BitmapLayer activeLayer = (BitmapLayer)this.documentWorkspace.ActiveLayer; + RenderArgs renderArgs = new RenderArgs(activeLayer.Surface); + MaskedSurface maskedSurface = new MaskedSurface(renderArgs.Surface, selectionOutline); + SurfaceForClipboard surfaceForClipboard = new SurfaceForClipboard(maskedSurface); + Rectangle selectionBounds = Utility.GetRegionBounds(selectionRegion); + + if (selectionBounds.Width > 0 && selectionBounds.Height > 0) + { + Surface copySurface = new Surface(selectionBounds.Width, selectionBounds.Height); + Bitmap copyBitmap = copySurface.CreateAliasedBitmap(); + Bitmap copyOpaqueBitmap = new Bitmap(copySurface.Width, copySurface.Height, PixelFormat.Format24bppRgb); + + using (Graphics copyBitmapGraphics = Graphics.FromImage(copyBitmap)) + { + copyBitmapGraphics.Clear(Color.White); + } + + maskedSurface.Draw(copySurface, -selectionBounds.X, -selectionBounds.Y); + + using (Graphics copyOpaqueBitmapGraphics = Graphics.FromImage(copyOpaqueBitmap)) + { + copyOpaqueBitmapGraphics.Clear(Color.White); + copyOpaqueBitmapGraphics.DrawImage(copyBitmap, 0, 0); + } + + DataObject dataObject = new DataObject(); + + dataObject.SetData(DataFormats.Bitmap, copyOpaqueBitmap); + dataObject.SetData(surfaceForClipboard); + + int retryCount = 2; + + while (retryCount >= 0) + { + try + { + using (new WaitCursorChanger(this.documentWorkspace)) + { + Clipboard.SetDataObject(dataObject, true); + } + + break; + } + + catch + { + if (retryCount == 0) + { + success = false; + Utility.ErrorBox(this.documentWorkspace, + PdnResources.GetString("CopyAction.Error.TransferToClipboard")); + } + else + { + Thread.Sleep(200); + } + } + + finally + { + --retryCount; + } + } + + copySurface.Dispose(); + copyBitmap.Dispose(); + copyOpaqueBitmap.Dispose(); + } + + selectionRegion.Dispose(); + selectionOutline.Dispose(); + renderArgs.Dispose(); + maskedSurface.Dispose(); + } + } + + catch (OutOfMemoryException) + { + success = false; + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("CopyAction.Error.OutOfMemory")); + } + + catch (Exception) + { + success = false; + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("CopyAction.Error.Generic")); + } + + Utility.GCFullCollect(); + return success; + } + + public CopyToClipboardAction(DocumentWorkspace documentWorkspace) + { + SystemLayer.Tracing.LogFeature("CopyToClipboardAction"); + this.documentWorkspace = documentWorkspace; + } + } +} diff --git a/src/Actions/CutAction.cs b/src/Actions/CutAction.cs new file mode 100644 index 0000000..6527c87 --- /dev/null +++ b/src/Actions/CutAction.cs @@ -0,0 +1,79 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class CutAction + { + public static string StaticName + { + get + { + return PdnResources.GetString("CutAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuEditCutIcon.png"); + } + } + + public void PerformAction(DocumentWorkspace documentWorkspace) + { + HistoryMemento finalHM; + + if (documentWorkspace.Selection.IsEmpty) + { + finalHM = null; + } + else + { + CopyToClipboardAction ctca = new CopyToClipboardAction(documentWorkspace); + bool result = ctca.PerformAction(); + + if (!result) + { + finalHM = null; + } + else + { + using (new PushNullToolMode(documentWorkspace)) + { + EraseSelectionFunction esa = new EraseSelectionFunction(); + HistoryMemento hm = esa.Execute(documentWorkspace); + + CompoundHistoryMemento chm = new CompoundHistoryMemento( + StaticName, + StaticImage, + new HistoryMemento[] { hm }); + + finalHM = chm; + } + } + } + + if (finalHM != null) + { + documentWorkspace.History.PushNewMemento(finalHM); + } + } + + public CutAction() + { + SystemLayer.Tracing.LogFeature("CutAction"); + } + } +} diff --git a/src/Actions/FlipLayerHorizontalAction.cs b/src/Actions/FlipLayerHorizontalAction.cs new file mode 100644 index 0000000..e17c869 --- /dev/null +++ b/src/Actions/FlipLayerHorizontalAction.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using System; + +namespace PaintDotNet.Actions +{ + internal class FlipLayerHorizontalFunction + : FlipLayerFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("FlipLayerHorizontalAction.Name"); + } + } + + public FlipLayerHorizontalFunction(int layerIndex) + : base(StaticName, + PdnResources.GetImageResource("Icons.MenuLayersFlipHorizontalIcon.png"), + FlipType.Horizontal, + layerIndex) + { + } + } +} diff --git a/src/Actions/FlipLayerVerticalAction.cs b/src/Actions/FlipLayerVerticalAction.cs new file mode 100644 index 0000000..5317ecf --- /dev/null +++ b/src/Actions/FlipLayerVerticalAction.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using System; + +namespace PaintDotNet.Actions +{ + internal class FlipLayerVerticalFunction + : FlipLayerFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("FlipLayerVerticalAction.Name"); + } + } + + public FlipLayerVerticalFunction(int layerIndex) + : base(StaticName, + PdnResources.GetImageResource("Icons.MenuLayersFlipVerticalIcon.png"), + FlipType.Vertical, + layerIndex) + { + } + } +} diff --git a/src/Actions/HistoryFastForwardAction.cs b/src/Actions/HistoryFastForwardAction.cs new file mode 100644 index 0000000..c28d5ec --- /dev/null +++ b/src/Actions/HistoryFastForwardAction.cs @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class HistoryFastForwardAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + DateTime lastUpdate = DateTime.Now; + + documentWorkspace.History.BeginStepGroup(); + + using (new WaitCursorChanger(documentWorkspace)) + { + documentWorkspace.SuspendToolCursorChanges(); + + while (documentWorkspace.History.RedoStack.Count > 0) + { + documentWorkspace.History.StepForward(); + + if ((DateTime.Now - lastUpdate).TotalMilliseconds >= 500) + { + documentWorkspace.History.EndStepGroup(); + documentWorkspace.Update(); + lastUpdate = DateTime.Now; + documentWorkspace.History.BeginStepGroup(); + } + } + + documentWorkspace.ResumeToolCursorChanges(); + } + + documentWorkspace.History.EndStepGroup(); + + Utility.GCFullCollect(); + documentWorkspace.Document.Invalidate(); + documentWorkspace.Update(); + + return null; + } + + public HistoryFastForwardAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because the process of undo/redo has its own + // set of protocols for determining whether to keep the tool active, or to refresh it + } + } +} diff --git a/src/Actions/HistoryRedoAction.cs b/src/Actions/HistoryRedoAction.cs new file mode 100644 index 0000000..1d72272 --- /dev/null +++ b/src/Actions/HistoryRedoAction.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class HistoryRedoAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + if (documentWorkspace.History.RedoStack.Count > 0) + { + if (!(documentWorkspace.History.RedoStack[documentWorkspace.History.RedoStack.Count - 1] is NullHistoryMemento)) + { + using (new WaitCursorChanger(documentWorkspace.FindForm())) + { + documentWorkspace.History.StepForward(); + documentWorkspace.Update(); + } + } + + Utility.GCFullCollect(); + } + + return null; + } + + public HistoryRedoAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because the process of undo/redo has its own + // set of protocols for determine whether to keep the tool active, or to refresh it + } + } +} diff --git a/src/Actions/HistoryRewindAction.cs b/src/Actions/HistoryRewindAction.cs new file mode 100644 index 0000000..82cfef5 --- /dev/null +++ b/src/Actions/HistoryRewindAction.cs @@ -0,0 +1,59 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class HistoryRewindAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + DateTime lastUpdate = DateTime.Now; + + documentWorkspace.History.BeginStepGroup(); + + using (new WaitCursorChanger(documentWorkspace)) + { + documentWorkspace.SuspendToolCursorChanges(); + + while (documentWorkspace.History.UndoStack.Count > 1) + { + documentWorkspace.History.StepBackward(); + + if ((DateTime.Now - lastUpdate).TotalMilliseconds >= 500) + { + documentWorkspace.History.EndStepGroup(); + documentWorkspace.Update(); + lastUpdate = DateTime.Now; + documentWorkspace.History.BeginStepGroup(); + } + } + + documentWorkspace.ResumeToolCursorChanges(); + } + + documentWorkspace.History.EndStepGroup(); + + Utility.GCFullCollect(); + documentWorkspace.Document.Invalidate(); + documentWorkspace.Update(); + + return null; + } + + public HistoryRewindAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because the process of undo/redo has its own + // set of protocols for determining whether to keep the tool active, or to refresh it + } + } +} diff --git a/src/Actions/HistoryUndoAction.cs b/src/Actions/HistoryUndoAction.cs new file mode 100644 index 0000000..5729919 --- /dev/null +++ b/src/Actions/HistoryUndoAction.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class HistoryUndoAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + if (documentWorkspace.History.UndoStack.Count > 0) + { + if (!(documentWorkspace.History.UndoStack[documentWorkspace.History.UndoStack.Count - 1] is NullHistoryMemento)) + { + using (new WaitCursorChanger(documentWorkspace.FindForm())) + { + documentWorkspace.History.StepBackward(); + documentWorkspace.Update(); + } + } + + Utility.GCFullCollect(); + } + + return null; + } + + public HistoryUndoAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because the process of undo/redo has its own + // set of protocols for determine whether to keep the tool active, or to refresh it + } + } +} diff --git a/src/Actions/ImportFromFileAction.cs b/src/Actions/ImportFromFileAction.cs new file mode 100644 index 0000000..f11167a --- /dev/null +++ b/src/Actions/ImportFromFileAction.cs @@ -0,0 +1,379 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + // TODO: split into Action and Function(s) + internal sealed class ImportFromFileAction + : DocumentWorkspaceAction + { + public static string StaticName + { + get + { + return PdnResources.GetString("ImportFromFileAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuLayersImportFromFileIcon.png"); + } + } + + private void Rollback(List historyMementos) + { + for (int i = historyMementos.Count - 1; i >= 0; i--) + { + HistoryMemento ha = historyMementos[i]; + ha.PerformUndo(); + } + } + + private HistoryMemento DoCanvasResize(DocumentWorkspace documentWorkspace, Size newLayerSize) + { + HistoryMemento retHA; + + int layerIndex = documentWorkspace.ActiveLayerIndex; + + Size newSize = new Size(Math.Max(newLayerSize.Width, documentWorkspace.Document.Width), + Math.Max(newLayerSize.Height, documentWorkspace.Document.Height)); + + Document newDoc; + + try + { + using (new WaitCursorChanger(documentWorkspace)) + { + Utility.GCFullCollect(); + + newDoc = CanvasSizeAction.ResizeDocument(documentWorkspace.Document, newSize, + AnchorEdge.TopLeft, documentWorkspace.AppWorkspace.AppEnvironment.SecondaryColor); + } + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(documentWorkspace, PdnResources.GetString("ImportFromFileAction.AskForCanvasResize.OutOfMemory")); + newDoc = null; + } + + if (newDoc == null) + { + retHA = null; + } + else + { + retHA = new ReplaceDocumentHistoryMemento(string.Empty, null, documentWorkspace); + + using (new WaitCursorChanger(documentWorkspace)) + { + documentWorkspace.Document = newDoc; + } + + documentWorkspace.ActiveLayer = (Layer)documentWorkspace.Document.Layers[layerIndex]; + } + + return retHA; + } + + private HistoryMemento ImportOneLayer(DocumentWorkspace documentWorkspace, BitmapLayer layer) + { + HistoryMemento retHA; + List historyMementos = new List(); + bool success = true; + + if (success) + { + if (!documentWorkspace.Selection.IsEmpty) + { + HistoryMemento ha = new DeselectFunction().Execute(documentWorkspace); + historyMementos.Add(ha); + } + } + + if (success) + { + if (layer.Width > documentWorkspace.Document.Width || + layer.Height > documentWorkspace.Document.Height) + { + HistoryMemento ha = DoCanvasResize(documentWorkspace, layer.Size); + + if (ha == null) + { + success = false; + } + else + { + historyMementos.Add(ha); + } + } + } + + if (success) + { + if (layer.Size != documentWorkspace.Document.Size) + { + BitmapLayer newLayer; + + try + { + using (new WaitCursorChanger(documentWorkspace)) + { + Utility.GCFullCollect(); + + newLayer = CanvasSizeAction.ResizeLayer((BitmapLayer)layer, documentWorkspace.Document.Size, + AnchorEdge.TopLeft, ColorBgra.White.NewAlpha(0)); + } + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(documentWorkspace, PdnResources.GetString("ImportFromFileAction.ImportOneLayer.OutOfMemory")); + success = false; + newLayer = null; + } + + if (newLayer != null) + { + layer.Dispose(); + layer = newLayer; + } + } + } + + if (success) + { + NewLayerHistoryMemento nlha = new NewLayerHistoryMemento(string.Empty, null, documentWorkspace, documentWorkspace.Document.Layers.Count); + documentWorkspace.Document.Layers.Add(layer); + historyMementos.Add(nlha); + } + + if (success) + { + HistoryMemento[] has = historyMementos.ToArray(); + retHA = new CompoundHistoryMemento(string.Empty, null, has); + } + else + { + Rollback(historyMementos); + retHA = null; + } + + return retHA; + } + + /// + /// Presents a user interface and performs the operations required for importing an entire document. + /// + /// + /// + /// + /// This function will take ownership of the Document given to it, and will Dispose() of it. + /// + private HistoryMemento ImportDocument(DocumentWorkspace documentWorkspace, Document document, out Rectangle lastLayerBounds) + { + List historyMementos = new List(); + bool[] selected; + + selected = new bool[document.Layers.Count]; + for (int i = 0; i < selected.Length; ++i) + { + selected[i] = true; + } + + lastLayerBounds = Rectangle.Empty; + + if (selected != null) + { + List layers = new List(); + + for (int i = 0; i < selected.Length; ++i) + { + if (selected[i]) + { + layers.Add((Layer)document.Layers[i]); + } + } + + foreach (Layer layer in layers) + { + document.Layers.Remove(layer); + } + + document.Dispose(); + document = null; + + foreach (Layer layer in layers) + { + lastLayerBounds = layer.Bounds; + HistoryMemento ha = ImportOneLayer(documentWorkspace, (BitmapLayer)layer); + + if (ha != null) + { + historyMementos.Add(ha); + } + else + { + Rollback(historyMementos); + historyMementos.Clear(); + break; + } + } + } + + if (document != null) + { + document.Dispose(); + document = null; + } + + if (historyMementos.Count > 0) + { + HistoryMemento[] has = historyMementos.ToArray(); + return new CompoundHistoryMemento(string.Empty, null, has); + } + else + { + lastLayerBounds = Rectangle.Empty; + return null; + } + } + + private HistoryMemento ImportOneFile(DocumentWorkspace documentWorkspace, string fileName, out Rectangle lastLayerBounds) + { + documentWorkspace.AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + + ProgressEventHandler progressCallback = (s, e) => + { + documentWorkspace.AppWorkspace.Widgets.StatusBarProgress.SetProgressStatusBar(e.Percent); + }; + + FileType fileType; + Document document = DocumentWorkspace.LoadDocument(documentWorkspace, fileName, out fileType, progressCallback); + + documentWorkspace.AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar(); + + if (document != null) + { + string name = Path.ChangeExtension(Path.GetFileName(fileName), null); + string newLayerNameFormat = PdnResources.GetString("ImportFromFileAction.ImportOneFile.NewLayer.Format"); + + foreach (Layer layer in document.Layers) + { + layer.Name = string.Format(newLayerNameFormat, name, layer.Name); + layer.IsBackground = false; + } + + HistoryMemento ha = ImportDocument(documentWorkspace, document, out lastLayerBounds); + return ha; + } + else + { + lastLayerBounds = Rectangle.Empty; + return null; + } + } + + public HistoryMemento ImportMultipleFiles(DocumentWorkspace documentWorkspace, string[] fileNames) + { + HistoryMemento retHA = null; + List historyMementos = new List(); + Rectangle lastLayerBounds = Rectangle.Empty; + + foreach (string fileName in fileNames) + { + HistoryMemento ha = ImportOneFile(documentWorkspace, fileName, out lastLayerBounds); + + if (ha != null) + { + historyMementos.Add(ha); + } + else + { + Rollback(historyMementos); + historyMementos.Clear(); + break; + } + } + + if (lastLayerBounds.Width > 0 && lastLayerBounds.Height > 0) + { + SelectionHistoryMemento sha = new SelectionHistoryMemento(null, null, documentWorkspace); + historyMementos.Add(sha); + documentWorkspace.Selection.PerformChanging(); + documentWorkspace.Selection.Reset(); + documentWorkspace.Selection.SetContinuation(lastLayerBounds, System.Drawing.Drawing2D.CombineMode.Replace); + documentWorkspace.Selection.CommitContinuation(); + documentWorkspace.Selection.PerformChanged(); + } + + if (historyMementos.Count > 0) + { + HistoryMemento[] haArray = historyMementos.ToArray(); + retHA = new CompoundHistoryMemento(StaticName, StaticImage, haArray); + } + + return retHA; + } + + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + string[] fileNames; + string startingDir = Path.GetDirectoryName(documentWorkspace.FilePath); + DialogResult result = DocumentWorkspace.ChooseFiles(documentWorkspace, out fileNames, true, startingDir); + HistoryMemento retHA = null; + + if (result == DialogResult.OK) + { + Type oldToolType = documentWorkspace.GetToolType(); + documentWorkspace.SetTool(null); + + retHA = ImportMultipleFiles(documentWorkspace, fileNames); + + Type newToolType; + if (retHA != null) + { + CompoundHistoryMemento cha = new CompoundHistoryMemento(StaticName, StaticImage, new HistoryMemento[] { retHA }); + retHA = cha; + newToolType = typeof(Tools.MoveTool); + } + else + { + newToolType = oldToolType; + } + + documentWorkspace.SetToolFromType(newToolType); + } + + return retHA; + } + + public ImportFromFileAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because opening this dialog does not necessitate + // refreshing the tool. This is handled by PerformAction() as appropriate. + // The tool should only be changed if the action is performed, but not if the dialog + // is cancelled out of. + } + } +} diff --git a/src/Actions/MoveActiveLayerDownAction.cs b/src/Actions/MoveActiveLayerDownAction.cs new file mode 100644 index 0000000..307efd6 --- /dev/null +++ b/src/Actions/MoveActiveLayerDownAction.cs @@ -0,0 +1,59 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class MoveActiveLayerDownAction + : DocumentWorkspaceAction + { + public static string StaticName + { + get + { + return PdnResources.GetString("MoveLayerDown.HistoryMementoName"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuLayersMoveLayerDownIcon.png"); + } + } + + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + HistoryMemento hm = null; + int index = documentWorkspace.ActiveLayerIndex; + + if (index != 0) + { + SwapLayerHistoryMemento slhm = new SwapLayerHistoryMemento( + StaticName, + StaticImage, + documentWorkspace, + index, + index - 1); + + hm = slhm.PerformUndo(); + } + + return hm; + } + + public MoveActiveLayerDownAction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/Actions/MoveActiveLayerUpAction.cs b/src/Actions/MoveActiveLayerUpAction.cs new file mode 100644 index 0000000..60abaf4 --- /dev/null +++ b/src/Actions/MoveActiveLayerUpAction.cs @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class MoveActiveLayerUpAction + : DocumentWorkspaceAction + { + public static string StaticName + { + get + { + return PdnResources.GetString("MoveLayerUp.HistoryMementoName"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuLayersMoveLayerUpIcon.png"); + } + } + + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + HistoryMemento hm = null; + int index = documentWorkspace.ActiveLayerIndex; + + if (index != documentWorkspace.Document.Layers.Count - 1) + { + SwapLayerFunction slf = new SwapLayerFunction(index, index + 1); + HistoryMemento slfhm = slf.Execute(documentWorkspace); + + hm = new CompoundHistoryMemento( + StaticName, + StaticImage, + new HistoryMemento[] { slfhm }); + + documentWorkspace.ActiveLayer = (Layer)documentWorkspace.Document.Layers[index + 1]; + } + + return hm; + } + + public MoveActiveLayerUpAction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/Actions/NewImageAction.cs b/src/Actions/NewImageAction.cs new file mode 100644 index 0000000..92544ae --- /dev/null +++ b/src/Actions/NewImageAction.cs @@ -0,0 +1,93 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class NewImageAction + : AppWorkspaceAction + { + public override void PerformAction(AppWorkspace appWorkspace) + { + using (NewFileDialog nfd = new NewFileDialog()) + { + Size newDocSize = appWorkspace.GetNewDocumentSize(); + + if (Utility.IsClipboardImageAvailable()) + { + try + { + Utility.GCFullCollect(); + IDataObject clipData = System.Windows.Forms.Clipboard.GetDataObject(); + + using (Image clipImage = (Image)clipData.GetData(DataFormats.Bitmap)) + { + int width2 = clipImage.Width; + int height2 = clipImage.Height; + newDocSize = new Size(width2, height2); + } + } + + catch (Exception ex) + { + if (ex is OutOfMemoryException || + ex is ExternalException || + ex is NullReferenceException) + { + // ignore + } + else + { + throw; + } + } + } + + nfd.OriginalSize = new Size(newDocSize.Width, newDocSize.Height); + nfd.OriginalDpuUnit = SettingNames.GetLastNonPixelUnits(); + nfd.OriginalDpu = Document.GetDefaultDpu(nfd.OriginalDpuUnit); + nfd.Units = nfd.OriginalDpuUnit; + nfd.Resolution = nfd.OriginalDpu; + nfd.ConstrainToAspect = Settings.CurrentUser.GetBoolean(SettingNames.LastMaintainAspectRatioNF, false); + + DialogResult dr = nfd.ShowDialog(appWorkspace); + + if (dr == DialogResult.OK) + { + bool success = appWorkspace.CreateBlankDocumentInNewWorkspace(new Size(nfd.ImageWidth, nfd.ImageHeight), nfd.Units, nfd.Resolution, false); + + if (success) + { + appWorkspace.ActiveDocumentWorkspace.ZoomBasis = ZoomBasis.FitToWindow; + Settings.CurrentUser.SetBoolean(SettingNames.LastMaintainAspectRatioNF, nfd.ConstrainToAspect); + + if (nfd.Units != MeasurementUnit.Pixel) + { + Settings.CurrentUser.SetString(SettingNames.LastNonPixelUnits, nfd.Units.ToString()); + } + + if (appWorkspace.Units != MeasurementUnit.Pixel) + { + appWorkspace.Units = nfd.Units; + } + } + } + } + } + + public NewImageAction() + { + } + } +} diff --git a/src/Actions/OpenActiveLayerPropertiesAction.cs b/src/Actions/OpenActiveLayerPropertiesAction.cs new file mode 100644 index 0000000..01ba55b --- /dev/null +++ b/src/Actions/OpenActiveLayerPropertiesAction.cs @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class OpenActiveLayerPropertiesAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + bool oldDirtyValue = documentWorkspace.Document.Dirty; + + using (Form lpd = documentWorkspace.ActiveLayer.CreateConfigDialog()) + { + DialogResult result = Utility.ShowDialog(lpd, documentWorkspace.FindForm()); + + if (result == DialogResult.Cancel) + { + documentWorkspace.Document.Dirty = oldDirtyValue; + } + } + + return null; + } + + public OpenActiveLayerPropertiesAction() + : base(ActionFlags.KeepToolActive) + { + // This action does not require that the current tool be deactivated. + } + } +} diff --git a/src/Actions/OpenFileAction.cs b/src/Actions/OpenFileAction.cs new file mode 100644 index 0000000..509139a --- /dev/null +++ b/src/Actions/OpenFileAction.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class OpenFileAction + : AppWorkspaceAction + { + public override void PerformAction(AppWorkspace appWorkspace) + { + string filePath; + + if (appWorkspace.ActiveDocumentWorkspace == null) + { + filePath = null; + } + else + { + // Default to the directory the active document came from + string fileName; + FileType fileType; + SaveConfigToken saveConfigToken; + appWorkspace.ActiveDocumentWorkspace.GetDocumentSaveOptions(out fileName, out fileType, out saveConfigToken); + filePath = Path.GetDirectoryName(fileName); + } + + string[] newFileNames; + DialogResult result = DocumentWorkspace.ChooseFiles(appWorkspace, out newFileNames, true, filePath); + + if (result == DialogResult.OK) + { + appWorkspace.OpenFilesInNewWorkspace(newFileNames); + } + } + + public OpenFileAction() + { + } + } +} diff --git a/src/Actions/PasteAction.cs b/src/Actions/PasteAction.cs new file mode 100644 index 0000000..ee2ab6f --- /dev/null +++ b/src/Actions/PasteAction.cs @@ -0,0 +1,431 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using PaintDotNet.Tools; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class PasteAction + { + private DocumentWorkspace documentWorkspace; + + private sealed class IntensityMaskOp + : BinaryPixelOp + { + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + byte intensity = rhs.GetIntensityByte(); + ColorBgra result = ColorBgra.FromBgra(lhs.B, lhs.G, lhs.R, (byte)Utility.FastScaleByteByByte(intensity, lhs.A)); + return result; + } + } + + /// + /// Pastes from the clipboard into the document. + /// + /// true if the paste operation completed, false if there was an error or if it was cancelled for some reason + public bool PerformAction() + { + SurfaceForClipboard surfaceForClipboard = null; + IDataObject clipData = null; + + try + { + Utility.GCFullCollect(); + clipData = Clipboard.GetDataObject(); + } + + catch (ExternalException) + { + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.TransferFromClipboard")); + return false; + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.OutOfMemory")); + return false; + } + + // First "ask" the current tool if it wants to handle it + bool handledByTool = false; + if (this.documentWorkspace.Tool != null) + { + this.documentWorkspace.Tool.PerformPaste(clipData, out handledByTool); + } + + if (handledByTool) + { + return true; + } + + if (clipData.GetDataPresent(typeof(SurfaceForClipboard))) + { + try + { + Utility.GCFullCollect(); + surfaceForClipboard = clipData.GetData(typeof(SurfaceForClipboard)) as SurfaceForClipboard; + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.OutOfMemory")); + return false; + } + } + + if (surfaceForClipboard != null && surfaceForClipboard.MaskedSurface.IsDisposed) + { + // Have been getting crash reports where sfc contains a disposed MaskedSurface ... + surfaceForClipboard = null; + } + + if (surfaceForClipboard == null && + (clipData.GetDataPresent(DataFormats.Bitmap, true) || clipData.GetDataPresent(DataFormats.EnhancedMetafile, true))) + { + Image image; + + try + { + Utility.GCFullCollect(); + image = clipData.GetData(DataFormats.Bitmap, true) as Image; + + if (image == null) + { + image = SystemLayer.Clipboard.GetEmfFromClipboard(this.documentWorkspace); + } + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.OutOfMemory")); + return false; + } + + // Sometimes we get weird errors if we're in, say, 16-bit mode but the image was copied + // to the clipboard in 32-bit mode + if (image == null) + { + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.NotRecognized")); + return false; + } + + MaskedSurface maskedSurface = null; + + try + { + Utility.GCFullCollect(); + Bitmap bitmap; + Surface surface = null; + + if (image is Bitmap) + { + bitmap = (Bitmap)image; + image = null; + } + else + { + bitmap = new Bitmap(image); + image.Dispose(); + image = null; + } + + surface = Surface.CopyFromBitmap(bitmap); + bitmap.Dispose(); + bitmap = null; + + maskedSurface = new MaskedSurface(surface, new PdnRegion(surface.Bounds)); + + surface.Dispose(); + surface = null; + } + + catch (Exception) + { + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.OutOfMemory")); + return false; + } + + surfaceForClipboard = new SurfaceForClipboard(maskedSurface); + } + + if (surfaceForClipboard == null || surfaceForClipboard.MaskedSurface == null) + { + // silently fail: like what if a program overwrote the clipboard in between the time + // we enabled the "Paste" menu item and the user actually clicked paste? + // it could happen! + Utility.ErrorBox(this.documentWorkspace, PdnResources.GetString("PasteAction.Error.NoImage")); + return false; + } + + // If the image is larger than the document, ask them if they'd like to make the image larger first + Rectangle bounds = surfaceForClipboard.Bounds; + + if (bounds.Width > this.documentWorkspace.Document.Width || + bounds.Height > this.documentWorkspace.Document.Height) + { + Surface thumb; + + try + { + using (new WaitCursorChanger(this.documentWorkspace)) + { + thumb = CreateThumbnail(surfaceForClipboard); + } + } + + catch (OutOfMemoryException) + { + thumb = null; + } + + DialogResult dr = ShowExpandCanvasTaskDialog(this.documentWorkspace, thumb); + + int layerIndex = this.documentWorkspace.ActiveLayerIndex; + + switch (dr) + { + case DialogResult.Yes: + Size newSize = new Size(Math.Max(bounds.Width, this.documentWorkspace.Document.Width), + Math.Max(bounds.Height, this.documentWorkspace.Document.Height)); + + Document newDoc = CanvasSizeAction.ResizeDocument( + this.documentWorkspace.Document, + newSize, + AnchorEdge.TopLeft, + this.documentWorkspace.AppWorkspace.AppEnvironment.SecondaryColor); + + if (newDoc == null) + { + return false; // user clicked cancel! + } + else + { + HistoryMemento rdha = new ReplaceDocumentHistoryMemento( + CanvasSizeAction.StaticName, + CanvasSizeAction.StaticImage, + this.documentWorkspace); + + this.documentWorkspace.Document = newDoc; + this.documentWorkspace.History.PushNewMemento(rdha); + this.documentWorkspace.ActiveLayer = (Layer)this.documentWorkspace.Document.Layers[layerIndex]; + } + + break; + + case DialogResult.No: + break; + + case DialogResult.Cancel: + return false; + + default: + throw new InvalidEnumArgumentException("Internal error: DialogResult was neither Yes, No, nor Cancel"); + } + } + + // Decide where to paste to: If the paste is within bounds of the document, do as normal + // Otherwise, center it. + Rectangle docBounds = this.documentWorkspace.Document.Bounds; + Rectangle intersect1 = Rectangle.Intersect(docBounds, bounds); + bool doMove = intersect1 != bounds; //intersect1.IsEmpty; + + Point pasteOffset; + + if (doMove) + { + pasteOffset = new Point(-bounds.X + (docBounds.Width / 2) - (bounds.Width / 2), + -bounds.Y + (docBounds.Height / 2) - (bounds.Height / 2)); + } + else + { + pasteOffset = new Point(0, 0); + } + + // Paste to the place it was originally copied from (for PDN-to-PDN transfers) + // and then if its not pasted within the viewable rectangle we pan to that location + RectangleF visibleDocRectF = this.documentWorkspace.VisibleDocumentRectangleF; + Rectangle visibleDocRect = Utility.RoundRectangle(visibleDocRectF); + Rectangle bounds2 = new Rectangle(new Point(bounds.X + pasteOffset.X, bounds.Y + pasteOffset.Y), bounds.Size); + Rectangle intersect2 = Rectangle.Intersect(bounds2, visibleDocRect); + bool doPan = intersect2.IsEmpty; + + this.documentWorkspace.SetTool(null); + this.documentWorkspace.SetToolFromType(typeof(MoveTool)); + + ((MoveTool)this.documentWorkspace.Tool).PasteMouseDown(surfaceForClipboard, pasteOffset); + + if (doPan) + { + Point centerPtView = new Point(visibleDocRect.Left + (visibleDocRect.Width / 2), + visibleDocRect.Top + (visibleDocRect.Height / 2)); + + Point centerPtPasted = new Point(bounds2.Left + (bounds2.Width / 2), + bounds2.Top + (bounds2.Height / 2)); + + Size delta = new Size(centerPtPasted.X - centerPtView.X, + centerPtPasted.Y - centerPtView.Y); + + PointF docScrollPos = this.documentWorkspace.DocumentScrollPositionF; + + PointF newDocScrollPos = new PointF(docScrollPos.X + delta.Width, + docScrollPos.Y + delta.Height); + + this.documentWorkspace.DocumentScrollPositionF = newDocScrollPos; + } + + return true; + } + + private static Surface CreateThumbnail(SurfaceForClipboard surfaceForClipboard) + { + const int thumbLength96dpi = 120; + int thumbSizeOurDpi = SystemLayer.UI.ScaleWidth(thumbLength96dpi); + + Surface surface = surfaceForClipboard.MaskedSurface.SurfaceReadOnly; + PdnGraphicsPath maskPath = surfaceForClipboard.MaskedSurface.CreatePath(); + Rectangle bounds = surfaceForClipboard.Bounds; + + Surface thumb = CreateThumbnail(surface, maskPath, bounds, thumbSizeOurDpi); + + maskPath.Dispose(); + + return thumb; + } + + public static Surface CreateThumbnail(Surface sourceSurface, PdnGraphicsPath maskPath, Rectangle bounds, int thumbSideLength) + { + Size thumbSize = Utility.ComputeThumbnailSize(bounds.Size, thumbSideLength); + + Surface thumb = new Surface(Math.Max(5, thumbSize.Width + 4), Math.Max(5, thumbSize.Height + 4)); + thumb.Clear(ColorBgra.Transparent); + thumb.Clear(new Rectangle(1, 1, thumb.Width - 2, thumb.Height - 2), ColorBgra.Black); + + Rectangle insetRect = new Rectangle(2, 2, thumb.Width - 4, thumb.Height - 4); + + Surface thumbInset = thumb.CreateWindow(insetRect); + thumbInset.Clear(ColorBgra.Transparent); + + float scaleX = (float)thumbInset.Width / (float)bounds.Width; + float scaleY = (float)thumbInset.Height / (float)bounds.Height; + + Matrix scaleMatrix = new Matrix(); + scaleMatrix.Translate(-bounds.X, -bounds.Y, System.Drawing.Drawing2D.MatrixOrder.Append); + scaleMatrix.Scale(scaleX, scaleY, System.Drawing.Drawing2D.MatrixOrder.Append); + + thumbInset.SuperSamplingFitSurface(sourceSurface); + + Surface maskInset = new Surface(thumbInset.Size); + maskInset.Clear(ColorBgra.Black); + using (RenderArgs maskInsetRA = new RenderArgs(maskInset)) + { + maskInsetRA.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + maskInsetRA.Graphics.Transform = scaleMatrix; + maskInsetRA.Graphics.FillPath(Brushes.White, maskPath); + maskInsetRA.Graphics.DrawPath(Pens.White, maskPath); + } + + scaleMatrix.Dispose(); + scaleMatrix = null; + + IntensityMaskOp maskOp = new IntensityMaskOp(); + maskOp.Apply(maskInset, thumbInset, maskInset); + + UserBlendOps.NormalBlendOp normalOp = new UserBlendOps.NormalBlendOp(); + thumbInset.ClearWithCheckboardPattern(); + normalOp.Apply(thumbInset, thumbInset, maskInset); + + maskInset.Dispose(); + maskInset = null; + + thumbInset.Dispose(); + thumbInset = null; + + using (RenderArgs thumbRA = new RenderArgs(thumb)) + { + Utility.DrawDropShadow1px(thumbRA.Graphics, thumb.Bounds); + } + + return thumb; + } + + private static DialogResult ShowExpandCanvasTaskDialog(IWin32Window owner, Surface thumbnail) + { + DialogResult result; + + Icon formIcon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuEditPasteIcon.png").Reference); + string formTitle = PdnResources.GetString("ExpandCanvasQuestion.Title"); + + RenderArgs taskImageRA = new RenderArgs(thumbnail); + Image taskImage = taskImageRA.Bitmap; + string introText = PdnResources.GetString("ExpandCanvasQuestion.IntroText"); + + TaskButton yesTB = new TaskButton( + PdnResources.GetImageResource("Icons.ExpandCanvasQuestion.YesTB.Image.png").Reference, + PdnResources.GetString("ExpandCanvasQuestion.YesTB.ActionText"), + PdnResources.GetString("ExpandCanvasQuestion.YesTB.ExplanationText")); + + TaskButton noTB = new TaskButton( + PdnResources.GetImageResource("Icons.ExpandCanvasQuestion.NoTB.Image.png").Reference, + PdnResources.GetString("ExpandCanvasQuestion.NoTB.ActionText"), + PdnResources.GetString("ExpandCanvasQuestion.NoTB.ExplanationText")); + + TaskButton cancelTB = new TaskButton( + TaskButton.Cancel.Image, + PdnResources.GetString("ExpandCanvasQuestion.CancelTB.ActionText"), + PdnResources.GetString("ExpandCanvasQuestion.CancelTB.ExplanationText")); + + int width96dpi = (TaskDialog.DefaultPixelWidth96Dpi * 3) / 2; + + TaskButton clickedTB = TaskDialog.Show( + owner, + formIcon, + formTitle, + taskImage, + false, + introText, + new TaskButton[] { yesTB, noTB, cancelTB }, + yesTB, + cancelTB, + width96dpi); + + if (clickedTB == yesTB) + { + result = DialogResult.Yes; + } + else if (clickedTB == noTB) + { + result = DialogResult.No; + } + else + { + result = DialogResult.Cancel; + } + + taskImageRA.Dispose(); + taskImageRA = null; + + return result; + } + + public PasteAction(DocumentWorkspace documentWorkspace) + { + SystemLayer.Tracing.LogFeature("PasteAction"); + this.documentWorkspace = documentWorkspace; + } + } +} diff --git a/src/Actions/PasteInToNewImageAction.cs b/src/Actions/PasteInToNewImageAction.cs new file mode 100644 index 0000000..d9feb3f --- /dev/null +++ b/src/Actions/PasteInToNewImageAction.cs @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + internal sealed class PasteInToNewImageAction + : AppWorkspaceAction + { + public override void PerformAction(AppWorkspace appWorkspace) + { + try + { + IDataObject pasted; + Image image; + + using (new WaitCursorChanger(appWorkspace)) + { + Utility.GCFullCollect(); + pasted = Clipboard.GetDataObject(); + image = (Image)pasted.GetData(DataFormats.Bitmap); + } + + if (image == null) + { + Utility.ErrorBox(appWorkspace, PdnResources.GetString("PasteInToNewImageAction.Error.NoClipboardImage")); + } + else + { + Size newSize = image.Size; + image.Dispose(); + image = null; + pasted = null; + + Document document = null; + + using (new WaitCursorChanger(appWorkspace)) + { + document = new Document(newSize); + DocumentWorkspace dw = appWorkspace.AddNewDocumentWorkspace(); + dw.Document = document; + + dw.History.PushNewMemento(new NullHistoryMemento(string.Empty, null)); + + PasteInToNewLayerAction pitnla = new PasteInToNewLayerAction(dw); + bool result = pitnla.PerformAction(); + + if (result) + { + dw.Selection.Reset(); + dw.SetDocumentSaveOptions(null, null, null); + dw.History.ClearAll(); + + dw.History.PushNewMemento( + new NullHistoryMemento( + PdnResources.GetString("NewImageAction.Name"), + PdnResources.GetImageResource("Icons.MenuLayersAddNewLayerIcon.png"))); + + appWorkspace.ActiveDocumentWorkspace = dw; + } + else + { + appWorkspace.RemoveDocumentWorkspace(dw); + document.Dispose(); + } + } + } + } + + catch (ExternalException) + { + Utility.ErrorBox(appWorkspace, PdnResources.GetString("AcquireImageAction.Error.Clipboard.TransferError")); + return; + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(appWorkspace, PdnResources.GetString("AcquireImageAction.Error.Clipboard.OutOfMemory")); + return; + } + + catch (ThreadStateException) + { + // The ApartmentState property of the application is not set to ApartmentState.STA + // I don't think this one will ever happen, seeing as how Main is tagged with the + // STA attribute. + return; + } + } + } +} diff --git a/src/Actions/PasteInToNewLayerAction.cs b/src/Actions/PasteInToNewLayerAction.cs new file mode 100644 index 0000000..77c6f58 --- /dev/null +++ b/src/Actions/PasteInToNewLayerAction.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using System; +using System.Collections.Generic; + +namespace PaintDotNet.Actions +{ + internal sealed class PasteInToNewLayerAction + { + private DocumentWorkspace documentWorkspace; + + public bool PerformAction() + { + HistoryFunctionResult hfr = this.documentWorkspace.ExecuteFunction(new AddNewBlankLayerFunction()); + + if (hfr == HistoryFunctionResult.Success) + { + PasteAction pa = new PasteAction(this.documentWorkspace); + bool result = pa.PerformAction(); + + if (!result) + { + using (new WaitCursorChanger(this.documentWorkspace)) + { + this.documentWorkspace.History.StepBackward(); + } + } + else + { + return true; + } + } + + return false; + } + + public PasteInToNewLayerAction(DocumentWorkspace documentWorkspace) + { + this.documentWorkspace = documentWorkspace; + } + } +} diff --git a/src/Actions/PrintAction.cs b/src/Actions/PrintAction.cs new file mode 100644 index 0000000..89b6ef0 --- /dev/null +++ b/src/Actions/PrintAction.cs @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Text; + +namespace PaintDotNet.Actions +{ + internal sealed class PrintAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + if (!ScanningAndPrinting.CanPrint) + { + Utility.ShowWiaError(documentWorkspace); + return null; + } + + using (new PushNullToolMode(documentWorkspace)) + { + // render image to a bitmap, save it to disk + Surface scratch = documentWorkspace.BorrowScratchSurface(this.GetType().Name + ".PerformAction()"); + + try + { + scratch.Clear(); + RenderArgs ra = new RenderArgs(scratch); + + documentWorkspace.Update(); + + using (new WaitCursorChanger(documentWorkspace)) + { + ra.Surface.Clear(ColorBgra.White); + documentWorkspace.Document.Render(ra, false); + } + + string tempName = Path.GetTempFileName() + ".bmp"; + ra.Bitmap.Save(tempName, ImageFormat.Bmp); + + try + { + ScanningAndPrinting.Print(documentWorkspace, tempName); + } + + catch (Exception ex) + { + Utility.ShowWiaError(documentWorkspace); + Tracing.Ping(ex.ToString()); + // TODO: do a "better" error dialog here + } + + // Try to delete the temp file but don't worry if we can't + bool result = FileSystem.TryDeleteFile(tempName); + } + + finally + { + documentWorkspace.ReturnScratchSurface(scratch); + } + } + + return null; + } + + public PrintAction() + : base(ActionFlags.KeepToolActive) + { + } + } +} diff --git a/src/Actions/ResizeAction.cs b/src/Actions/ResizeAction.cs new file mode 100644 index 0000000..d1cf017 --- /dev/null +++ b/src/Actions/ResizeAction.cs @@ -0,0 +1,430 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using PaintDotNet.Threading; +using System; +using System.Collections; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Actions +{ + // TODO: split in to Action and Function + internal sealed class ResizeAction + : DocumentWorkspaceAction + { + public static string StaticName + { + get + { + return PdnResources.GetString("ResizeAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuImageResizeIcon.png"); + } + } + + private sealed class FitSurfaceContext + { + private Surface dstSurface; + private Surface srcSurface; + private Rectangle[] dstRois; + private ResamplingAlgorithm algorithm; + + public Surface DstSurface + { + get + { + return dstSurface; + } + } + + public Surface SrcSurface + { + get + { + return srcSurface; + } + } + + public Rectangle[] DstRois + { + get + { + return dstRois; + } + } + + public ResamplingAlgorithm Algorithm + { + get + { + return algorithm; + } + } + + public event Procedure RenderedRect; + private void OnRenderedRect() + { + if (RenderedRect != null) + { + RenderedRect(); + } + } + + public void FitSurface(object context) + { + int index = (int)context; + dstSurface.FitSurface(algorithm, srcSurface, dstRois[index]); + OnRenderedRect(); + } + + public FitSurfaceContext(Surface dstSurface, Surface srcSurface, Rectangle[] dstRois, ResamplingAlgorithm algorithm) + { + this.dstSurface = dstSurface; + this.srcSurface = srcSurface; + this.dstRois = dstRois; + this.algorithm = algorithm; + } + } + + private static BitmapLayer ResizeLayer(BitmapLayer layer, int width, int height, ResamplingAlgorithm algorithm, + int tileCount, Procedure progressCallback, ref bool pleaseStopMonitor) + { + Surface surface = new Surface(width, height); + surface.Clear(ColorBgra.FromBgra(255, 255, 255, 0)); + + PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(); + int rectCount; + + if (tileCount == 0) + { + rectCount = Processor.LogicalCpuCount; + } + else + { + rectCount = tileCount; + } + + Rectangle[] rects = new Rectangle[rectCount]; + Utility.SplitRectangle(surface.Bounds, rects); + + FitSurfaceContext fsc = new FitSurfaceContext(surface, layer.Surface, rects, algorithm); + + if (progressCallback != null) + { + fsc.RenderedRect += progressCallback; + } + + WaitCallback callback = new WaitCallback(fsc.FitSurface); + + for (int i = 0; i < rects.Length; ++i) + { + if (pleaseStopMonitor) + { + break; + } + else + { + threadPool.QueueUserWorkItem(callback, BoxedConstants.GetInt32(i)); + } + } + + threadPool.Drain(); + threadPool.DrainExceptions(); + + if (pleaseStopMonitor) + { + surface.Dispose(); + surface = null; + } + + BitmapLayer newLayer; + + if (surface == null) + { + newLayer = null; + } + else + { + newLayer = new BitmapLayer(surface, true); + newLayer.LoadProperties(layer.SaveProperties()); + } + + if (progressCallback != null) + { + fsc.RenderedRect -= progressCallback; + } + + return newLayer; + } + + public static BitmapLayer ResizeLayer(BitmapLayer layer, int width, int height, ResamplingAlgorithm algorithm) + { + bool pleaseStop = false; + return ResizeLayer(layer, width, height, algorithm, 0, null, ref pleaseStop); + } + + private class ResizeProgressDialog + : CallbackWithProgressDialog + { + private int maxTiles; + private int tilesCompleted = 0; + private int tilesPerLayer; + private Document dst; + private Document src; + private Size newSize; + private ResamplingAlgorithm algorithm; + private bool returnVal; + private bool pleaseStop = false; + + public ResizeProgressDialog(Control owner, Document dst, Document src, Size newSize, ResamplingAlgorithm algorithm) + : base (owner, PdnInfo.GetBareProductName(), PdnResources.GetString("ResizeAction.ProgressDialog.Description")) + { + this.dst = dst; + this.src = src; + this.newSize = newSize; + this.algorithm = algorithm; + this.tilesPerLayer = 50 * Processor.LogicalCpuCount; + this.maxTiles = tilesPerLayer * src.Layers.Count; + this.Icon = Utility.ImageToIcon(StaticImage.Reference); + } + + protected override void OnCancelClick() + { + this.pleaseStop = true; + base.OnCancelClick(); + } + + private void RenderedRectHandler() + { + this.Owner.BeginInvoke(new Procedure(MarshaledProgressUpdate)); + } + + private void MarshaledProgressUpdate() + { + ++tilesCompleted; + double progress = 100.0 * ((double)tilesCompleted / (double)maxTiles); + this.Progress = (int)Math.Round(progress); + } + + public bool DoResize() + { + DialogResult result = this.ShowDialog(true, false, new ThreadStart(ResizeDocument)); + + if (!this.returnVal && !this.Cancelled) + { + Utility.ErrorBox(this.Owner, PdnResources.GetString("ResizeAction.PerformAction.UnspecifiedError")); + } + + return this.returnVal; + } + + private void ResizeDocument() + { + this.pleaseStop = false; + + // This is only sort of a hack: we must try and allocate enough for 2 extra layer-sized buffers + // Then we free them immediately. This is just so that if we don't have enough memory that we'll + // fail sooner rather than later. + Surface s1 = new Surface(this.newSize); + Surface s2 = new Surface(this.newSize); + + try + { + foreach (Layer layer in src.Layers) + { + if (this.pleaseStop) + { + this.returnVal = false; + return; + } + + if (layer is BitmapLayer) + { + Layer newLayer = ResizeLayer((BitmapLayer)layer, this.newSize.Width, this.newSize.Height, this.algorithm, + this.tilesPerLayer, new Procedure(RenderedRectHandler), ref this.pleaseStop); + + if (newLayer == null) + { + this.returnVal = false; + return; + } + + dst.Layers.Add(newLayer); + } + else + { + throw new InvalidOperationException("Resize does not support Layers that are not BitmapLayers"); + } + } + } + + finally + { + s1.Dispose(); + s2.Dispose(); + } + + this.returnVal = true; + } + } + + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + int newWidth; + int newHeight; + double newDpu; + MeasurementUnit newDpuUnit; + + string resamplingAlgorithm = Settings.CurrentUser.GetString(SettingNames.LastResamplingMethod, + ResamplingAlgorithm.SuperSampling.ToString()); + + ResamplingAlgorithm alg; + + try + { + alg = (ResamplingAlgorithm)Enum.Parse(typeof(ResamplingAlgorithm), resamplingAlgorithm, true); + } + + catch + { + alg = ResamplingAlgorithm.SuperSampling; + } + + bool maintainAspect = Settings.CurrentUser.GetBoolean(SettingNames.LastMaintainAspectRatio, true); + + using (ResizeDialog rd = new ResizeDialog()) + { + rd.OriginalSize = documentWorkspace.Document.Size; + rd.OriginalDpuUnit = documentWorkspace.Document.DpuUnit; + rd.OriginalDpu = documentWorkspace.Document.DpuX; + rd.ImageHeight = documentWorkspace.Document.Height; + rd.ImageWidth = documentWorkspace.Document.Width; + rd.ResamplingAlgorithm = alg; + rd.LayerCount = documentWorkspace.Document.Layers.Count; + rd.Units = rd.OriginalDpuUnit; + rd.Resolution = documentWorkspace.Document.DpuX; + rd.Units = SettingNames.GetLastNonPixelUnits(); + rd.ConstrainToAspect = maintainAspect; + + DialogResult result = rd.ShowDialog(documentWorkspace); + + if (result == DialogResult.Cancel) + { + return null; + } + + Settings.CurrentUser.SetString(SettingNames.LastResamplingMethod, rd.ResamplingAlgorithm.ToString()); + Settings.CurrentUser.SetBoolean(SettingNames.LastMaintainAspectRatio, rd.ConstrainToAspect); + newDpuUnit = rd.Units; + newWidth = rd.ImageWidth; + newHeight = rd.ImageHeight; + newDpu = rd.Resolution; + alg = rd.ResamplingAlgorithm; + + if (newDpuUnit != MeasurementUnit.Pixel) + { + Settings.CurrentUser.SetString(SettingNames.LastNonPixelUnits, newDpuUnit.ToString()); + + if (documentWorkspace.AppWorkspace.Units != MeasurementUnit.Pixel) + { + documentWorkspace.AppWorkspace.Units = newDpuUnit; + } + } + + // if the new size equals the old size, there's really no point in doing anything + if (documentWorkspace.Document.Size == new Size(rd.ImageWidth, rd.ImageHeight) && + documentWorkspace.Document.DpuX == newDpu && + documentWorkspace.Document.DpuUnit == newDpuUnit) + { + return null; + } + } + + HistoryMemento ha; + + if (newWidth == documentWorkspace.Document.Width && + newHeight == documentWorkspace.Document.Height) + { + // Only adjusting Dpu or DpuUnit + ha = new MetaDataHistoryMemento(StaticName, StaticImage, documentWorkspace); + documentWorkspace.Document.DpuUnit = newDpuUnit; + documentWorkspace.Document.DpuX = newDpu; + documentWorkspace.Document.DpuY = newDpu; + } + else + { + try + { + using (new WaitCursorChanger(documentWorkspace)) + { + ha = new ReplaceDocumentHistoryMemento(StaticName, StaticImage, documentWorkspace); + } + + Document newDocument = new Document(newWidth, newHeight); + newDocument.ReplaceMetaDataFrom(documentWorkspace.Document); + newDocument.DpuUnit = newDpuUnit; + newDocument.DpuX = newDpu; + newDocument.DpuY = newDpu; + ResizeProgressDialog rpd = new ResizeProgressDialog(documentWorkspace, newDocument, documentWorkspace.Document, new Size(newWidth, newHeight), alg); + Utility.GCFullCollect(); + bool result = rpd.DoResize(); + + if (!result) + { + return null; + } + + documentWorkspace.Document = newDocument; + } + + catch (WorkerThreadException ex) + { + if (ex.InnerException is OutOfMemoryException) + { + Utility.ErrorBox(documentWorkspace, PdnResources.GetString("ResizeAction.PerformAction.OutOfMemory")); + return null; + } + else + { + throw; + } + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(documentWorkspace, PdnResources.GetString("ResizeAction.PerformAction.OutOfMemory")); + return null; + } + } + + return ha; + } + + public ResizeAction() + : base(ActionFlags.KeepToolActive) + { + // We use ActionFlags.KeepToolActive because opening this dialog does not necessitate + // refreshing the tool. This is handled by DocumentWorkspace.set_Document as appropriate. + } + } +} diff --git a/src/Actions/SendFeedbackAction.cs b/src/Actions/SendFeedbackAction.cs new file mode 100644 index 0000000..96cc631 --- /dev/null +++ b/src/Actions/SendFeedbackAction.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; + +namespace PaintDotNet.Actions +{ + internal sealed class SendFeedbackAction + : AppWorkspaceAction + { + private string GetEmailLaunchString(string email, string subject, string body) + { + const string emailFormat = "mailto:{0}?subject={1}&body={2}"; + string bodyUE = body.Replace("\r\n", "%0D%0A"); + string launchString = string.Format(emailFormat, email, subject, bodyUE); + return launchString; + } + + public override void PerformAction(AppWorkspace appWorkspace) + { + string email = InvariantStrings.FeedbackEmail; + string subjectFormat = PdnResources.GetString("SendFeedback.Email.Subject.Format"); + string subject = string.Format(subjectFormat, PdnInfo.GetFullAppName()); + string body = PdnResources.GetString("SendFeedback.Email.Body"); + string launchMe = GetEmailLaunchString(email, subject, body); + launchMe = launchMe.Substring(0, Math.Min(1024, launchMe.Length)); + + try + { + Process.Start(launchMe); + } + + catch (Exception) + { + Utility.ErrorBox(appWorkspace, PdnResources.GetString("LaunchLink.Error")); + } + } + + public SendFeedbackAction() + { + } + } +} diff --git a/src/Actions/ZoomInAction.cs b/src/Actions/ZoomInAction.cs new file mode 100644 index 0000000..8853b8a --- /dev/null +++ b/src/Actions/ZoomInAction.cs @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class ZoomInAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + documentWorkspace.ZoomIn(); + return null; + } + + public ZoomInAction() + : base(ActionFlags.KeepToolActive) + { + } + } +} diff --git a/src/Actions/ZoomOutAction.cs b/src/Actions/ZoomOutAction.cs new file mode 100644 index 0000000..4fba531 --- /dev/null +++ b/src/Actions/ZoomOutAction.cs @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class ZoomOutAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + documentWorkspace.ZoomOut(); + return null; + } + + public ZoomOutAction() + : base(ActionFlags.KeepToolActive) + { + } + } +} diff --git a/src/Actions/ZoomToSelectionAction.cs b/src/Actions/ZoomToSelectionAction.cs new file mode 100644 index 0000000..69cb6a7 --- /dev/null +++ b/src/Actions/ZoomToSelectionAction.cs @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class ZoomToSelectionAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + documentWorkspace.ZoomToSelection(); + return null; + } + + public ZoomToSelectionAction() + : base(ActionFlags.KeepToolActive) + { + } + } +} diff --git a/src/Actions/ZoomToWindowAction.cs b/src/Actions/ZoomToWindowAction.cs new file mode 100644 index 0000000..7328e95 --- /dev/null +++ b/src/Actions/ZoomToWindowAction.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Actions +{ + internal sealed class ZoomToWindowAction + : DocumentWorkspaceAction + { + public override HistoryMemento PerformAction(DocumentWorkspace documentWorkspace) + { + if (documentWorkspace.ZoomBasis == ZoomBasis.FitToWindow) + { + documentWorkspace.ZoomBasis = ZoomBasis.ScaleFactor; + } + else + { + documentWorkspace.ZoomBasis = ZoomBasis.FitToWindow; + } + + return null; + } + + public ZoomToWindowAction() + : base(ActionFlags.KeepToolActive) + { + } + } +} diff --git a/src/AnchorChooserControl.cs b/src/AnchorChooserControl.cs new file mode 100644 index 0000000..99b5ed5 --- /dev/null +++ b/src/AnchorChooserControl.cs @@ -0,0 +1,272 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + internal class AnchorChooserControl + : System.Windows.Forms.UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + private Image centerImage = null; + private AnchorEdge[][] xyToAnchorEdge; + private Hashtable anchorEdgeToXy; // maps AnchorEdge -> Point + private AnchorEdge anchorEdge = AnchorEdge.TopLeft; + + public event EventHandler AnchorEdgeChanged; + protected virtual void OnAnchorEdgeChanged() + { + if (AnchorEdgeChanged != null) + { + AnchorEdgeChanged(this, EventArgs.Empty); + } + } + + [DefaultValue(AnchorEdge.TopLeft)] + public AnchorEdge AnchorEdge + { + get + { + return anchorEdge; + } + + set + { + if (anchorEdge != value) + { + anchorEdge = value; + OnAnchorEdgeChanged(); + Invalidate(); + Update(); + } + } + } + + public AnchorChooserControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + this.ResizeRedraw = true; + + this.centerImage = PdnResources.GetImageResource("Images.AnchorChooserControl.AnchorImage.png").Reference; + this.xyToAnchorEdge = new AnchorEdge[][] { + new AnchorEdge[] { AnchorEdge.TopLeft, AnchorEdge.Top, AnchorEdge.TopRight }, + new AnchorEdge[] { AnchorEdge.Left, AnchorEdge.Middle, AnchorEdge.Right }, + new AnchorEdge[] { AnchorEdge.BottomLeft, AnchorEdge.Bottom, AnchorEdge.BottomRight } + }; + + this.anchorEdgeToXy = new Hashtable(); + this.anchorEdgeToXy.Add(AnchorEdge.TopLeft, new Point(0, 0)); + this.anchorEdgeToXy.Add(AnchorEdge.Top, new Point(1, 0)); + this.anchorEdgeToXy.Add(AnchorEdge.TopRight, new Point(2, 0)); + this.anchorEdgeToXy.Add(AnchorEdge.Left, new Point(0, 1)); + this.anchorEdgeToXy.Add(AnchorEdge.Middle, new Point(1, 1)); + this.anchorEdgeToXy.Add(AnchorEdge.Right, new Point(2, 1)); + this.anchorEdgeToXy.Add(AnchorEdge.BottomLeft, new Point(0, 2)); + this.anchorEdgeToXy.Add(AnchorEdge.Bottom, new Point(1, 2)); + this.anchorEdgeToXy.Add(AnchorEdge.BottomRight, new Point(2, 2)); + + this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + } + } + + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + #endregion + + private MouseButtons mouseButtonDown; + private bool mouseDown = false; + private Point mouseDownPoint; + private bool drawHotPush = false; + private Point hotAnchorButton = new Point(-1, -1); + + protected override void OnMouseDown(MouseEventArgs e) + { + if (!this.mouseDown) + { + this.mouseDown = true; + this.mouseButtonDown = e.Button; + this.mouseDownPoint = new Point(e.X, e.Y); + + int anchorX = (e.X * 3) / this.Width; + int anchorY = (e.Y * 3) / this.Height; + + this.hotAnchorButton = new Point(anchorX, anchorY); + this.drawHotPush = true; + Invalidate(); + } + + base.OnMouseDown (e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (this.mouseDown && e.Button == this.mouseButtonDown) + { + int anchorX = (e.X * 3) / this.Width; + int anchorY = (e.Y * 3) / this.Height; + + this.drawHotPush = (anchorX == this.hotAnchorButton.X && anchorY == this.hotAnchorButton.Y); + } + + Invalidate(); + base.OnMouseMove (e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + if (this.mouseDown && e.Button == this.mouseButtonDown) + { + int anchorX = (e.X * 3) / this.Width; + int anchorY = (e.Y * 3) / this.Height; + + if (anchorX == this.hotAnchorButton.X && anchorY == this.hotAnchorButton.Y && + anchorX >= 0 && anchorX <= 2 && + anchorY >= 0 && anchorY <= 2) + { + AnchorEdge newEdge = (AnchorEdge)this.xyToAnchorEdge[anchorY][anchorX]; + this.AnchorEdge = newEdge; + Invalidate(); + } + } + + this.drawHotPush = false; + this.mouseDown = false; + base.OnMouseUp (e); + } + + protected override void OnMouseLeave(EventArgs e) + { + Invalidate(); + base.OnMouseLeave(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + + // Clear background + e.Graphics.Clear(SystemColors.Control); + + // Draw each part + Point selection = (Point)this.anchorEdgeToXy[this.anchorEdge]; + + double controlCenterX = (double)this.Width / 2.0; + double controlCenterY = (double)this.Height / 2.0; + + Pen linePen = new Pen(SystemColors.WindowText, (((float)Width + (float)Height) / 2.0f) / 64.0f); + AdjustableArrowCap cap = new AdjustableArrowCap((float)Width / 32.0f, (float)Height / 32.0f, true); + linePen.CustomEndCap = cap; + + Point mousePoint = PointToClient(Control.MousePosition); + int mouseAnchorX = (int)Math.Floor(((float)mousePoint.X * 3.0f) / (float)this.Width); + int mouseAnchorY = (int)Math.Floor(((float)mousePoint.Y * 3.0f) / (float)this.Height); + + for (int y = 0; y < 3; ++y) + { + for (int x = 0; x < 3; ++x) + { + AnchorEdge edge = this.xyToAnchorEdge[y][x]; + Point offset = (Point)this.anchorEdgeToXy[edge]; + Point vector = new Point(offset.X - selection.X, offset.Y - selection.Y); + + int left = (this.Width * x) / 3; + int top = (this.Height * y) / 3; + int right = Math.Min(this.Width - 1, (this.Width * (x + 1)) / 3); + int bottom = Math.Min(this.Height - 1, (this.Height * (y + 1)) / 3); + int width = right - left; + int height = bottom - top; + + if (vector.X == 0 && vector.Y == 0) + { + ButtonRenderer.DrawButton(e.Graphics, new Rectangle(left, top, width, height), PushButtonState.Pressed); + e.Graphics.DrawImage(this.centerImage, left + 3, top + 3, width - 6, height - 6); + } + else + { + PushButtonState state; + + if (drawHotPush && x == this.hotAnchorButton.X && y == this.hotAnchorButton.Y) + { + state = PushButtonState.Pressed; + } + else + { + state = PushButtonState.Normal; + + if (!mouseDown && mouseAnchorX == x && mouseAnchorY == y) + { + state = PushButtonState.Hot; + } + } + + ButtonRenderer.DrawButton(e.Graphics, new Rectangle(left, top, width, height), state); + + if (vector.X <= 1 && vector.X >= -1 && vector.Y <= 1 && vector.Y >= -1) + { + double vectorMag = Math.Sqrt((double)((vector.X * vector.X) + (vector.Y * vector.Y))); + double normalX = (double)vector.X / vectorMag; + double normalY = (double)vector.Y / vectorMag; + + Point center = new Point((left + right) / 2, (top + bottom) / 2); + + Point start = new Point(center.X - (width / 4) * vector.X, center.Y - (height / 4) * vector.Y); + Point end = new Point( + start.X + (int)(((double)width / 2.0) * normalX), + start.Y + (int)(((double)height / 2.0) * normalY)); + + PixelOffsetMode oldPOM = e.Graphics.PixelOffsetMode; + e.Graphics.PixelOffsetMode = PixelOffsetMode.Half; + e.Graphics.DrawLine(linePen, start, end); + e.Graphics.PixelOffsetMode = oldPOM; + } + } + } + } + + linePen.Dispose(); + base.OnPaint(e); + } + } +} diff --git a/src/AnchorChooserControl.resx b/src/AnchorChooserControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/AnchorEdge.cs b/src/AnchorEdge.cs new file mode 100644 index 0000000..8bdc0c2 --- /dev/null +++ b/src/AnchorEdge.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum AnchorEdge + { + TopLeft, + Top, + TopRight, + Left, + Middle, + Right, + BottomLeft, + Bottom, + BottomRight + } +} diff --git a/src/AppEnvironment.cs b/src/AppEnvironment.cs new file mode 100644 index 0000000..b454f2c --- /dev/null +++ b/src/AppEnvironment.cs @@ -0,0 +1,929 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Drawing.Text; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; + +namespace PaintDotNet +{ + // TODO: rename AppEnvironment -> ToolEnvironment or ToolConfigItems or something + /// + /// Manages document-independent workspace configuration details, and provides + /// notification events for every item that can change. + /// + [Serializable] + internal sealed class AppEnvironment + : IDisposable, + ICloneable, + IDeserializationCallback + { + private TextAlignment textAlignment; + private GradientInfo gradientInfo; + private FontSmoothing fontSmoothing; + private FontInfo fontInfo; + private PenInfo penInfo; + private BrushInfo brushInfo; + private ColorBgra primaryColor; + private ColorBgra secondaryColor; + private bool alphaBlending; + private ShapeDrawType shapeDrawType; + private bool antiAliasing; + private ColorPickerClickBehavior colorPickerClickBehavior; + private ResamplingAlgorithm resamplingAlgorithm; + private float tolerance; + + // Added in v3.20. If not found in the serialized data, must default to CombineMode.Replace. + // Conveniently for us, this is equal to (CombineMode)0. Otherwise we would need to have a + // boolean flag as well in order to detect if the data needed to be reset to default. + [OptionalField] + private CombineMode selectionCombineMode; + + [OptionalField] + private FloodMode floodMode; + + [OptionalField] + private SelectionDrawModeInfo selectionDrawModeInfo; + + public static AppEnvironment GetDefaultAppEnvironment() + { + AppEnvironment appEnvironment; + + try + { + string defaultAppEnvBase64 = Settings.CurrentUser.GetString(SettingNames.DefaultAppEnvironment, null); + + if (defaultAppEnvBase64 == null) + { + appEnvironment = null; + } + else + { + byte[] defaultAppEnvBytes = System.Convert.FromBase64String(defaultAppEnvBase64); + BinaryFormatter formatter = new BinaryFormatter(); + + using (MemoryStream stream = new MemoryStream(defaultAppEnvBytes, false)) + { + object defaultAppEnvObject = formatter.Deserialize(stream); + appEnvironment = (AppEnvironment)defaultAppEnvObject; + } + } + } + + catch (Exception) + { + appEnvironment = null; + } + + if (appEnvironment == null) + { + appEnvironment = new AppEnvironment(); + appEnvironment.SetToDefaults(); + } + + return appEnvironment; + } + + public void SaveAsDefaultAppEnvironment() + { + BinaryFormatter formatter = new BinaryFormatter(); + MemoryStream stream = new MemoryStream(); + formatter.Serialize(stream, this); + byte[] bytes = stream.GetBuffer(); + string base64 = Convert.ToBase64String(bytes); + Settings.CurrentUser.SetString(SettingNames.DefaultAppEnvironment, base64); + } + + public void LoadFrom(AppEnvironment appEnvironment) + { + this.textAlignment = appEnvironment.textAlignment; + this.gradientInfo = appEnvironment.gradientInfo.Clone(); + this.fontSmoothing = appEnvironment.fontSmoothing; + this.fontInfo = appEnvironment.fontInfo.Clone(); + this.penInfo = appEnvironment.penInfo.Clone(); + this.brushInfo = appEnvironment.brushInfo.Clone(); + this.primaryColor = appEnvironment.primaryColor; + this.secondaryColor = appEnvironment.secondaryColor; + this.alphaBlending = appEnvironment.alphaBlending; + this.shapeDrawType = appEnvironment.shapeDrawType; + this.antiAliasing = appEnvironment.antiAliasing; + this.colorPickerClickBehavior = appEnvironment.colorPickerClickBehavior; + this.resamplingAlgorithm = appEnvironment.resamplingAlgorithm; + this.tolerance = appEnvironment.tolerance; + this.selectionCombineMode = appEnvironment.selectionCombineMode; + this.floodMode = appEnvironment.floodMode; + this.selectionDrawModeInfo = appEnvironment.selectionDrawModeInfo.Clone(); + PerformAllChanged(); + } + + #region Font stuff + public TextAlignment TextAlignment + { + get + { + return this.textAlignment; + } + + set + { + if (value != this.textAlignment) + { + OnTextAlignmentChanging(); + this.textAlignment = value; + OnTextAlignmentChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler TextAlignmentChanging; + + private void OnTextAlignmentChanging() + { + if (TextAlignmentChanging != null) + { + TextAlignmentChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler TextAlignmentChanged; + + private void OnTextAlignmentChanged() + { + if (TextAlignmentChanged != null) + { + TextAlignmentChanged(this, EventArgs.Empty); + } + } + + public FontInfo FontInfo + { + get + { + return this.fontInfo; + } + + set + { + if (this.fontInfo != value) + { + OnFontInfoChanging(); + this.fontInfo = value; + OnFontInfoChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler FontInfoChanging; + + private void OnFontInfoChanging() + { + if (FontInfoChanging != null) + { + FontInfoChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler FontInfoChanged; + + private void OnFontInfoChanged() + { + if (FontInfoChanged != null) + { + FontInfoChanged(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler FontSmoothingChanging; + + private void OnFontSmoothingChanging() + { + if (FontSmoothingChanging != null) + { + FontSmoothingChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler FontSmoothingChanged; + + private void OnFontSmoothingChanged() + { + if (FontSmoothingChanged != null) + { + FontSmoothingChanged(this, EventArgs.Empty); + } + } + + public FontSmoothing FontSmoothing + { + get + { + return this.fontSmoothing; + } + + set + { + if (this.fontSmoothing != value) + { + OnFontSmoothingChanging(); + this.fontSmoothing = value; + OnFontSmoothingChanged(); + } + } + } + #endregion + + #region GradientInfo + [field: NonSerialized] + public event EventHandler GradientInfoChanging; + + private void OnGradientInfoChanging() + { + if (GradientInfoChanging != null) + { + GradientInfoChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler GradientInfoChanged; + + private void OnGradientInfoChanged() + { + if (GradientInfoChanged != null) + { + GradientInfoChanged(this, EventArgs.Empty); + } + } + + public GradientInfo GradientInfo + { + get + { + return this.gradientInfo; + } + + set + { + OnGradientInfoChanging(); + this.gradientInfo = value; + OnGradientInfoChanged(); + } + } + #endregion + + #region PenInfo + public Pen CreatePen(bool swapColors) + { + if (!swapColors) + { + return PenInfo.CreatePen(BrushInfo, PrimaryColor.ToColor(), SecondaryColor.ToColor()); + } + else + { + return PenInfo.CreatePen(BrushInfo, SecondaryColor.ToColor(), PrimaryColor.ToColor()); + } + } + + public PenInfo PenInfo + { + get + { + return this.penInfo.Clone(); + } + + set + { + if (this.penInfo != value) + { + OnPenInfoChanging(); + this.penInfo = value.Clone(); + OnPenInfoChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler PenInfoChanging; + + private void OnPenInfoChanging() + { + if (PenInfoChanging != null) + { + PenInfoChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler PenInfoChanged; + + private void OnPenInfoChanged() + { + if (PenInfoChanged != null) + { + PenInfoChanged(this, EventArgs.Empty); + } + } + #endregion + + #region BrushInfo + public Brush CreateBrush(bool swapColors) + { + if (!swapColors) + { + return BrushInfo.CreateBrush(PrimaryColor.ToColor(), SecondaryColor.ToColor()); + } + else + { + return BrushInfo.CreateBrush(SecondaryColor.ToColor(), PrimaryColor.ToColor()); + } + } + + public BrushInfo BrushInfo + { + get + { + return this.brushInfo.Clone(); + } + + set + { + OnBrushInfoChanging(); + this.brushInfo = value.Clone(); + OnBrushInfoChanged(); + } + } + + [field: NonSerialized] + public event EventHandler BrushInfoChanging; + + private void OnBrushInfoChanging() + { + if (BrushInfoChanging != null) + { + BrushInfoChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler BrushInfoChanged; + + private void OnBrushInfoChanged() + { + if (BrushInfoChanged != null) + { + BrushInfoChanged(this, EventArgs.Empty); + } + } + #endregion + + #region PrimaryColor + public ColorBgra PrimaryColor + { + get + { + return this.primaryColor; + } + + set + { + if (this.primaryColor != value) + { + OnPrimaryColorChanging(); + this.primaryColor = value; + OnPrimaryColorChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler PrimaryColorChanging; + + private void OnPrimaryColorChanging() + { + if (PrimaryColorChanging != null) + { + PrimaryColorChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler PrimaryColorChanged; + + private void OnPrimaryColorChanged() + { + if (PrimaryColorChanged != null) + { + PrimaryColorChanged(this, EventArgs.Empty); + } + } + #endregion + + #region SecondaryColor + public ColorBgra SecondaryColor + { + get + { + return this.secondaryColor; + } + + set + { + if (this.secondaryColor != value) + { + OnBackColorChanging(); + this.secondaryColor = value; + OnSecondaryColorChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler SecondaryColorChanging; + + private void OnBackColorChanging() + { + if (SecondaryColorChanging != null) + { + SecondaryColorChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler SecondaryColorChanged; + + private void OnSecondaryColorChanged() + { + if (SecondaryColorChanged != null) + { + SecondaryColorChanged(this, EventArgs.Empty); + } + } + #endregion + + #region AlphaBlending + public CompositingMode GetCompositingMode() + { + return this.alphaBlending ? CompositingMode.SourceOver : CompositingMode.SourceCopy; + } + + public bool AlphaBlending + { + get + { + return this.alphaBlending; + } + + set + { + if (value != this.alphaBlending) + { + OnAlphaBlendingChanging(); + this.alphaBlending = value; + OnAlphaBlendingChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler AlphaBlendingChanging; + + private void OnAlphaBlendingChanging() + { + if (AlphaBlendingChanging != null) + { + AlphaBlendingChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler AlphaBlendingChanged; + + private void OnAlphaBlendingChanged() + { + if (AlphaBlendingChanged != null) + { + AlphaBlendingChanged(this, EventArgs.Empty); + } + } + #endregion + + #region ShapeDrawType + public ShapeDrawType ShapeDrawType + { + get + { + return this.shapeDrawType; + } + + set + { + if (this.shapeDrawType != value) + { + OnShapeDrawTypeChanging(); + this.shapeDrawType = value; + OnShapeDrawTypeChanged(); + } + } + } + + [field: NonSerialized] + public event EventHandler ShapeDrawTypeChanging; + + private void OnShapeDrawTypeChanging() + { + if (ShapeDrawTypeChanging != null) + { + ShapeDrawTypeChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler ShapeDrawTypeChanged; + + private void OnShapeDrawTypeChanged() + { + if (ShapeDrawTypeChanged != null) + { + ShapeDrawTypeChanged(this, EventArgs.Empty); + } + } + #endregion + + #region AntiAliasing + [field: NonSerialized] + public event EventHandler AntiAliasingChanging; + + private void OnAntiAliasingChanging() + { + if (AntiAliasingChanging != null) + { + AntiAliasingChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler AntiAliasingChanged; + + private void OnAntiAliasingChanged() + { + if (AntiAliasingChanged != null) + { + AntiAliasingChanged(this, EventArgs.Empty); + } + } + + public bool AntiAliasing + { + get + { + return this.antiAliasing; + } + + set + { + if (this.antiAliasing != value) + { + OnAntiAliasingChanging(); + this.antiAliasing = value; + OnAntiAliasingChanged(); + } + } + } + #endregion + + #region Color Picker behavior + [field: NonSerialized] + public event EventHandler ColorPickerClickBehaviorChanging; + + private void OnColorPickerClickBehaviorChanging() + { + if (ColorPickerClickBehaviorChanging != null) + { + ColorPickerClickBehaviorChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler ColorPickerClickBehaviorChanged; + + private void OnColorPickerClickBehaviorChanged() + { + if (ColorPickerClickBehaviorChanged != null) + { + ColorPickerClickBehaviorChanged(this, EventArgs.Empty); + } + } + + public ColorPickerClickBehavior ColorPickerClickBehavior + { + get + { + return this.colorPickerClickBehavior; + } + + set + { + if (this.colorPickerClickBehavior != value) + { + OnColorPickerClickBehaviorChanging(); + this.colorPickerClickBehavior = value; + OnColorPickerClickBehaviorChanged(); + } + } + } + #endregion + + #region ResamplingAlgorithm + [field: NonSerialized] + public event EventHandler ResamplingAlgorithmChanging; + + private void OnResamplingAlgorithmChanging() + { + if (ResamplingAlgorithmChanging != null) + { + ResamplingAlgorithmChanging(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler ResamplingAlgorithmChanged; + + private void OnResamplingAlgorithmChanged() + { + if (ResamplingAlgorithmChanged != null) + { + ResamplingAlgorithmChanged(this, EventArgs.Empty); + } + } + + public ResamplingAlgorithm ResamplingAlgorithm + { + get + { + return this.resamplingAlgorithm; + } + + set + { + if (value != this.resamplingAlgorithm) + { + OnResamplingAlgorithmChanging(); + this.resamplingAlgorithm = value; + OnResamplingAlgorithmChanged(); + } + } + } + #endregion + + #region Tolerance + [field: NonSerialized] + public event EventHandler ToleranceChanged; + + private void OnToleranceChanged() + { + if (ToleranceChanged != null) + { + ToleranceChanged(this, EventArgs.Empty); + } + } + + [field: NonSerialized] + public event EventHandler ToleranceChanging; + + private void OnToleranceChanging() + { + if (ToleranceChanging != null) + { + ToleranceChanging(this, EventArgs.Empty); + } + } + + public float Tolerance + { + get + { + return tolerance; + } + + set + { + if (tolerance != value) + { + tolerance = value; + OnToleranceChanged(); + } + } + } + #endregion + + #region SelectionCombineMode + [field: NonSerialized] + public event EventHandler SelectionCombineModeChanged; + + private void OnSelectionCombineModeChanged() + { + if (SelectionCombineModeChanged != null) + { + SelectionCombineModeChanged(this, EventArgs.Empty); + } + } + + public CombineMode SelectionCombineMode + { + get + { + return this.selectionCombineMode; + } + + set + { + if (this.selectionCombineMode != value) + { + this.selectionCombineMode = value; + OnSelectionCombineModeChanged(); + } + } + } + #endregion + + #region FloodMode + [field: NonSerialized] + public event EventHandler FloodModeChanged; + + private void OnFloodModeChanged() + { + if (FloodModeChanged != null) + { + FloodModeChanged(this, EventArgs.Empty); + } + } + + public FloodMode FloodMode + { + get + { + return this.floodMode; + } + + set + { + if (this.floodMode != value) + { + this.floodMode = value; + OnFloodModeChanged(); + } + } + } + #endregion + + #region SelectionDrawModeInfo + [field: NonSerialized] + public event EventHandler SelectionDrawModeInfoChanged; + + private void OnSelectionDrawModeInfoChanged() + { + if (SelectionDrawModeInfoChanged != null) + { + SelectionDrawModeInfoChanged(this, EventArgs.Empty); + } + } + + public SelectionDrawModeInfo SelectionDrawModeInfo + { + get + { + return this.selectionDrawModeInfo.Clone(); + } + + set + { + if (!this.selectionDrawModeInfo.Equals(value)) + { + this.selectionDrawModeInfo = value.Clone(); + OnSelectionDrawModeInfoChanged(); + } + } + } + #endregion + + public void PerformAllChanged() + { + OnFontInfoChanged(); + OnFontSmoothingChanged(); + OnPenInfoChanged(); + OnBrushInfoChanged(); + OnGradientInfoChanged(); + OnSecondaryColorChanged(); + OnPrimaryColorChanged(); + OnAlphaBlendingChanged(); + OnShapeDrawTypeChanged(); + OnAntiAliasingChanged(); + OnTextAlignmentChanged(); + OnToleranceChanged(); + OnColorPickerClickBehaviorChanged(); + OnResamplingAlgorithmChanging(); + OnSelectionCombineModeChanged(); + OnFloodModeChanged(); + OnSelectionDrawModeInfoChanged(); + } + + public void SetToDefaults() + { + this.antiAliasing = true; + this.fontSmoothing = FontSmoothing.Smooth; + this.primaryColor = ColorBgra.FromBgra(0, 0, 0, 255); + this.secondaryColor = ColorBgra.FromBgra(255, 255, 255, 255); + this.gradientInfo = new GradientInfo(GradientType.LinearClamped, false); + this.penInfo = new PenInfo(PenInfo.DefaultDashStyle, 2.0f, PenInfo.DefaultLineCap, PenInfo.DefaultLineCap, PenInfo.DefaultCapScale); + this.brushInfo = new BrushInfo(BrushType.Solid, HatchStyle.BackwardDiagonal); + + try + { + this.fontInfo = new FontInfo(new FontFamily("Arial"), 12, FontStyle.Regular); + } + + catch (Exception) + { + this.fontInfo = new FontInfo(new FontFamily(GenericFontFamilies.SansSerif), 12, FontStyle.Regular); + } + + this.textAlignment = TextAlignment.Left; + this.shapeDrawType = ShapeDrawType.Outline; + this.alphaBlending = true; + this.tolerance = 0.5f; + + this.colorPickerClickBehavior = ColorPickerClickBehavior.NoToolSwitch; + this.resamplingAlgorithm = ResamplingAlgorithm.Bilinear; + this.selectionCombineMode = CombineMode.Replace; + this.floodMode = FloodMode.Local; + this.selectionDrawModeInfo = SelectionDrawModeInfo.CreateDefault(); + } + + public AppEnvironment() + { + SetToDefaults(); + } + + ~AppEnvironment() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + } + } + + public AppEnvironment Clone() + { + BinaryFormatter formatter = new BinaryFormatter(); + MemoryStream stream = new MemoryStream(); + formatter.Serialize(stream, this); + stream.Seek(0, SeekOrigin.Begin); + object cloned = formatter.Deserialize(stream); + stream.Dispose(); + stream = null; + return (AppEnvironment)cloned; + } + + object ICloneable.Clone() + { + return Clone(); + } + + void IDeserializationCallback.OnDeserialization(object sender) + { + if (this.selectionDrawModeInfo == null) + { + this.selectionDrawModeInfo = SelectionDrawModeInfo.CreateDefault(); + } + } + } +} \ No newline at end of file diff --git a/src/AppWorkspace.cs b/src/AppWorkspace.cs new file mode 100644 index 0000000..0605952 --- /dev/null +++ b/src/AppWorkspace.cs @@ -0,0 +1,2447 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.Effects; +using PaintDotNet.HistoryFunctions; +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using PaintDotNet.Tools; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class AppWorkspace + : UserControl, + ISnapObstacleHost + { + private readonly string cursorInfoStatusBarFormat = PdnResources.GetString("StatusBar.CursorInfo.Format"); + private readonly string imageInfoStatusBarFormat = PdnResources.GetString("StatusBar.Size.Format"); + + private Type defaultToolTypeChoice; + + private Type globalToolTypeChoice = null; + private bool globalRulersChoice = false; + + private AppEnvironment appEnvironment; + private DocumentWorkspace activeDocumentWorkspace; + + // if a new workspace is added, and this workspace is not dirty, then it will be removed. + // This keeps track of the last workspace added via CreateBlankDocumentInNewWorkspace (if + // true was passed for its 2nd parameter) + private DocumentWorkspace initialWorkspace; + + private List documentWorkspaces = new List(); + private WorkspaceWidgets widgets; + + private Panel workspacePanel; + private PdnToolBar toolBar; + private PdnStatusBar statusBar; + + private ToolsForm mainToolBarForm; + private LayerForm layerForm; + private HistoryForm historyForm; + private ColorsForm colorsForm; + + private MostRecentFiles mostRecentFiles = null; + private const int defaultMostRecentFilesMax = 8; + + private SnapObstacleController snapObstacle; + private bool addedToSnapManager = false; + private int ignoreUpdateSnapObstacle = 0; + private int suspendThumbnailUpdates = 0; + + public void CheckForUpdates() + { + this.toolBar.MainMenu.CheckForUpdates(); + } + + public IDisposable SuspendThumbnailUpdates() + { + CallbackOnDispose resumeFn = new CallbackOnDispose(ResumeThumbnailUpdates); + + ++this.suspendThumbnailUpdates; + + if (this.suspendThumbnailUpdates == 1) + { + Widgets.DocumentStrip.SuspendThumbnailUpdates(); + Widgets.LayerControl.SuspendLayerPreviewUpdates(); + } + + return resumeFn; + } + + private void ResumeThumbnailUpdates() + { + --this.suspendThumbnailUpdates; + + if (this.suspendThumbnailUpdates == 0) + { + Widgets.DocumentStrip.ResumeThumbnailUpdates(); + Widgets.LayerControl.ResumeLayerPreviewUpdates(); + } + } + + public Type DefaultToolType + { + get + { + return this.defaultToolTypeChoice; + } + + set + { + this.defaultToolTypeChoice = value; + Settings.CurrentUser.SetString(SettingNames.DefaultToolTypeName, value.Name); + } + } + + public Type GlobalToolTypeChoice + { + get + { + return this.globalToolTypeChoice; + } + + set + { + this.globalToolTypeChoice = value; + + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.SetToolFromType(value); + } + } + } + + public DocumentWorkspace InitialWorkspace + { + set + { + this.initialWorkspace = value; + } + } + + public event EventHandler RulersEnabledChanged; + protected virtual void OnRulersEnabledChanged() + { + if (RulersEnabledChanged != null) + { + RulersEnabledChanged(this, EventArgs.Empty); + } + } + + public bool RulersEnabled + { + get + { + return this.globalRulersChoice; + } + + set + { + if (this.globalRulersChoice != value) + { + this.globalRulersChoice = value; + + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.RulersEnabled = value; + } + + OnRulersEnabledChanged(); + } + } + } + + private void DocumentWorkspace_DrawGridChanged(object sender, EventArgs e) + { + DrawGrid = this.activeDocumentWorkspace.DrawGrid; + } + + private void ViewConfigStrip_DrawGridChanged(object sender, EventArgs e) + { + DrawGrid = ((ViewConfigStrip)sender).DrawGrid; + } + + private bool DrawGrid + { + get + { + return this.Widgets.ViewConfigStrip.DrawGrid; + } + + set + { + if (this.Widgets.ViewConfigStrip.DrawGrid != value) + { + this.Widgets.ViewConfigStrip.DrawGrid = value; + } + + if (this.activeDocumentWorkspace != null && this.activeDocumentWorkspace.DrawGrid != value) + { + this.activeDocumentWorkspace.DrawGrid = value; + } + + Settings.CurrentUser.SetBoolean(SettingNames.DrawGrid, this.DrawGrid); + } + } + + public event EventHandler UnitsChanged; + protected virtual void OnUnitsChanged() + { + if (UnitsChanged != null) + { + UnitsChanged(this, EventArgs.Empty); + } + } + + public MeasurementUnit Units + { + get + { + return this.widgets.ViewConfigStrip.Units; + } + + set + { + this.widgets.ViewConfigStrip.Units = value; + } + } + + public SnapObstacle SnapObstacle + { + get + { + if (this.snapObstacle == null) + { + // HACK: for some reason retrieving the ClientRectangle can raise a VisibleChanged event + // so we initially pass in Rectangle.Empty for the rectangle bounds + this.snapObstacle = new SnapObstacleController( + this.Name, + Rectangle.Empty, + SnapRegion.Interior, + true); + + this.snapObstacle.EnableSave = false; + + PdnBaseForm pdbForm = FindForm() as PdnBaseForm; + pdbForm.Moving += new MovingEventHandler(ParentForm_Moving); + pdbForm.Move += new EventHandler(ParentForm_Move); + pdbForm.ResizeEnd += new EventHandler(ParentForm_ResizeEnd); + pdbForm.Layout += new LayoutEventHandler(ParentForm_Layout); + pdbForm.SizeChanged += new EventHandler(ParentForm_SizeChanged); + + UpdateSnapObstacle(); + } + + return this.snapObstacle; + } + } + + private void ParentForm_Move(object sender, EventArgs e) + { + UpdateSnapObstacle(); + } + + private void ParentForm_SizeChanged(object sender, EventArgs e) + { + UpdateSnapObstacle(); + } + + private void ParentForm_Layout(object sender, LayoutEventArgs e) + { + UpdateSnapObstacle(); + } + + private void ParentForm_ResizeEnd(object sender, EventArgs e) + { + UpdateSnapObstacle(); + } + + private void ParentForm_Moving(object sender, MovingEventArgs e) + { + UpdateSnapObstacle(); + } + + private void SuspendUpdateSnapObstacle() + { + ++this.ignoreUpdateSnapObstacle; + } + + private void ResumeUpdateSnapObstacle() + { + --this.ignoreUpdateSnapObstacle; + } + + private void UpdateSnapObstacle() + { + if (this.ignoreUpdateSnapObstacle > 0) + { + return; + } + + if (this.snapObstacle == null) + { + return; + } + + if (!this.addedToSnapManager) + { + SnapManager sm = SnapManager.FindMySnapManager(this); + + if (sm != null) + { + SnapObstacle so = this.SnapObstacle; + + if (!this.addedToSnapManager) + { + sm.AddSnapObstacle(this.SnapObstacle); + this.addedToSnapManager = true; + + FindForm().Shown += new EventHandler(AppWorkspace_Shown); + } + } + } + + if (this.snapObstacle != null) + { + Rectangle clientRect; + + if (ActiveDocumentWorkspace != null) + { + clientRect = ActiveDocumentWorkspace.VisibleViewRectangle; + } + else + { + clientRect = this.workspacePanel.ClientRectangle; + } + + Rectangle screenRect = this.workspacePanel.RectangleToScreen(clientRect); + this.snapObstacle.SetBounds(screenRect); + this.snapObstacle.Enabled = this.Visible && this.Enabled; + } + } + + private void AppWorkspace_Shown(object sender, EventArgs e) + { + UpdateSnapObstacle(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + UpdateSnapObstacle(); + base.OnLayout(levent); + } + + protected override void OnLocationChanged(EventArgs e) + { + UpdateSnapObstacle(); + base.OnLocationChanged(e); + } + + protected override void OnSizeChanged(EventArgs e) + { + UpdateSnapObstacle(); + base.OnSizeChanged(e); + } + + protected override void OnEnabledChanged(EventArgs e) + { + UpdateSnapObstacle(); + base.OnEnabledChanged(e); + } + + protected override void OnVisibleChanged(EventArgs e) + { + UpdateSnapObstacle(); + base.OnVisibleChanged(e); + } + + public void ResetFloatingForms() + { + ResetFloatingForm(Widgets.ToolsForm); + ResetFloatingForm(Widgets.HistoryForm); + ResetFloatingForm(Widgets.LayerForm); + ResetFloatingForm(Widgets.ColorsForm); + } + + public void ResetFloatingForm(FloatingToolForm ftf) + { + SnapManager sm = SnapManager.FindMySnapManager(this); + + if (ftf == Widgets.ToolsForm) + { + sm.ParkObstacle(Widgets.ToolsForm, this, HorizontalSnapEdge.Top, VerticalSnapEdge.Left); + } + else if (ftf == Widgets.HistoryForm) + { + sm.ParkObstacle(Widgets.HistoryForm, this, HorizontalSnapEdge.Top, VerticalSnapEdge.Right); + } + else if (ftf == Widgets.LayerForm) + { + sm.ParkObstacle(Widgets.LayerForm, this, HorizontalSnapEdge.Bottom, VerticalSnapEdge.Right); + } + else if (ftf == Widgets.ColorsForm) + { + sm.ParkObstacle(Widgets.ColorsForm, this, HorizontalSnapEdge.Bottom, VerticalSnapEdge.Left); + } + else + { + throw new ArgumentException(); + } + } + + private Set> effectLoadErrors = new Set>(); + + public void ReportEffectLoadError(Triple error) + { + lock (this.effectLoadErrors) + { + if (!this.effectLoadErrors.Contains(error)) + { + this.effectLoadErrors.Add(error); + } + } + } + + public static string GetLocalizedEffectErrorMessage(Assembly assembly, Type type, Exception exception) + { + IPluginSupportInfo supportInfo; + string typeName; + + if (type != null) + { + typeName = type.FullName; + supportInfo = PluginSupportInfo.GetPluginSupportInfo(type); + } + else if (exception is TypeLoadException) + { + TypeLoadException asTlex = exception as TypeLoadException; + typeName = asTlex.TypeName; + supportInfo = PluginSupportInfo.GetPluginSupportInfo(assembly); + } + else + { + supportInfo = PluginSupportInfo.GetPluginSupportInfo(assembly); + typeName = null; + } + + return GetLocalizedEffectErrorMessage(assembly, typeName, supportInfo, exception); + } + + public static string GetLocalizedEffectErrorMessage(Assembly assembly, string typeName, Exception exception) + { + IPluginSupportInfo supportInfo = PluginSupportInfo.GetPluginSupportInfo(assembly); + return GetLocalizedEffectErrorMessage(assembly, typeName, supportInfo, exception); + } + + private static string GetLocalizedEffectErrorMessage(Assembly assembly, string typeName, IPluginSupportInfo supportInfo, Exception exception) + { + string fileName = assembly.Location; + string shortErrorFormat = PdnResources.GetString("EffectErrorMessage.ShortFormat"); + string fullErrorFormat = PdnResources.GetString("EffectErrorMessage.FullFormat"); + string notSuppliedText = PdnResources.GetString("EffectErrorMessage.InfoNotSupplied"); + + string errorText; + + if (supportInfo == null) + { + errorText = string.Format( + shortErrorFormat, + fileName ?? notSuppliedText, + typeName ?? notSuppliedText, + exception.ToString()); + } + else + { + errorText = string.Format( + fullErrorFormat, + fileName ?? notSuppliedText, + typeName ?? supportInfo.DisplayName ?? notSuppliedText, + (supportInfo.Version ?? new Version()).ToString(), + supportInfo.Author ?? notSuppliedText, + supportInfo.Copyright ?? notSuppliedText, + (supportInfo.WebsiteUri == null ? notSuppliedText : supportInfo.WebsiteUri.ToString()), + exception.ToString()); + } + + return errorText; + } + + public IList> GetEffectLoadErrors() + { + return this.effectLoadErrors.ToArray(); + } + + public void RunEffect(Type effectType) + { + // TODO: this is kind of a hack + this.toolBar.MainMenu.RunEffect(effectType); + } + + public PdnToolBar ToolBar + { + get + { + return this.toolBar; + } + } + + private ImageResource FileNewIcon + { + get + { + return PdnResources.GetImageResource("Icons.MenuFileNewIcon.png"); + } + } + + private ImageResource ImageFromDiskIcon + { + get + { + return PdnResources.GetImageResource("Icons.ImageFromDiskIcon.png"); + } + } + + public MostRecentFiles MostRecentFiles + { + get + { + if (this.mostRecentFiles == null) + { + this.mostRecentFiles = new MostRecentFiles(defaultMostRecentFilesMax); + } + + return this.mostRecentFiles; + } + } + + private void DocumentWorkspace_DocumentChanging(object sender, EventArgs e) + { + UI.SuspendControlPainting(this); + } + + private void DocumentWorkspace_DocumentChanged(object sender, EventArgs e) + { + UpdateDocInfoInStatusBar(); + + UI.ResumeControlPainting(this); + Invalidate(true); + } + + private void CoordinatesToStrings(int x, int y, out string xString, out string yString, out string unitsString) + { + this.activeDocumentWorkspace.Document.CoordinatesToStrings(this.Units, x, y, out xString, out yString, out unitsString); + } + + private void UpdateCursorInfoInStatusBar(int cursorX, int cursorY) + { + SuspendLayout(); + + if (this.activeDocumentWorkspace == null || + this.activeDocumentWorkspace.Document == null) + { + this.statusBar.CursorInfoText = string.Empty; + } + else + { + string xString; + string yString; + string units; + + CoordinatesToStrings(cursorX, cursorY, out xString, out yString, out units); + + string cursorText = string.Format( + CultureInfo.InvariantCulture, + this.cursorInfoStatusBarFormat, + xString, + units, + yString, + units); + + this.statusBar.CursorInfoText = cursorText; + } + + ResumeLayout(false); + } + + private void UpdateDocInfoInStatusBar() + { + if (this.activeDocumentWorkspace == null || + this.activeDocumentWorkspace.Document == null) + { + this.statusBar.ImageInfoStatusText = string.Empty; + } + else if (this.activeDocumentWorkspace != null && + this.activeDocumentWorkspace.Document != null) + { + string widthString; + string heightString; + string units; + + CoordinatesToStrings( + this.activeDocumentWorkspace.Document.Width, + this.activeDocumentWorkspace.Document.Height, + out widthString, + out heightString, + out units); + + string imageText = string.Format( + CultureInfo.InvariantCulture, + this.imageInfoStatusBarFormat, + widthString, + units, + heightString, + units); + + this.statusBar.ImageInfoStatusText = imageText; + } + } + + [Browsable(false)] + public WorkspaceWidgets Widgets + { + get + { + return this.widgets; + } + } + + [Browsable(false)] + public AppEnvironment AppEnvironment + { + get + { + return this.appEnvironment; + } + } + + [Browsable(false)] + public DocumentWorkspace ActiveDocumentWorkspace + { + get + { + return this.activeDocumentWorkspace; + } + + set + { + if (value != this.activeDocumentWorkspace) + { + if (value != null && + this.documentWorkspaces.IndexOf(value) == -1) + { + throw new ArgumentException("DocumentWorkspace was not created with AddNewDocumentWorkspace"); + } + + bool focused = false; + if (this.activeDocumentWorkspace != null) + { + focused = this.activeDocumentWorkspace.Focused; + } + + UI.SuspendControlPainting(this); + OnActiveDocumentWorkspaceChanging(); + this.activeDocumentWorkspace = value; + OnActiveDocumentWorkspaceChanged(); + UI.ResumeControlPainting(this); + + Refresh(); + + if (value != null) + { + value.Focus(); + } + } + } + } + + private void ActiveDocumentWorkspace_FirstInputAfterGotFocus(object sender, EventArgs e) + { + this.toolBar.DocumentStrip.EnsureItemFullyVisible(this.toolBar.DocumentStrip.SelectedDocumentIndex); + } + + public DocumentWorkspace[] DocumentWorkspaces + { + get + { + return this.documentWorkspaces.ToArray(); + } + } + + public DocumentWorkspace AddNewDocumentWorkspace() + { + if (this.initialWorkspace != null) + { + if (this.initialWorkspace.Document == null || !this.initialWorkspace.Document.Dirty) + { + this.globalToolTypeChoice = this.initialWorkspace.GetToolType(); + RemoveDocumentWorkspace(this.initialWorkspace); + this.initialWorkspace = null; + } + } + + DocumentWorkspace dw = new DocumentWorkspace(); + + dw.AppWorkspace = this; + this.documentWorkspaces.Add(dw); + this.toolBar.DocumentStrip.AddDocumentWorkspace(dw); + + return dw; + } + + public Image GetDocumentWorkspaceThumbnail(DocumentWorkspace dw) + { + this.toolBar.DocumentStrip.SyncThumbnails(); + Image[] images = this.toolBar.DocumentStrip.DocumentThumbnails; + DocumentWorkspace[] documents = this.toolBar.DocumentStrip.DocumentList; + + for (int i = 0; i < documents.Length; ++i) + { + if (documents[i] == dw) + { + return images[i]; + } + } + + throw new ArgumentException("The requested DocumentWorkspace doesn't exist in this AppWorkspace"); + } + + public void RemoveDocumentWorkspace(DocumentWorkspace documentWorkspace) + { + int dwIndex = this.documentWorkspaces.IndexOf(documentWorkspace); + + if (dwIndex == -1) + { + throw new ArgumentException("DocumentWorkspace was not created with AddNewDocumentWorkspace"); + } + + bool removingCurrentDW; + if (this.ActiveDocumentWorkspace == documentWorkspace) + { + removingCurrentDW = true; + this.globalToolTypeChoice = documentWorkspace.GetToolType(); + } + else + { + removingCurrentDW = false; + } + + documentWorkspace.SetTool(null); + + // Choose new active DW if removing the current DW + if (removingCurrentDW) + { + if (this.documentWorkspaces.Count == 1) + { + this.ActiveDocumentWorkspace = null; + } + else if (dwIndex == 0) + { + this.ActiveDocumentWorkspace = this.documentWorkspaces[1]; + } + else + { + this.ActiveDocumentWorkspace = this.documentWorkspaces[dwIndex - 1]; + } + } + + this.documentWorkspaces.Remove(documentWorkspace); + this.toolBar.DocumentStrip.RemoveDocumentWorkspace(documentWorkspace); + + if (this.initialWorkspace == documentWorkspace) + { + this.initialWorkspace = null; + } + + // Clean up the DocumentWorkspace + Document document = documentWorkspace.Document; + + documentWorkspace.Document = null; + document.Dispose(); + + documentWorkspace.Dispose(); + documentWorkspace = null; + } + + private void UpdateHistoryButtons() + { + if (ActiveDocumentWorkspace == null) + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Undo, false); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Redo, false); + } + else + { + if (ActiveDocumentWorkspace.History.UndoStack.Count > 1) + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Undo, true); + } + else + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Undo, false); + } + + if (ActiveDocumentWorkspace.History.RedoStack.Count > 0) + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Redo, true); + } + else + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Redo, false); + } + } + } + + private void HistoryChangedHandler(object sender, EventArgs e) + { + UpdateHistoryButtons(); + + // some actions change the document size: make sure we update our status bar panel + // TODO: shouldn't this be handled by our DocumentWorkspace.DocumentChanged handler...? + UpdateDocInfoInStatusBar(); + } + + public event EventHandler ActiveDocumentWorkspaceChanging; + protected virtual void OnActiveDocumentWorkspaceChanging() + { + SuspendUpdateSnapObstacle(); + + if (ActiveDocumentWorkspaceChanging != null) + { + ActiveDocumentWorkspaceChanging(this, EventArgs.Empty); + } + + if (this.activeDocumentWorkspace != null) + { + this.activeDocumentWorkspace.FirstInputAfterGotFocus += + ActiveDocumentWorkspace_FirstInputAfterGotFocus; + + this.activeDocumentWorkspace.RulersEnabledChanged -= this.DocumentWorkspace_RulersEnabledChanged; + this.activeDocumentWorkspace.DocumentMouseEnter -= this.DocumentMouseEnterHandler; + this.activeDocumentWorkspace.DocumentMouseLeave -= this.DocumentMouseLeaveHandler; + this.activeDocumentWorkspace.DocumentMouseMove -= this.DocumentMouseMoveHandler; + this.activeDocumentWorkspace.DocumentMouseDown -= this.DocumentMouseDownHandler; + this.activeDocumentWorkspace.Scroll -= this.DocumentWorkspace_Scroll; + this.activeDocumentWorkspace.Layout -= this.DocumentWorkspace_Layout; + this.activeDocumentWorkspace.DrawGridChanged -= this.DocumentWorkspace_DrawGridChanged; + this.activeDocumentWorkspace.DocumentClick -= this.DocumentClick; + this.activeDocumentWorkspace.DocumentMouseUp -= this.DocumentMouseUpHandler; + this.activeDocumentWorkspace.DocumentKeyPress -= this.DocumentKeyPress; + this.activeDocumentWorkspace.DocumentKeyUp -= this.DocumenKeyUp; + this.activeDocumentWorkspace.DocumentKeyDown -= this.DocumentKeyDown; + + this.activeDocumentWorkspace.History.Changed -= HistoryChangedHandler; + this.activeDocumentWorkspace.StatusChanged -= OnDocumentWorkspaceStatusChanged; + this.activeDocumentWorkspace.DocumentChanging -= DocumentWorkspace_DocumentChanging; + this.activeDocumentWorkspace.DocumentChanged -= DocumentWorkspace_DocumentChanged; + this.activeDocumentWorkspace.Selection.Changing -= SelectedPathChangingHandler; + this.activeDocumentWorkspace.Selection.Changed -= SelectedPathChangedHandler; + this.activeDocumentWorkspace.ScaleFactorChanged -= ZoomChangedHandler; + this.activeDocumentWorkspace.ZoomBasisChanged -= DocumentWorkspace_ZoomBasisChanged; + + this.activeDocumentWorkspace.Visible = false; + this.historyForm.HistoryControl.HistoryStack = null; + + this.activeDocumentWorkspace.ToolChanging -= this.ToolChangingHandler; + this.activeDocumentWorkspace.ToolChanged -= this.ToolChangedHandler; + + if (this.activeDocumentWorkspace.Tool != null) + { + while (this.activeDocumentWorkspace.Tool.IsMouseEntered) + { + this.activeDocumentWorkspace.Tool.PerformMouseLeave(); + } + } + + Type toolType = this.activeDocumentWorkspace.GetToolType(); + + if (toolType != null) + { + this.globalToolTypeChoice = this.activeDocumentWorkspace.GetToolType(); + } + } + + ResumeUpdateSnapObstacle(); + UpdateSnapObstacle(); + } + + public event EventHandler ActiveDocumentWorkspaceChanged; + protected virtual void OnActiveDocumentWorkspaceChanged() + { + SuspendUpdateSnapObstacle(); + + if (this.activeDocumentWorkspace == null) + { + this.toolBar.CommonActionsStrip.SetButtonEnabled(CommonAction.Print, false); + this.toolBar.CommonActionsStrip.SetButtonEnabled(CommonAction.Save, false); + } + else + { + this.activeDocumentWorkspace.SuspendLayout(); + + this.toolBar.CommonActionsStrip.SetButtonEnabled(CommonAction.Print, true); + this.toolBar.CommonActionsStrip.SetButtonEnabled(CommonAction.Save, true); + + this.activeDocumentWorkspace.BackColor = System.Drawing.SystemColors.ControlDark; + this.activeDocumentWorkspace.Dock = System.Windows.Forms.DockStyle.Fill; + this.activeDocumentWorkspace.DrawGrid = this.DrawGrid; + this.activeDocumentWorkspace.PanelAutoScroll = true; + this.activeDocumentWorkspace.RulersEnabled = this.globalRulersChoice; + this.activeDocumentWorkspace.TabIndex = 0; + this.activeDocumentWorkspace.TabStop = false; + this.activeDocumentWorkspace.RulersEnabledChanged += this.DocumentWorkspace_RulersEnabledChanged; + this.activeDocumentWorkspace.DocumentMouseEnter += this.DocumentMouseEnterHandler; + this.activeDocumentWorkspace.DocumentMouseLeave += this.DocumentMouseLeaveHandler; + this.activeDocumentWorkspace.DocumentMouseMove += this.DocumentMouseMoveHandler; + this.activeDocumentWorkspace.DocumentMouseDown += this.DocumentMouseDownHandler; + this.activeDocumentWorkspace.Scroll += this.DocumentWorkspace_Scroll; + this.activeDocumentWorkspace.DrawGridChanged += this.DocumentWorkspace_DrawGridChanged; + this.activeDocumentWorkspace.DocumentClick += this.DocumentClick; + this.activeDocumentWorkspace.DocumentMouseUp += this.DocumentMouseUpHandler; + this.activeDocumentWorkspace.DocumentKeyPress += this.DocumentKeyPress; + this.activeDocumentWorkspace.DocumentKeyUp += this.DocumenKeyUp; + this.activeDocumentWorkspace.DocumentKeyDown += this.DocumentKeyDown; + + if (this.workspacePanel.Controls.Contains(this.activeDocumentWorkspace)) + { + this.activeDocumentWorkspace.Visible = true; + } + else + { + this.activeDocumentWorkspace.Dock = DockStyle.Fill; + this.workspacePanel.Controls.Add(this.activeDocumentWorkspace); + } + + this.activeDocumentWorkspace.Layout += this.DocumentWorkspace_Layout; + this.toolBar.ViewConfigStrip.ScaleFactor = this.activeDocumentWorkspace.ScaleFactor; + this.toolBar.ViewConfigStrip.ZoomBasis = this.activeDocumentWorkspace.ZoomBasis; + + this.activeDocumentWorkspace.AppWorkspace = this; + this.activeDocumentWorkspace.History.Changed += HistoryChangedHandler; + this.activeDocumentWorkspace.StatusChanged += OnDocumentWorkspaceStatusChanged; + this.activeDocumentWorkspace.DocumentChanging += DocumentWorkspace_DocumentChanging; + this.activeDocumentWorkspace.DocumentChanged += DocumentWorkspace_DocumentChanged; + this.activeDocumentWorkspace.Selection.Changing += SelectedPathChangingHandler; + this.activeDocumentWorkspace.Selection.Changed += SelectedPathChangedHandler; + this.activeDocumentWorkspace.ScaleFactorChanged += ZoomChangedHandler; + this.activeDocumentWorkspace.ZoomBasisChanged += DocumentWorkspace_ZoomBasisChanged; + + this.activeDocumentWorkspace.Units = this.widgets.ViewConfigStrip.Units; + + this.historyForm.HistoryControl.HistoryStack = this.ActiveDocumentWorkspace.History; + + this.activeDocumentWorkspace.ToolChanging += this.ToolChangingHandler; + this.activeDocumentWorkspace.ToolChanged += this.ToolChangedHandler; + + this.toolBar.ViewConfigStrip.RulersEnabled = this.activeDocumentWorkspace.RulersEnabled; + this.toolBar.DocumentStrip.SelectDocumentWorkspace(this.activeDocumentWorkspace); + + this.activeDocumentWorkspace.SetToolFromType(this.globalToolTypeChoice); + + UpdateSelectionToolbarButtons(); + UpdateHistoryButtons(); + UpdateDocInfoInStatusBar(); + + this.activeDocumentWorkspace.ResumeLayout(); + this.activeDocumentWorkspace.PerformLayout(); + + this.activeDocumentWorkspace.FirstInputAfterGotFocus += + ActiveDocumentWorkspace_FirstInputAfterGotFocus; + } + + if (ActiveDocumentWorkspaceChanged != null) + { + ActiveDocumentWorkspaceChanged(this, EventArgs.Empty); + } + + UpdateStatusBarContextStatus(); + ResumeUpdateSnapObstacle(); + UpdateSnapObstacle(); + } + + public AppWorkspace() + { + SuspendLayout(); + + // initialize! + InitializeComponent(); + InitializeFloatingForms(); + + this.mainToolBarForm.ToolsControl.SetTools(DocumentWorkspace.ToolInfos); + this.mainToolBarForm.ToolsControl.ToolClicked += new ToolClickedEventHandler(this.MainToolBar_ToolClicked); + + this.toolBar.ToolChooserStrip.SetTools(DocumentWorkspace.ToolInfos); + this.toolBar.ToolChooserStrip.ToolClicked += new ToolClickedEventHandler(this.MainToolBar_ToolClicked); + + this.toolBar.AppWorkspace = this; + + // init the Widgets container + this.widgets = new WorkspaceWidgets(this); + this.widgets.ViewConfigStrip = this.toolBar.ViewConfigStrip; + this.widgets.CommonActionsStrip = this.toolBar.CommonActionsStrip; + this.widgets.ToolConfigStrip = this.toolBar.ToolConfigStrip; + this.widgets.ToolsForm = this.mainToolBarForm; + this.widgets.LayerForm = this.layerForm; + this.widgets.HistoryForm = this.historyForm; + this.widgets.ColorsForm = this.colorsForm; + this.widgets.StatusBarProgress = this.statusBar; + this.widgets.DocumentStrip = this.toolBar.DocumentStrip; + + // Load our settings and initialize the AppEnvironment + LoadSettings(); + + // hook into Environment *Changed events + AppEnvironment.PrimaryColorChanged += PrimaryColorChangedHandler; + AppEnvironment.SecondaryColorChanged += SecondaryColorChangedHandler; + AppEnvironment.ShapeDrawTypeChanged += ShapeDrawTypeChangedHandler; + AppEnvironment.GradientInfoChanged += GradientInfoChangedHandler; + AppEnvironment.ToleranceChanged += OnEnvironmentToleranceChanged; + AppEnvironment.AlphaBlendingChanged += AlphaBlendingChangedHandler; + AppEnvironment.FontInfo = this.toolBar.ToolConfigStrip.FontInfo; + AppEnvironment.TextAlignment = this.toolBar.ToolConfigStrip.FontAlignment; + AppEnvironment.AntiAliasingChanged += Environment_AntiAliasingChanged; + AppEnvironment.FontInfoChanged += Environment_FontInfoChanged; + AppEnvironment.FontSmoothingChanged += Environment_FontSmoothingChanged; + AppEnvironment.TextAlignmentChanged += Environment_TextAlignmentChanged; + AppEnvironment.PenInfoChanged += Environment_PenInfoChanged; + AppEnvironment.BrushInfoChanged += Environment_BrushInfoChanged; + AppEnvironment.ColorPickerClickBehaviorChanged += Environment_ColorPickerClickBehaviorChanged; + AppEnvironment.ResamplingAlgorithmChanged += Environment_ResamplingAlgorithmChanged; + AppEnvironment.SelectionCombineModeChanged += Environment_SelectionCombineModeChanged; + AppEnvironment.FloodModeChanged += Environment_FloodModeChanged; + AppEnvironment.SelectionDrawModeInfoChanged += Environment_SelectionDrawModeInfoChanged; + + this.toolBar.DocumentStrip.RelinquishFocus += RelinquishFocusHandler; + + this.toolBar.ToolConfigStrip.ToleranceChanged += OnToolBarToleranceChanged; + this.toolBar.ToolConfigStrip.FontAlignmentChanged += ToolConfigStrip_TextAlignmentChanged; + this.toolBar.ToolConfigStrip.FontInfoChanged += ToolConfigStrip_FontTextChanged; + this.toolBar.ToolConfigStrip.FontSmoothingChanged += ToolConfigStrip_FontSmoothingChanged; + this.toolBar.ToolConfigStrip.RelinquishFocus += RelinquishFocusHandler2; + + this.toolBar.CommonActionsStrip.RelinquishFocus += OnToolStripRelinquishFocus; + this.toolBar.CommonActionsStrip.MouseWheel += OnToolStripMouseWheel; + this.toolBar.CommonActionsStrip.ButtonClick += CommonActionsStrip_ButtonClick; + + this.toolBar.ViewConfigStrip.DrawGridChanged += ViewConfigStrip_DrawGridChanged; + this.toolBar.ViewConfigStrip.RulersEnabledChanged += ViewConfigStrip_RulersEnabledChanged; + this.toolBar.ViewConfigStrip.ZoomBasisChanged += ViewConfigStrip_ZoomBasisChanged; + this.toolBar.ViewConfigStrip.ZoomScaleChanged += ViewConfigStrip_ZoomScaleChanged; + this.toolBar.ViewConfigStrip.ZoomIn += ViewConfigStrip_ZoomIn; + this.toolBar.ViewConfigStrip.ZoomOut += ViewConfigStrip_ZoomOut; + this.toolBar.ViewConfigStrip.UnitsChanged += ViewConfigStrip_UnitsChanged; + this.toolBar.ViewConfigStrip.RelinquishFocus += OnToolStripRelinquishFocus; + this.toolBar.ViewConfigStrip.MouseWheel += OnToolStripMouseWheel; + + this.toolBar.ToolConfigStrip.BrushInfoChanged += DrawConfigStrip_BrushChanged; + this.toolBar.ToolConfigStrip.ShapeDrawTypeChanged += DrawConfigStrip_ShapeDrawTypeChanged; + this.toolBar.ToolConfigStrip.PenInfoChanged += DrawConfigStrip_PenChanged; + this.toolBar.ToolConfigStrip.GradientInfoChanged += ToolConfigStrip_GradientInfoChanged; + this.toolBar.ToolConfigStrip.AlphaBlendingChanged += OnDrawConfigStripAlphaBlendingChanged; + this.toolBar.ToolConfigStrip.AntiAliasingChanged += DrawConfigStrip_AntiAliasingChanged; + this.toolBar.ToolConfigStrip.RelinquishFocus += OnToolStripRelinquishFocus; + this.toolBar.ToolConfigStrip.ColorPickerClickBehaviorChanged += ToolConfigStrip_ColorPickerClickBehaviorChanged; + this.toolBar.ToolConfigStrip.ResamplingAlgorithmChanged += ToolConfigStrip_ResamplingAlgorithmChanged; + this.toolBar.ToolConfigStrip.SelectionCombineModeChanged += ToolConfigStrip_SelectionCombineModeChanged; + this.toolBar.ToolConfigStrip.FloodModeChanged += ToolConfigStrip_FloodModeChanged; + this.toolBar.ToolConfigStrip.SelectionDrawModeInfoChanged += ToolConfigStrip_SelectionDrawModeInfoChanged; + this.toolBar.ToolConfigStrip.SelectionDrawModeUnitsChanging += ToolConfigStrip_SelectionDrawModeUnitsChanging; + + this.toolBar.ToolConfigStrip.MouseWheel += OnToolStripMouseWheel; + + this.toolBar.DocumentStrip.RelinquishFocus += OnToolStripRelinquishFocus; + this.toolBar.DocumentStrip.DocumentClicked += DocumentStrip_DocumentTabClicked; + this.toolBar.DocumentStrip.DocumentListChanged += DocumentStrip_DocumentListChanged; + + // Synchronize + AppEnvironment.PerformAllChanged(); + + this.globalToolTypeChoice = this.defaultToolTypeChoice; + this.toolBar.ToolConfigStrip.ToolBarConfigItems = ToolBarConfigItems.None; + + this.layerForm.LayerControl.AppWorkspace = this; + + ResumeLayout(); + PerformLayout(); + } + + private void ToolConfigStrip_ColorPickerClickBehaviorChanged(object sender, EventArgs e) + { + this.appEnvironment.ColorPickerClickBehavior = this.widgets.ToolConfigStrip.ColorPickerClickBehavior; + } + + private void Environment_ColorPickerClickBehaviorChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.ColorPickerClickBehavior = this.appEnvironment.ColorPickerClickBehavior; + } + + private void ToolConfigStrip_ResamplingAlgorithmChanged(object sender, EventArgs e) + { + this.appEnvironment.ResamplingAlgorithm = this.widgets.ToolConfigStrip.ResamplingAlgorithm; + } + + private void Environment_ResamplingAlgorithmChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.ResamplingAlgorithm = this.appEnvironment.ResamplingAlgorithm; + } + + private void ToolConfigStrip_SelectionCombineModeChanged(object sender, EventArgs e) + { + this.appEnvironment.SelectionCombineMode = this.widgets.ToolConfigStrip.SelectionCombineMode; + } + + private void Environment_SelectionCombineModeChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.SelectionCombineMode = this.appEnvironment.SelectionCombineMode; + } + + private void ToolConfigStrip_FloodModeChanged(object sender, EventArgs e) + { + this.appEnvironment.FloodMode = this.widgets.ToolConfigStrip.FloodMode; + } + + private void Environment_FloodModeChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.FloodMode = this.appEnvironment.FloodMode; + } + + private void ToolConfigStrip_SelectionDrawModeInfoChanged(object sender, EventArgs e) + { + this.appEnvironment.SelectionDrawModeInfo = this.widgets.ToolConfigStrip.SelectionDrawModeInfo; + } + + private void Environment_SelectionDrawModeInfoChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.SelectionDrawModeInfo = this.appEnvironment.SelectionDrawModeInfo; + } + + private sealed class ToolConfigStrip_SelectionDrawModeUnitsChangeHandler + { + private ToolConfigStrip toolConfigStrip; + private Document activeDocument; + private MeasurementUnit oldUnits; + + public ToolConfigStrip_SelectionDrawModeUnitsChangeHandler(ToolConfigStrip toolConfigStrip, Document activeDocument) + { + this.toolConfigStrip = toolConfigStrip; + this.activeDocument = activeDocument; + this.oldUnits = toolConfigStrip.SelectionDrawModeInfo.Units; + } + + public void Initialize() + { + this.toolConfigStrip.SelectionDrawModeUnitsChanged += ToolConfigStrip_SelectionDrawModeUnitsChanged; + } + + public void ToolConfigStrip_SelectionDrawModeUnitsChanged(object sender, EventArgs e) + { + try + { + SelectionDrawModeInfo sdmi = this.toolConfigStrip.SelectionDrawModeInfo; + MeasurementUnit newUnits = sdmi.Units; + + double oldWidth = sdmi.Width; + double oldHeight = sdmi.Height; + + double newWidth; + double newHeight; + + newWidth = Document.ConvertMeasurement(oldWidth, this.oldUnits, this.activeDocument.DpuUnit, this.activeDocument.DpuX, newUnits); + newHeight = Document.ConvertMeasurement(oldHeight, this.oldUnits, this.activeDocument.DpuUnit, this.activeDocument.DpuY, newUnits); + + SelectionDrawModeInfo newSdmi = sdmi.CloneWithNewWidthAndHeight(newWidth, newHeight); + this.toolConfigStrip.SelectionDrawModeInfo = newSdmi; + } + + finally + { + this.toolConfigStrip.SelectionDrawModeUnitsChanged -= ToolConfigStrip_SelectionDrawModeUnitsChanged; + } + } + } + + private void ToolConfigStrip_SelectionDrawModeUnitsChanging(object sender, EventArgs e) + { + if (this.ActiveDocumentWorkspace != null && this.ActiveDocumentWorkspace.Document != null) + { + ToolConfigStrip_SelectionDrawModeUnitsChangeHandler tcsSdmuch = new ToolConfigStrip_SelectionDrawModeUnitsChangeHandler( + this.toolBar.ToolConfigStrip, this.ActiveDocumentWorkspace.Document); + + tcsSdmuch.Initialize(); + } + } + + private void DocumentStrip_DocumentListChanged(object sender, EventArgs e) + { + bool enableThem = (this.widgets.DocumentStrip.DocumentCount != 0); + + this.widgets.ToolsForm.Enabled = enableThem; + this.widgets.HistoryForm.Enabled = enableThem; + this.widgets.LayerForm.Enabled = enableThem; + this.widgets.ColorsForm.Enabled = enableThem; + this.widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Paste, enableThem); + + UpdateHistoryButtons(); + UpdateDocInfoInStatusBar(); + UpdateCursorInfoInStatusBar(0, 0); + } + + public void SaveSettings() + { + Settings.CurrentUser.SetBoolean(SettingNames.Rulers, this.globalRulersChoice); + Settings.CurrentUser.SetBoolean(SettingNames.DrawGrid, this.DrawGrid); + Settings.CurrentUser.SetString(SettingNames.DefaultToolTypeName, this.defaultToolTypeChoice.Name); + this.MostRecentFiles.SaveMruList(); + } + + private void LoadDefaultToolType() + { + string defaultToolTypeName = Settings.CurrentUser.GetString(SettingNames.DefaultToolTypeName, Tool.DefaultToolType.Name); + + ToolInfo[] tis = DocumentWorkspace.ToolInfos; + ToolInfo ti = Array.Find( + tis, + delegate(ToolInfo check) + { + if (string.Compare(defaultToolTypeName, check.ToolType.Name, StringComparison.InvariantCultureIgnoreCase) == 0) + { + return true; + } + else + { + return false; + } + }); + + if (ti == null) + { + this.defaultToolTypeChoice = Tool.DefaultToolType; + } + else + { + this.defaultToolTypeChoice = ti.ToolType; + } + } + + public void LoadSettings() + { + try + { + LoadDefaultToolType(); + + this.globalToolTypeChoice = this.defaultToolTypeChoice; + this.globalRulersChoice = Settings.CurrentUser.GetBoolean(SettingNames.Rulers, false); + this.DrawGrid = Settings.CurrentUser.GetBoolean(SettingNames.DrawGrid, false); + + this.appEnvironment = AppEnvironment.GetDefaultAppEnvironment(); + + this.widgets.ViewConfigStrip.Units = (MeasurementUnit)Enum.Parse(typeof(MeasurementUnit), + Settings.CurrentUser.GetString(SettingNames.Units, MeasurementUnit.Pixel.ToString()), true); + } + + catch (Exception) + { + this.appEnvironment = new AppEnvironment(); + this.appEnvironment.SetToDefaults(); + + try + { + Settings.CurrentUser.Delete( + new string[] + { + SettingNames.Rulers, + SettingNames.DrawGrid, + SettingNames.Units, + SettingNames.DefaultAppEnvironment, + SettingNames.DefaultToolTypeName, + }); + } + + catch (Exception) + { + } + } + + try + { + this.toolBar.ToolConfigStrip.LoadFromAppEnvironment(this.appEnvironment); + } + + catch (Exception) + { + this.appEnvironment = new AppEnvironment(); + this.appEnvironment.SetToDefaults(); + this.toolBar.ToolConfigStrip.LoadFromAppEnvironment(this.appEnvironment); + } + } + + protected override void OnLoad(EventArgs e) + { + if (this.ActiveDocumentWorkspace != null) + { + this.ActiveDocumentWorkspace.Select(); + } + + UpdateSnapObstacle(); + + base.OnLoad(e); + } + + public void RefreshTool() + { + Type toolType = activeDocumentWorkspace.GetToolType(); + Widgets.ToolsControl.SelectTool(toolType); + } + + private void GradientInfoChangedHandler(object sender, EventArgs e) + { + if (widgets.ToolConfigStrip.GradientInfo != AppEnvironment.GradientInfo) + { + widgets.ToolConfigStrip.GradientInfo = AppEnvironment.GradientInfo; + } + } + + private void ToolConfigStrip_GradientInfoChanged(object sender, EventArgs e) + { + if (AppEnvironment.GradientInfo != widgets.ToolConfigStrip.GradientInfo) + { + AppEnvironment.GradientInfo = widgets.ToolConfigStrip.GradientInfo; + } + } + + /// + /// Keeps the Environment's ShapeDrawType and the corresponding widget synchronized + /// + private void ShapeDrawTypeChangedHandler(object sender, EventArgs e) + { + if (widgets.ToolConfigStrip.ShapeDrawType != AppEnvironment.ShapeDrawType) + { + widgets.ToolConfigStrip.ShapeDrawType = AppEnvironment.ShapeDrawType; + } + } + + /// + /// Keeps the Environment's alpha blending value and the corresponding widget synchronized + /// + private void AlphaBlendingChangedHandler(object sender, EventArgs e) + { + if (widgets.ToolConfigStrip.AlphaBlending != AppEnvironment.AlphaBlending) + { + widgets.ToolConfigStrip.AlphaBlending = AppEnvironment.AlphaBlending; + } + } + + private void ColorDisplay_UserPrimaryAndSecondaryColorsChanged(object sender, EventArgs e) + { + // We need to make sure that we don't change which user color is selected (primary vs. secondary) + // To do this we choose the ordering based on which one is currently active (primary vs. secondary) + if (widgets.ColorsForm.WhichUserColor == WhichUserColor.Primary) + { + widgets.ColorsForm.SetColorControlsRedraw(false); + SecondaryColorChangedHandler(sender, e); + PrimaryColorChangedHandler(sender, e); + widgets.ColorsForm.SetColorControlsRedraw(true); + widgets.ColorsForm.WhichUserColor = WhichUserColor.Primary; + } + else //if (widgets.ColorsForm.WhichUserColor == WhichUserColor.Background) + { + widgets.ColorsForm.SetColorControlsRedraw(false); + PrimaryColorChangedHandler(sender, e); + SecondaryColorChangedHandler(sender, e); + widgets.ColorsForm.SetColorControlsRedraw(true); + widgets.ColorsForm.WhichUserColor = WhichUserColor.Secondary; + } + } + + private void PrimaryColorChangedHandler(object sender, EventArgs e) + { + if (sender == appEnvironment) + { + widgets.ColorsForm.UserPrimaryColor = AppEnvironment.PrimaryColor; + } + } + + private void OnToolBarToleranceChanged(object sender, EventArgs e) + { + AppEnvironment.Tolerance = widgets.ToolConfigStrip.Tolerance; + this.Focus(); + } + + private void OnEnvironmentToleranceChanged(object sender, EventArgs e) + { + widgets.ToolConfigStrip.Tolerance = AppEnvironment.Tolerance; + this.Focus(); + } + + private void SecondaryColorChangedHandler(object sender, EventArgs e) + { + if (sender == appEnvironment) + { + widgets.ColorsForm.UserSecondaryColor = AppEnvironment.SecondaryColor; + } + } + + private void RelinquishFocusHandler(object sender, EventArgs e) + { + this.Focus(); + } + + private void RelinquishFocusHandler2(object sender, EventArgs e) + { + if (this.activeDocumentWorkspace != null) + { + this.activeDocumentWorkspace.Focus(); + } + } + + private void ColorsForm_UserPrimaryColorChanged(object sender, ColorEventArgs e) + { + ColorsForm cf = (ColorsForm)sender; + AppEnvironment.PrimaryColor = e.Color; + } + + private void ColorsForm_UserSecondaryColorChanged(object sender, ColorEventArgs e) + { + ColorsForm cf = (ColorsForm)sender; + AppEnvironment.SecondaryColor = e.Color; + } + + /// + /// Handles the SelectedPathChanging event that is raised by the AppEnvironment. + /// + private void SelectedPathChangingHandler(object sender, EventArgs e) + { + } + + private void UpdateSelectionToolbarButtons() + { + if (ActiveDocumentWorkspace == null || ActiveDocumentWorkspace.Selection.IsEmpty) + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Cut, false); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Copy, false); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Deselect, false); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.CropToSelection, false); + } + else + { + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Cut, true); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Copy, true); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.Deselect, true); + widgets.CommonActionsStrip.SetButtonEnabled(CommonAction.CropToSelection, true); + } + } + + /// + /// Handles the SelectedPathChanged event that is raised by the AppEnvironment. + /// + private void SelectedPathChangedHandler(object sender, EventArgs e) + { + UpdateSelectionToolbarButtons(); + } + + private void ZoomChangedHandler(object sender, EventArgs e) + { + ScaleFactor sf = this.activeDocumentWorkspace.ScaleFactor; + this.toolBar.ViewConfigStrip.SuspendEvents(); + this.toolBar.ViewConfigStrip.ZoomBasis = this.activeDocumentWorkspace.ZoomBasis; + this.toolBar.ViewConfigStrip.ScaleFactor = sf; + this.toolBar.ViewConfigStrip.ResumeEvents(); + } + + private void InitializeComponent() + { + this.toolBar = new PdnToolBar(); + this.statusBar = new PdnStatusBar(); + this.workspacePanel = new Panel(); + this.workspacePanel.SuspendLayout(); + this.statusBar.SuspendLayout(); + this.SuspendLayout(); + // + // toolBar + // + this.toolBar.Name = "toolBar"; + this.toolBar.Dock = DockStyle.Top; + // + // statusBar + // + this.statusBar.Name = "statusBar"; + // + // workspacePanel + // + this.workspacePanel.Name = "workspacePanel"; + this.workspacePanel.Dock = DockStyle.Fill; + // + // AppWorkspace + // + this.Controls.Add(this.workspacePanel); + this.Controls.Add(this.statusBar); + this.Controls.Add(this.toolBar); + this.Name = "AppWorkspace"; + this.Size = new System.Drawing.Size(872, 640); + this.workspacePanel.ResumeLayout(false); + this.statusBar.ResumeLayout(false); + this.statusBar.PerformLayout(); + this.ResumeLayout(false); + } + + private void DocumentStrip_DocumentTabClicked( + object sender, + EventArgs> e) + { + switch (e.Data.Second) + { + case DocumentClickAction.Select: + this.ActiveDocumentWorkspace = e.Data.First; + break; + + case DocumentClickAction.Close: + CloseWorkspaceAction cwa = new CloseWorkspaceAction(e.Data.First); + PerformAction(cwa); + break; + + default: + throw new NotImplementedException("Code for DocumentClickAction." + e.Data.Second.ToString() + " not implemented"); + } + + Update(); + } + + private void OnToolStripMouseWheel(object sender, MouseEventArgs e) + { + if (this.activeDocumentWorkspace != null) + { + this.activeDocumentWorkspace.PerformMouseWheel((Control)sender, e); + } + } + + private void OnToolStripRelinquishFocus(object sender, EventArgs e) + { + if (this.activeDocumentWorkspace != null) + { + this.activeDocumentWorkspace.Focus(); + } + } + + // The Document* events are raised by the Document class, handled here, + // and relayed as necessary. For instance, for the DocumentMouse* events, + // these are all relayed to the active tool. + + private void DocumentMouseEnterHandler(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformMouseEnter(); + } + } + + private void DocumentMouseLeaveHandler(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformMouseLeave(); + } + } + + private void DocumentMouseUpHandler(object sender, MouseEventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformMouseUp(e); + } + } + + private void DocumentMouseDownHandler(object sender, MouseEventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformMouseDown(e); + } + } + + private void DocumentMouseMoveHandler(object sender, MouseEventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformMouseMove(e); + } + + UpdateCursorInfoInStatusBar(e.X, e.Y); + } + + private void DocumentClick(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformClick(); + } + } + + private void DocumentKeyPress(object sender, KeyPressEventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformKeyPress(e); + } + } + + private void DocumentKeyDown(object sender, KeyEventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformKeyDown(e); + } + } + + private void DocumenKeyUp(object sender, KeyEventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + ActiveDocumentWorkspace.Tool.PerformKeyUp(e); + } + } + + private void InitializeFloatingForms() + { + // MainToolBarForm + mainToolBarForm = new ToolsForm(); + mainToolBarForm.RelinquishFocus += RelinquishFocusHandler; + mainToolBarForm.ProcessCmdKeyEvent += OnToolFormProcessCmdKeyEvent; + + // LayerForm + layerForm = new LayerForm(); + layerForm.LayerControl.AppWorkspace = this; + layerForm.LayerControl.ClickedOnLayer += LayerControl_ClickedOnLayer; + layerForm.NewLayerButtonClick += LayerForm_NewLayerButtonClicked; + layerForm.DeleteLayerButtonClick += LayerForm_DeleteLayerButtonClicked; + layerForm.DuplicateLayerButtonClick += LayerForm_DuplicateLayerButtonClick; + layerForm.MergeLayerDownClick += new EventHandler(LayerForm_MergeLayerDownClick); + layerForm.MoveLayerUpButtonClick += LayerForm_MoveLayerUpButtonClicked; + layerForm.MoveLayerDownButtonClick += LayerForm_MoveLayerDownButtonClicked; + layerForm.PropertiesButtonClick += LayerForm_PropertiesButtonClick; + layerForm.RelinquishFocus += RelinquishFocusHandler; + layerForm.ProcessCmdKeyEvent += OnToolFormProcessCmdKeyEvent; + + // HistoryForm + historyForm = new HistoryForm(); + historyForm.RewindButtonClicked += HistoryForm_RewindButtonClicked; + historyForm.UndoButtonClicked += HistoryForm_UndoButtonClicked; + historyForm.RedoButtonClicked += HistoryForm_RedoButtonClicked; + historyForm.FastForwardButtonClicked += HistoryForm_FastForwardButtonClicked; + historyForm.RelinquishFocus += RelinquishFocusHandler; + historyForm.ProcessCmdKeyEvent += OnToolFormProcessCmdKeyEvent; + + // ColorsForm + colorsForm = new ColorsForm(); + colorsForm.PaletteCollection = new PaletteCollection(); + colorsForm.WhichUserColor = WhichUserColor.Primary; + colorsForm.UserPrimaryColorChanged += ColorsForm_UserPrimaryColorChanged; + colorsForm.UserSecondaryColorChanged += ColorsForm_UserSecondaryColorChanged; + colorsForm.RelinquishFocus += RelinquishFocusHandler; + colorsForm.ProcessCmdKeyEvent += OnToolFormProcessCmdKeyEvent; + } + + // TODO: put at correct scope + public event CmdKeysEventHandler ProcessCmdKeyEvent; + + private bool OnToolFormProcessCmdKeyEvent(object sender, ref Message msg, Keys keyData) + { + if (ProcessCmdKeyEvent != null) + { + return ProcessCmdKeyEvent(sender, ref msg, keyData); + } + else + { + return false; + } + } + + public void PerformActionAsync(AppWorkspaceAction performMe) + { + this.BeginInvoke(new Procedure(PerformAction), new object[] { performMe }); + } + + public void PerformAction(AppWorkspaceAction performMe) + { + Update(); + + using (new WaitCursorChanger(this)) + { + performMe.PerformAction(this); + } + + Update(); + } + + private void MainToolBar_ToolClicked(object sender, ToolClickedEventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.Focus(); + ActiveDocumentWorkspace.SetToolFromType(e.ToolType); + } + } + + private void ToolChangingHandler(object sender, EventArgs e) + { + UI.SuspendControlPainting(this.toolBar); + + if (ActiveDocumentWorkspace.Tool != null) + { + // unregister for events here (none at this time) + } + } + + private void ToolChangedHandler(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace.Tool != null) + { + this.widgets.ToolsControl.SelectTool(ActiveDocumentWorkspace.GetToolType(), false); + this.toolBar.ToolChooserStrip.SelectTool(ActiveDocumentWorkspace.GetToolType(), false); + this.toolBar.ToolConfigStrip.Visible = true; // HACK: see bug #2702 + this.toolBar.ToolConfigStrip.ToolBarConfigItems = ActiveDocumentWorkspace.Tool.ToolBarConfigItems; + this.globalToolTypeChoice = ActiveDocumentWorkspace.GetToolType(); + } + + UpdateStatusBarContextStatus(); + + UI.ResumeControlPainting(this.toolBar); + this.toolBar.Refresh(); + } + + private void DrawConfigStrip_AntiAliasingChanged(object sender, System.EventArgs e) + { + AppEnvironment.AntiAliasing = ((ToolConfigStrip)sender).AntiAliasing; + } + + private void DrawConfigStrip_PenChanged(object sender, System.EventArgs e) + { + AppEnvironment.PenInfo = this.toolBar.ToolConfigStrip.PenInfo; + } + + private void DrawConfigStrip_BrushChanged(object sender, System.EventArgs e) + { + AppEnvironment.BrushInfo = this.toolBar.ToolConfigStrip.BrushInfo; + } + + private void LayerControl_ClickedOnLayer(object sender, EventArgs ce) + { + if (ActiveDocumentWorkspace != null) + { + if (ce.Data != ActiveDocumentWorkspace.ActiveLayer) + { + ActiveDocumentWorkspace.ActiveLayer = ce.Data; + } + } + + this.RelinquishFocusHandler(sender, EventArgs.Empty); + } + + private void LayerForm_NewLayerButtonClicked(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.ExecuteFunction(new AddNewBlankLayerFunction()); + } + } + + private void LayerForm_DeleteLayerButtonClicked(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null && ActiveDocumentWorkspace.Document.Layers.Count > 1) + { + ActiveDocumentWorkspace.ExecuteFunction(new DeleteLayerFunction(ActiveDocumentWorkspace.ActiveLayerIndex)); + } + } + + private void LayerForm_MergeLayerDownClick(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + if (ActiveDocumentWorkspace != null && + ActiveDocumentWorkspace.ActiveLayerIndex > 0) + { + // TODO: keep this in sync with LayersMenu. not appropriate to refactor into an Action for a 'dot' release + int newLayerIndex = Utility.Clamp( + ActiveDocumentWorkspace.ActiveLayerIndex - 1, + 0, + ActiveDocumentWorkspace.Document.Layers.Count - 1); + + ActiveDocumentWorkspace.ExecuteFunction( + new MergeLayerDownFunction(ActiveDocumentWorkspace.ActiveLayerIndex)); + + ActiveDocumentWorkspace.ActiveLayerIndex = newLayerIndex; + } + } + } + + private void LayerForm_DuplicateLayerButtonClick(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.ExecuteFunction(new DuplicateLayerFunction(ActiveDocumentWorkspace.ActiveLayerIndex)); + } + } + + private void LayerForm_MoveLayerUpButtonClicked(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null && ActiveDocumentWorkspace.Document.Layers.Count >= 2) + { + ActiveDocumentWorkspace.PerformAction(new MoveActiveLayerUpAction()); + } + } + + private void LayerForm_MoveLayerDownButtonClicked(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null && ActiveDocumentWorkspace.Document.Layers.Count >= 2) + { + ActiveDocumentWorkspace.PerformAction(new MoveActiveLayerDownAction()); + } + } + + private void DrawConfigStrip_ShapeDrawTypeChanged(object sender, System.EventArgs e) + { + if (AppEnvironment.ShapeDrawType != widgets.ToolConfigStrip.ShapeDrawType) + { + AppEnvironment.ShapeDrawType = widgets.ToolConfigStrip.ShapeDrawType; + } + } + + private void HistoryForm_UndoButtonClicked(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new HistoryUndoAction()); + } + } + + private void HistoryForm_RedoButtonClicked(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new HistoryRedoAction()); + } + } + + private void ViewConfigStrip_RulersEnabledChanged(object sender, System.EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.RulersEnabled = this.toolBar.ViewConfigStrip.RulersEnabled; + } + } + + private void HistoryForm_RewindButtonClicked(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new HistoryRewindAction()); + } + } + + private void HistoryForm_FastForwardButtonClicked(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new HistoryFastForwardAction()); + } + } + + private void LayerForm_PropertiesButtonClick(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new OpenActiveLayerPropertiesAction()); + } + } + + private void Environment_FontInfoChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.FontInfo = AppEnvironment.FontInfo; + } + + private void Environment_FontSmoothingChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.FontSmoothing = AppEnvironment.FontSmoothing; + } + + private void Environment_TextAlignmentChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.FontAlignment = AppEnvironment.TextAlignment; + } + + private void Environment_PenInfoChanged(object sender, EventArgs e) + { + this.widgets.ToolConfigStrip.PenInfo = AppEnvironment.PenInfo; + } + + private void Environment_BrushInfoChanged(object sender, EventArgs e) + { + } + + private void ToolConfigStrip_TextAlignmentChanged(object sender, EventArgs e) + { + AppEnvironment.TextAlignment = widgets.ToolConfigStrip.FontAlignment; + } + + private void ToolConfigStrip_FontTextChanged(object sender, EventArgs e) + { + AppEnvironment.FontInfo = widgets.ToolConfigStrip.FontInfo; + } + + private void ToolConfigStrip_FontSmoothingChanged(object sender, EventArgs e) + { + AppEnvironment.FontSmoothing = widgets.ToolConfigStrip.FontSmoothing; + } + + protected override void OnResize(EventArgs e) + { + UpdateSnapObstacle(); + + base.OnResize(e); + + if (ParentForm != null && this.ActiveDocumentWorkspace != null) + { + if (ParentForm.WindowState == FormWindowState.Minimized) + { + this.ActiveDocumentWorkspace.EnableToolPulse = false; + } + else + { + this.ActiveDocumentWorkspace.EnableToolPulse = true; + } + } + } + + private void DocumentWorkspace_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e) + { + OnScroll(e); + } + + private void DocumentWorkspace_Layout(object sender, LayoutEventArgs e) + { + UpdateSnapObstacle(); + } + + private void ViewConfigStrip_ZoomBasisChanged(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + if (ActiveDocumentWorkspace.ZoomBasis != this.toolBar.ViewConfigStrip.ZoomBasis) + { + ActiveDocumentWorkspace.ZoomBasis = this.toolBar.ViewConfigStrip.ZoomBasis; + } + } + } + + private void DocumentWorkspace_ZoomBasisChanged(object sender, EventArgs e) + { + if (this.toolBar.ViewConfigStrip.ZoomBasis != this.ActiveDocumentWorkspace.ZoomBasis) + { + this.toolBar.ViewConfigStrip.ZoomBasis = this.ActiveDocumentWorkspace.ZoomBasis; + } + } + + private void ViewConfigStrip_ZoomScaleChanged(object sender, EventArgs e) + { + if (ActiveDocumentWorkspace != null) + { + if (this.toolBar.ViewConfigStrip.ZoomBasis == ZoomBasis.ScaleFactor) + { + this.activeDocumentWorkspace.ScaleFactor = this.toolBar.ViewConfigStrip.ScaleFactor; + } + } + } + + private void DocumentWorkspace_RulersEnabledChanged(object sender, EventArgs e) + { + this.toolBar.ViewConfigStrip.RulersEnabled = this.activeDocumentWorkspace.RulersEnabled; + this.globalRulersChoice = this.activeDocumentWorkspace.RulersEnabled; + PerformLayout(); + ActiveDocumentWorkspace.UpdateRulerSelectionTinting(); + + Settings.CurrentUser.SetBoolean(SettingNames.Rulers, this.activeDocumentWorkspace.RulersEnabled); + } + + private void Environment_AntiAliasingChanged(object sender, EventArgs e) + { + this.toolBar.ToolConfigStrip.AntiAliasing = AppEnvironment.AntiAliasing; + } + + private void ViewConfigStrip_ZoomIn(object sender, EventArgs e) + { + if (this.ActiveDocumentWorkspace != null) + { + this.ActiveDocumentWorkspace.ZoomIn(); + } + } + + private void ViewConfigStrip_ZoomOut(object sender, EventArgs e) + { + if (this.ActiveDocumentWorkspace != null) + { + this.ActiveDocumentWorkspace.ZoomOut(); + } + } + + private void ViewConfigStrip_UnitsChanged(object sender, EventArgs e) + { + if (this.toolBar.ViewConfigStrip.Units != MeasurementUnit.Pixel) + { + Settings.CurrentUser.SetString(SettingNames.LastNonPixelUnits, this.toolBar.ViewConfigStrip.Units.ToString()); + } + + if (this.activeDocumentWorkspace != null) + { + this.activeDocumentWorkspace.Units = this.Units; + } + + Settings.CurrentUser.SetString(SettingNames.Units, this.toolBar.ViewConfigStrip.Units.ToString()); + + UpdateDocInfoInStatusBar(); + this.statusBar.CursorInfoText = string.Empty; + + OnUnitsChanged(); + } + + private void OnDrawConfigStripAlphaBlendingChanged(object sender, EventArgs e) + { + if (AppEnvironment.AlphaBlending != widgets.ToolConfigStrip.AlphaBlending) + { + AppEnvironment.AlphaBlending = widgets.ToolConfigStrip.AlphaBlending; + } + } + + public event EventHandler StatusChanged; + private void OnStatusChanged() + { + if (StatusChanged != null) + { + StatusChanged(this, EventArgs.Empty); + } + } + + private void OnDocumentWorkspaceStatusChanged(object sender, EventArgs e) + { + OnStatusChanged(); + UpdateStatusBarContextStatus(); + } + + private void UpdateStatusBarContextStatus() + { + if (ActiveDocumentWorkspace != null) + { + this.statusBar.ContextStatusText = this.activeDocumentWorkspace.StatusText; + this.statusBar.ContextStatusImage = this.activeDocumentWorkspace.StatusIcon; + } + else + { + this.statusBar.ContextStatusText = string.Empty; + this.statusBar.ContextStatusImage = null; + } + } + + private static bool NullGetThumbnailImageAbort() + { + return false; + } + + /// + /// Creates a blank document of the given size in a new workspace, and activates that workspace. + /// + /// + /// If isInitial=true, then last workspace added by this method is kept track of, and if it is not modified by + /// the time the next workspace is added, then it will be removed. + /// + /// true if everything was successful, false if there wasn't enough memory + public bool CreateBlankDocumentInNewWorkspace(Size size, MeasurementUnit dpuUnit, double dpu, bool isInitial) + { + DocumentWorkspace dw1 = this.activeDocumentWorkspace; + if (dw1 != null) + { + dw1.SuspendRefresh(); + } + + try + { + Document untitled = new Document(size.Width, size.Height); + untitled.DpuUnit = dpuUnit; + untitled.DpuX = dpu; + untitled.DpuY = dpu; + + BitmapLayer bitmapLayer; + + try + { + using (new WaitCursorChanger(this)) + { + bitmapLayer = Layer.CreateBackgroundLayer(size.Width, size.Height); + } + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this, PdnResources.GetString("NewImageAction.Error.OutOfMemory")); + return false; + } + + using (new WaitCursorChanger(this)) + { + bool focused = false; + + if (this.ActiveDocumentWorkspace != null && this.ActiveDocumentWorkspace.Focused) + { + focused = true; + } + + untitled.Layers.Add(bitmapLayer); + + DocumentWorkspace dw = this.AddNewDocumentWorkspace(); + this.Widgets.DocumentStrip.LockDocumentWorkspaceDirtyValue(dw, false); + dw.SuspendRefresh(); + + try + { + dw.Document = untitled; + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this, PdnResources.GetString("NewImageAction.Error.OutOfMemory")); + RemoveDocumentWorkspace(dw); + untitled.Dispose(); + return false; + } + + dw.ActiveLayer = (Layer)dw.Document.Layers[0]; + + this.ActiveDocumentWorkspace = dw; + + dw.SetDocumentSaveOptions(null, null, null); + dw.History.ClearAll(); + dw.History.PushNewMemento( + new NullHistoryMemento(PdnResources.GetString("NewImageAction.Name"), + this.FileNewIcon)); + + dw.Document.Dirty = false; + dw.ResumeRefresh(); + + if (isInitial) + { + this.initialWorkspace = dw; + } + + if (focused) + { + this.ActiveDocumentWorkspace.Focus(); + } + + this.Widgets.DocumentStrip.UnlockDocumentWorkspaceDirtyValue(dw); + } + } + + finally + { + if (dw1 != null) + { + dw1.ResumeRefresh(); + } + } + + return true; + } + + public bool OpenFilesInNewWorkspace(string[] fileNames) + { + if (IsDisposed) + { + return false; + } + + bool result = true; + + foreach (string fileName in fileNames) + { + result &= OpenFileInNewWorkspace(fileName); + + if (!result) + { + break; + } + } + + return result; + } + + public bool OpenFileInNewWorkspace(string fileName) + { + return OpenFileInNewWorkspace(fileName, true); + } + + public bool OpenFileInNewWorkspace(string fileName, bool addToMruList) + { + if (fileName == null) + { + throw new ArgumentNullException("fileName"); + } + + if (fileName.Length == 0) + { + throw new ArgumentOutOfRangeException("fileName.Length == 0"); + } + + PdnBaseForm.UpdateAllForms(); + + FileType fileType; + Document document; + + this.widgets.StatusBarProgress.ResetProgressStatusBar(); + + ProgressEventHandler progressCallback = + delegate(object sender, ProgressEventArgs e) + { + this.widgets.StatusBarProgress.SetProgressStatusBar(e.Percent); + }; + + document = DocumentWorkspace.LoadDocument(this, fileName, out fileType, progressCallback); + this.widgets.StatusBarProgress.EraseProgressStatusBar(); + + if (document == null) + { + this.Cursor = Cursors.Default; + } + else + { + using (new WaitCursorChanger(this)) + { + DocumentWorkspace dw = AddNewDocumentWorkspace(); + Widgets.DocumentStrip.LockDocumentWorkspaceDirtyValue(dw, false); + + try + { + dw.Document = document; + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this, PdnResources.GetString("LoadImage.Error.OutOfMemoryException")); + RemoveDocumentWorkspace(dw); + document.Dispose(); + return false; + } + + dw.ActiveLayer = (Layer)document.Layers[0]; + + dw.SetDocumentSaveOptions(fileName, fileType, null); + + this.ActiveDocumentWorkspace = dw; + + dw.History.ClearAll(); + + dw.History.PushNewMemento( + new NullHistoryMemento( + PdnResources.GetString("OpenImageAction.Name"), + this.ImageFromDiskIcon)); + + document.Dirty = false; + Widgets.DocumentStrip.UnlockDocumentWorkspaceDirtyValue(dw); + } + + if (document != null) + { + ActiveDocumentWorkspace.ZoomBasis = ZoomBasis.FitToWindow; + } + + // add to MRU list + if (addToMruList) + { + ActiveDocumentWorkspace.AddToMruList(); + } + + this.toolBar.DocumentStrip.SyncThumbnails(); + + WarnAboutSavedWithVersion(document.SavedWithVersion); + } + + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.Focus(); + } + + return document != null; + } + + private void WarnAboutSavedWithVersion(Version savedWith) + { + // warn about version? + // 2.1 Build 1897 signifies when the file format changed and broke backwards compatibility (for saving) + // 2.1 Build 1921 signifies when MemoryBlock was upgraded to support 64-bits, which broke it again + // 2.1 Build 1924 upgraded to "unimportant ordering" for MemoryBlock serialization so we can to faster multiproc saves + // (in v2.5 we always save in order, although that doesn't change the file format's laxness) + // 2.5 Build 2105 changed the way PropertyItems are serialized + // 2.6 Build upgrade to .NET 2.0, does not appear to be compatible with 2.5 and earlier files as a result + if (savedWith < new Version(2, 6, 0)) + { + Version ourVersion = PdnInfo.GetVersion(); + Version ourVersion2 = new Version(ourVersion.Major, ourVersion.Minor); + Version ourVersion3 = new Version(ourVersion.Major, ourVersion.Minor, ourVersion.Build); + + int fields; + + if (savedWith < ourVersion2) + { + fields = 2; + } + else + { + fields = 3; + } + + string format = PdnResources.GetString("SavedWithOlderVersion.Format"); + string text = string.Format(format, savedWith.ToString(fields), ourVersion.ToString(fields)); + + // TODO: should we even bother to inform them? It is probably more annoying than not, + // especially since older versions will say "Hey this file is corrupt OR saved with a newer version" + //Utility.InfoBox(this, text); + } + } + + /// + /// Computes what the size of a new document should be. If the screen is in a normal, + /// wider-than-tall (landscape) mode then it returns 800x600. If the screen is in a + /// taller-than-wide (portrait) mode then it retusn 600x800. If the screen is square + /// then it returns 800x600. + /// + public Size GetNewDocumentSize() + { + PdnBaseForm findForm = this.FindForm() as PdnBaseForm; + + if (findForm != null && findForm.ScreenAspect < 1.0) + { + return new Size(600, 800); + } + else + { + return new Size(800, 600); + } + } + + private void CommonActionsStrip_ButtonClick(object sender, EventArgs e) + { + CommonAction ca = e.Data; + + switch (ca) + { + case CommonAction.New: + PerformAction(new NewImageAction()); + break; + + case CommonAction.Open: + PerformAction(new OpenFileAction()); + break; + + case CommonAction.Save: + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.DoSave(); + } + break; + + case CommonAction.Print: + if (ActiveDocumentWorkspace != null) + { + PrintAction pa = new PrintAction(); + ActiveDocumentWorkspace.PerformAction(pa); + } + break; + + case CommonAction.Cut: + if (ActiveDocumentWorkspace != null) + { + CutAction cutAction = new CutAction(); + cutAction.PerformAction(ActiveDocumentWorkspace); + } + + break; + + case CommonAction.Copy: + if (ActiveDocumentWorkspace != null) + { + CopyToClipboardAction ctca = new CopyToClipboardAction(ActiveDocumentWorkspace); + ctca.PerformAction(); + } + break; + + case CommonAction.Paste: + if (ActiveDocumentWorkspace != null) + { + PasteAction pa = new PasteAction(ActiveDocumentWorkspace); + pa.PerformAction(); + } + + break; + + case CommonAction.CropToSelection: + if (ActiveDocumentWorkspace != null) + { + using (new PushNullToolMode(ActiveDocumentWorkspace)) + { + ActiveDocumentWorkspace.ExecuteFunction(new CropToSelectionFunction()); + } + } + + break; + + case CommonAction.Deselect: + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.ExecuteFunction(new DeselectFunction()); + } + break; + + case CommonAction.Undo: + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new HistoryUndoAction()); + } + break; + + case CommonAction.Redo: + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.PerformAction(new HistoryRedoAction()); + } + break; + + default: + throw new InvalidEnumArgumentException("e.Data"); + } + + if (ActiveDocumentWorkspace != null) + { + ActiveDocumentWorkspace.Focus(); + } + } + } +} diff --git a/src/AppWorkspace.resx b/src/AppWorkspace.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/AppWorkspaceAction.cs b/src/AppWorkspaceAction.cs new file mode 100644 index 0000000..377dafc --- /dev/null +++ b/src/AppWorkspaceAction.cs @@ -0,0 +1,23 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal abstract class AppWorkspaceAction + { + public abstract void PerformAction(AppWorkspace appWorkspace); + + public AppWorkspaceAction() + { + SystemLayer.Tracing.LogFeature("AWAction(" + GetType().Name + ")"); + } + } +} diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs new file mode 100644 index 0000000..237a426 --- /dev/null +++ b/src/AssemblyInfo.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] diff --git a/src/Base/AssemblyInfo.cs b/src/Base/AssemblyInfo.cs new file mode 100644 index 0000000..5da26d2 --- /dev/null +++ b/src/Base/AssemblyInfo.cs @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Paint.NET Base")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Always)] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: ComVisibleAttribute(false)] \ No newline at end of file diff --git a/src/Base/Base.csproj b/src/Base/Base.csproj new file mode 100644 index 0000000..02d18fd --- /dev/null +++ b/src/Base/Base.csproj @@ -0,0 +1,130 @@ + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Library + Properties + PaintDotNet.Base + PaintDotNet.Base + + + 2.0 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 268435456 + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + true + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + 268435456 + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + Code + + + Code + + + + Code + + + Code + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Base/BoxedConstants.cs b/src/Base/BoxedConstants.cs new file mode 100644 index 0000000..d24c0f0 --- /dev/null +++ b/src/Base/BoxedConstants.cs @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Provides access to a cached group of boxed, commonly used constants. + /// This helps to avoid boxing overhead, much of which consists of transferring + /// the item to the heap. Unboxing, on the other hand, is quite cheap. + /// This is commonly used to pass index values to worker threads. + /// + public sealed class BoxedConstants + { + private static object[] boxedInt32 = new object[1024]; + private static object boxedTrue = (object)true; + private static object boxedFalse = (object)false; + + public static object GetInt32(int value) + { + if (value >= boxedInt32.Length || value < 0) + { + return (object)value; + } + + if (boxedInt32[value] == null) + { + boxedInt32[value] = (object)value; + } + + return boxedInt32[value]; + } + + public static object GetBoolean(bool value) + { + return value ? boxedTrue : boxedFalse; + } + + static BoxedConstants() + { + } + + private BoxedConstants() + { + } + } +} diff --git a/src/Base/CallbackOnDispose.cs b/src/Base/CallbackOnDispose.cs new file mode 100644 index 0000000..099f98a --- /dev/null +++ b/src/Base/CallbackOnDispose.cs @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Threading; + +namespace PaintDotNet +{ + public sealed class CallbackOnDispose + : IDisposable + { + private Procedure callback; + + public CallbackOnDispose(Procedure callback) + { + this.callback = callback; + } + + ~CallbackOnDispose() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + Procedure callback2 = Interlocked.Exchange(ref this.callback, null); + + if (callback2 != null) + { + callback2(); + } + } + } +} diff --git a/src/Base/DeferredFormatter.cs b/src/Base/DeferredFormatter.cs new file mode 100644 index 0000000..8d0c919 --- /dev/null +++ b/src/Base/DeferredFormatter.cs @@ -0,0 +1,132 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.IO; + +namespace PaintDotNet +{ + public sealed class DeferredFormatter + { + private ArrayList objects = ArrayList.Synchronized(new ArrayList()); + private bool used = false; + private object context; + private long totalSize; + private long totalReportedBytes; + private bool useCompression; + private object lockObject = new object(); + + public object Context + { + get + { + return this.context; + } + } + + public bool UseCompression + { + get + { + return this.useCompression; + } + } + + public DeferredFormatter() + : this(false, null) + { + } + + public DeferredFormatter(bool useCompression, object context) + { + this.useCompression = useCompression; + this.context = context; + } + + public void AddDeferredObject(IDeferredSerializable theObject, long objectByteSize) + { + if (used) + { + throw new InvalidOperationException("object already finished serialization"); + } + + this.totalSize += objectByteSize; + objects.Add(theObject); + } + + public event EventHandler ReportedBytesChanged; + private void OnReportedBytesChanged() + { + if (ReportedBytesChanged != null) + { + ReportedBytesChanged(this, EventArgs.Empty); + } + } + + public long ReportedBytes + { + get + { + lock (lockObject) + { + return totalReportedBytes; + } + } + } + + /// + /// Reports that bytes have been successfully been written. + /// + /// + public void ReportBytes(long bytes) + { + lock (lockObject) + { + totalReportedBytes += bytes; + } + + OnReportedBytesChanged(); + } + + public void FinishSerialization(Stream output) + { + if (used) + { + throw new InvalidOperationException("object already finished deserialization or serialization"); + } + + used = true; + + foreach (IDeferredSerializable obj in this.objects) + { + obj.FinishSerialization(output, this); + } + + this.objects = null; + } + + public void FinishDeserialization(Stream input) + { + if (used) + { + throw new InvalidOperationException("object already finished deserialization or serialization"); + } + + used = true; + + foreach (IDeferredSerializable obj in this.objects) + { + obj.FinishDeserialization(input, this); + } + + this.objects = null; + } + } +} diff --git a/src/Base/Do.cs b/src/Base/Do.cs new file mode 100644 index 0000000..f05f798 --- /dev/null +++ b/src/Base/Do.cs @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public static class Do + { + public static void Test( + T value, + Function testFn, + Procedure ifTrueFn, + Procedure ifFalseFn) + { + (testFn(value) ? ifTrueFn : ifFalseFn)(value); + } + + public static void GenerateTest( + Function generate, + Function test, + Procedure ifTrue, + Procedure ifFalse) + { + Test(generate(), test, ifTrue, ifFalse); + } + + public static bool TryBool(Procedure actionProcedure) + { + try + { + actionProcedure(); + return true; + } + + catch (Exception) + { + return false; + } + } + + public static T TryCatch( + Function actionFunction, + Function catchClause) + { + T returnVal; + + try + { + returnVal = actionFunction(); + } + + catch (Exception ex) + { + returnVal = catchClause(ex); + } + + return returnVal; + } + } +} diff --git a/src/Base/EventArgs`1.cs b/src/Base/EventArgs`1.cs new file mode 100644 index 0000000..3754bde --- /dev/null +++ b/src/Base/EventArgs`1.cs @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public class EventArgs + : EventArgs + { + private T data; + public T Data + { + get + { + return data; + } + } + + public EventArgs(T data) + { + this.data = data; + } + } +} diff --git a/src/Base/Function.cs b/src/Base/Function.cs new file mode 100644 index 0000000..418d29b --- /dev/null +++ b/src/Base/Function.cs @@ -0,0 +1,17 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate R Function(); + public delegate R Function(T t); + public delegate R Function(T t, U u); +} diff --git a/src/Base/ICancelable.cs b/src/Base/ICancelable.cs new file mode 100644 index 0000000..73423c1 --- /dev/null +++ b/src/Base/ICancelable.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface ICancelable + { + void RequestCancel(); + } +} + \ No newline at end of file diff --git a/src/Base/ICloneable`1.cs b/src/Base/ICloneable`1.cs new file mode 100644 index 0000000..e885c7d --- /dev/null +++ b/src/Base/ICloneable`1.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface ICloneable + : ICloneable + { + new T Clone(); + } +} diff --git a/src/Base/IDeferredSerializable.cs b/src/Base/IDeferredSerializable.cs new file mode 100644 index 0000000..09d48ba --- /dev/null +++ b/src/Base/IDeferredSerializable.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + public interface IDeferredSerializable + : ISerializable + { + void FinishSerialization(Stream output, DeferredFormatter context); + void FinishDeserialization(Stream input, DeferredFormatter context); + } +} diff --git a/src/Base/IDisposedEvent.cs b/src/Base/IDisposedEvent.cs new file mode 100644 index 0000000..cf00fca --- /dev/null +++ b/src/Base/IDisposedEvent.cs @@ -0,0 +1,18 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface IDisposedEvent + { + event EventHandler Disposed; + } +} diff --git a/src/Base/IOEventArgs.cs b/src/Base/IOEventArgs.cs new file mode 100644 index 0000000..ff97637 --- /dev/null +++ b/src/Base/IOEventArgs.cs @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; + +namespace PaintDotNet +{ + public sealed class IOEventArgs + : EventArgs + { + /// + /// Whether we are reporting a Read or Write operation. + /// + private IOOperationType ioOperationType; + public IOOperationType IOOperationType + { + get + { + return ioOperationType; + } + } + + /// + /// The offset within the file that the operation is to begin, or has finished, at. + /// + private long position; + public long Position + { + get + { + return position; + } + } + + /// + /// The number of bytes that were read or written. + /// + private int count; + public int Count + { + get + { + return count; + } + } + + public IOEventArgs(IOOperationType ioOperationType, long position, int count) + { + this.ioOperationType = ioOperationType; + this.position = position; + this.count = count; + } + } +} diff --git a/src/Base/IOEventHandler.cs b/src/Base/IOEventHandler.cs new file mode 100644 index 0000000..a97d048 --- /dev/null +++ b/src/Base/IOEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate void IOEventHandler(object sender, IOEventArgs e); +} diff --git a/src/Base/IOOperationType.cs b/src/Base/IOOperationType.cs new file mode 100644 index 0000000..ed8456d --- /dev/null +++ b/src/Base/IOOperationType.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum IOOperationType + { + Read, + Write + } +} diff --git a/src/Base/IProgressEvents.cs b/src/Base/IProgressEvents.cs new file mode 100644 index 0000000..1a51fde --- /dev/null +++ b/src/Base/IProgressEvents.cs @@ -0,0 +1,99 @@ +//////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public interface IProgressEvents + { + /// + /// This method is called at the very beginning of the operation. + /// + /// + /// If the operation has a modal window open, this represents it. Otherwise, this is null and you should + /// use your own current modal window. + /// + /// You must call this method after initializing any UI you want to display. + /// If you will not be displaying UI, you must call this anyway. + /// + /// An object that can be used to cancel the operation at any time after callWhenUIShown is invoked, + /// and any time before EndOperation() is called. + /// + /// + /// This method must not return until EndOperation() is called. Note that EndOperation() will + /// be called from a separate thread. + /// + void BeginOperation(IWin32Window owner, EventHandler callWhenUIShown, ICancelable cancelSink); + + /// + /// Called to report the total number of work items that are part of the operation. + /// + /// The total number of work items that are part of the operation. + void SetItemCount(int itemCount); + + /// + /// Called to change which work item of the operation is in progress. + /// + /// + /// + /// This method is called after BeginOperation() but before BeginItem(), + /// or after EndItem() but before BeginItem(). + /// + void SetItemOrdinal(int itemOrdinal); + + /// + /// Called to report information about the current work item. + /// + /// Operation-dependent information about the current work item. + void SetItemInfo(TItemInfo itemInfo); + + /// + /// Reports the total number of work units that are involved in completing the current work item. + /// + /// The total number of work units that comprise the current work item. + void SetItemWorkTotal(long totalWork); + + /// + /// Called when a work item is starting. + /// + /// + void BeginItem(); + + /// + /// Reports the total amount of progress for the current work item. + /// + /// The total number of work units that have completed for the current work item. + void SetItemWorkProgress(long totalProgress); + + /// + /// Called when there is an error while completing the current work item. + /// + /// The exception that was encountered while completing the current work item. + /// You must return a value from the ItemFailedUserChoice enumeration. + WorkItemFailureAction ReportItemFailure(Exception ex); + + /// + /// Called after a work item is finished. + /// + void EndItem(WorkItemResult result); + + /// + /// Called after the operation is complete. + /// + /// Indicates whether the operation finished, or was cancelled. + /// + /// Even if the operation finished, individual work items may not have succeeded. + /// You will need to track the data passed to to EndItem() ReportItemError() to be able to monitor this. + /// You must close any UI shown during BeginOperation(), and then return from that method. + /// + void EndOperation(OperationResult result); + } +} diff --git a/src/Base/ISimpleCollection.cs b/src/Base/ISimpleCollection.cs new file mode 100644 index 0000000..5a80f50 --- /dev/null +++ b/src/Base/ISimpleCollection.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; + +namespace PaintDotNet +{ + public interface ISimpleCollection + { + V Get(K key); + void Set(K key, V value); + } +} diff --git a/src/Base/ImageResource.cs b/src/Base/ImageResource.cs new file mode 100644 index 0000000..ee2e428 --- /dev/null +++ b/src/Base/ImageResource.cs @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public abstract class ImageResource + : Resource + { + private sealed class FromImageResource + : ImageResource + { + protected override Image Load() + { + return (Image)Reference.Clone(); + } + + public FromImageResource(Image image) + : base(image) + { + } + } + + public static ImageResource FromImage(Image image) + { + if (image == null) + { + throw new ArgumentNullException("image"); + } + + ImageResource resource = new FromImageResource(image); + return resource; + } + + protected ImageResource() + : base() + { + } + + protected ImageResource(Image image) + : base(image) + { + } + } +} diff --git a/src/Base/OperationResult.cs b/src/Base/OperationResult.cs new file mode 100644 index 0000000..296e280 --- /dev/null +++ b/src/Base/OperationResult.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum OperationResult + { + Finished, + Canceled + } +} diff --git a/src/Base/Pair.cs b/src/Base/Pair.cs new file mode 100644 index 0000000..8d58e67 --- /dev/null +++ b/src/Base/Pair.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public static class Pair + { + public static Pair Create(T first, U second) + { + return new Pair(first, second); + } + } +} diff --git a/src/Base/Pair`2.cs b/src/Base/Pair`2.cs new file mode 100644 index 0000000..1d1dd48 --- /dev/null +++ b/src/Base/Pair`2.cs @@ -0,0 +1,112 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + [Serializable] + public struct Pair + { + private T first; + private U second; + + public T First + { + get + { + return this.first; + } + } + + public U Second + { + get + { + return this.second; + } + } + + public override int GetHashCode() + { + int firstHash; + int secondHash; + + if (object.ReferenceEquals(this.first, null)) + { + firstHash = 0; + } + else + { + firstHash = this.first.GetHashCode(); + } + + if (object.ReferenceEquals(this.second, null)) + { + secondHash = 0; + } + else + { + secondHash = this.second.GetHashCode(); + } + + return firstHash ^ secondHash; + } + + public override bool Equals(object obj) + { + return ((obj != null) && (obj is Pair) && (this == (Pair)obj)); + } + + public static bool operator ==(Pair lhs, Pair rhs) + { + bool firstEqual; + bool secondEqual; + + if (object.ReferenceEquals(lhs.First, null) && object.ReferenceEquals(rhs.First, null)) + { + firstEqual = true; + } + else if (object.ReferenceEquals(lhs.First, null) || object.ReferenceEquals(rhs.First, null)) + { + firstEqual = false; + } + else + { + firstEqual = lhs.First.Equals(rhs.First); + } + + if (object.ReferenceEquals(lhs.Second, null) && object.ReferenceEquals(rhs.Second, null)) + { + secondEqual = true; + } + else if (object.ReferenceEquals(lhs.Second, null) || object.ReferenceEquals(rhs.Second, null)) + { + secondEqual = false; + } + else + { + secondEqual = lhs.Second.Equals(rhs.Second); + } + + return firstEqual && secondEqual; + } + + public static bool operator !=(Pair lhs, Pair rhs) + { + return !(lhs == rhs); + } + + public Pair(T first, U second) + { + this.first = first; + this.second = second; + } + } +} diff --git a/src/Base/Procedure.cs b/src/Base/Procedure.cs new file mode 100644 index 0000000..601de1d --- /dev/null +++ b/src/Base/Procedure.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate void Procedure(); + public delegate void Procedure(T t); + public delegate void Procedure(T t, U u); + public delegate void Procedure(T t, U u, V v); + public delegate void Procedure(T t, U u, V v, W w); +} diff --git a/src/Base/PropertySystem/BooleanProperty.cs b/src/Base/PropertySystem/BooleanProperty.cs new file mode 100644 index 0000000..5bb461b --- /dev/null +++ b/src/Base/PropertySystem/BooleanProperty.cs @@ -0,0 +1,49 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public class BooleanProperty + : ScalarProperty + { + public BooleanProperty(object name) + : this(name, false) + { + } + + public BooleanProperty(object name, bool defaultValue) + : this(name, defaultValue, false) + { + } + + public BooleanProperty(object name, bool defaultValue, bool readOnly) + : this(name, defaultValue, readOnly, DefaultValueValidationFailureResult) + { + } + + public BooleanProperty(object name, bool defaultValue, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValue, false, true, readOnly, vvfResult) + { + } + + private BooleanProperty(BooleanProperty copyMe, BooleanProperty sentinelNotUsed) + : base(copyMe, sentinelNotUsed) + { + } + + public override Property Clone() + { + return new BooleanProperty(this, this); + } + } +} diff --git a/src/Base/PropertySystem/DoubleProperty.cs b/src/Base/PropertySystem/DoubleProperty.cs new file mode 100644 index 0000000..82987a5 --- /dev/null +++ b/src/Base/PropertySystem/DoubleProperty.cs @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + public sealed class DoubleProperty + : ScalarProperty + { + public DoubleProperty(object name) + : this(name, 0) + { + } + + public DoubleProperty(object name, double defaultValue) + : this(name, defaultValue, double.MinValue, double.MaxValue) + { + } + + public DoubleProperty(object name, double defaultValue, double minValue, double maxValue) + : this(name, defaultValue, minValue, maxValue, false) + { + } + + public DoubleProperty(object name, double defaultValue, double minValue, double maxValue, bool readOnly) + : this(name, defaultValue, minValue, maxValue, readOnly, DefaultValueValidationFailureResult) + { + } + + public DoubleProperty(object name, double defaultValue, double minValue, double maxValue, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValue, minValue, maxValue, readOnly, vvfResult) + { + } + + private DoubleProperty(DoubleProperty copyMe, DoubleProperty sentinelNotUsed) + : base(copyMe, sentinelNotUsed) + { + } + + public override Property Clone() + { + return new DoubleProperty(this, this); + } + } +} \ No newline at end of file diff --git a/src/Base/PropertySystem/DoubleVectorProperty.cs b/src/Base/PropertySystem/DoubleVectorProperty.cs new file mode 100644 index 0000000..ed79691 --- /dev/null +++ b/src/Base/PropertySystem/DoubleVectorProperty.cs @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + public sealed class DoubleVectorProperty + : VectorProperty + { + public DoubleVectorProperty(object name) + : this(name, Pair.Create(0.0, 0.0)) + { + } + + public DoubleVectorProperty(object name, Pair defaultValues) + : this(name, defaultValues, Pair.Create(double.MinValue, double.MinValue), Pair.Create(double.MaxValue, double.MaxValue)) + { + } + + public DoubleVectorProperty(object name, Pair defaultValues, Pair minValues, Pair maxValues) + : this(name, defaultValues, minValues, maxValues, false) + { + } + + public DoubleVectorProperty(object name, Pair defaultValues, Pair minValues, Pair maxValues, bool readOnly) + : this(name, defaultValues, minValues, maxValues, readOnly, DefaultValueValidationFailureResult) + { + } + + public DoubleVectorProperty(object name, Pair defaultValues, Pair minValues, Pair maxValues, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValues, minValues, maxValues, readOnly, vvfResult) + { + } + + private DoubleVectorProperty(DoubleVectorProperty cloneMe, DoubleVectorProperty sentinelNotUsed) + : base(cloneMe, sentinelNotUsed) + { + } + + public override Property Clone() + { + return new DoubleVectorProperty(this, this); + } + } +} diff --git a/src/Base/PropertySystem/IPropertyRef.cs b/src/Base/PropertySystem/IPropertyRef.cs new file mode 100644 index 0000000..03e63d1 --- /dev/null +++ b/src/Base/PropertySystem/IPropertyRef.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + public interface IPropertyRef + { + Property Property + { + get; + } + } +} diff --git a/src/Base/PropertySystem/ImageProperty.cs b/src/Base/PropertySystem/ImageProperty.cs new file mode 100644 index 0000000..ac42f48 --- /dev/null +++ b/src/Base/PropertySystem/ImageProperty.cs @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Drawing; + +namespace PaintDotNet.PropertySystem +{ + public sealed class ImageProperty + : Property + { + public ImageProperty(object propertyName) + : this(propertyName, null, false) + { + } + + public ImageProperty(object propertyName, ImageResource image) + : this(propertyName, image, false) + { + } + + public ImageProperty(object propertyName, ImageResource image, bool readOnly) + : base(propertyName, image, readOnly, ValueValidationFailureResult.Ignore) + { + } + + private ImageProperty(ImageProperty cloneMe, ImageProperty sentinelNotUsed) + : base(cloneMe, sentinelNotUsed) + { + } + + public override Property Clone() + { + return new ImageProperty(this, this); + } + + protected override bool ValidateNewValueT(ImageResource newValue) + { + return true; + } + + protected override ImageResource OnClampNewValueT(ImageResource newValue) + { + return newValue; + } + } +} diff --git a/src/Base/PropertySystem/Int32Property.cs b/src/Base/PropertySystem/Int32Property.cs new file mode 100644 index 0000000..bb9be1a --- /dev/null +++ b/src/Base/PropertySystem/Int32Property.cs @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public sealed class Int32Property + : ScalarProperty + { + public Int32Property(object name) + : this(name, 0) + { + } + + public Int32Property(object name, int defaultValue) + : this(name, defaultValue, int.MinValue, int.MaxValue) + { + } + + public Int32Property(object name, int defaultValue, int minValue, int maxValue) + : this(name, defaultValue, minValue, maxValue, false) + { + } + + public Int32Property(object name, int defaultValue, int minValue, int maxValue, bool readOnly) + : this(name, defaultValue, minValue, maxValue, readOnly, DefaultValueValidationFailureResult) + { + } + + public Int32Property(object name, int defaultValue, int minValue, int maxValue, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValue, minValue, maxValue, readOnly, vvfResult) + { + } + + private Int32Property(Int32Property copyMe, Int32Property sentinelNotUsed) + : base(copyMe, sentinelNotUsed) + { + } + + protected override int OnCoerceValueT(object newValue) + { + if (newValue is double) + { + return (int)(double)newValue; + } + else if (newValue is float) + { + return (int)(float)newValue; + } + + return base.OnCoerceValueT(newValue); + } + + public override Property Clone() + { + return new Int32Property(this, this); + } + } +} \ No newline at end of file diff --git a/src/Base/PropertySystem/LinkValuesBasedOnBooleanRule`2.cs b/src/Base/PropertySystem/LinkValuesBasedOnBooleanRule`2.cs new file mode 100644 index 0000000..bac72a4 --- /dev/null +++ b/src/Base/PropertySystem/LinkValuesBasedOnBooleanRule`2.cs @@ -0,0 +1,161 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; + +namespace PaintDotNet.PropertySystem +{ + /// + /// Defines a rule that binds the ReadOnly flag of property A to the value of boolean property B. + /// A and B must be different properties. + /// + public sealed class LinkValuesBasedOnBooleanRule + : PropertyCollectionRule + where TProperty : ScalarProperty + where TValue : struct, IComparable + { + private string[] targetPropertyNames; + private string sourcePropertyName; + private bool inverse; + private string lastChangedPropertyName; + + // When inverse=false, the target properties will be linked when sourceProperty == true + + public LinkValuesBasedOnBooleanRule(IEnumerable targetProperties, BooleanProperty sourceProperty, bool inverse) + : this(new List(targetProperties).ConvertAll(p => p.Name).ToArray(), sourceProperty.Name, inverse) + { + } + + public LinkValuesBasedOnBooleanRule(object[] targetPropertyNames, object sourcePropertyName, bool inverse) + { + if (targetPropertyNames.Length < 2) + { + throw new ArgumentException("Must have at least 2 items in targetPropertyNames"); + } + + this.targetPropertyNames = new string[targetPropertyNames.Length]; + + for (int i = 0; i < this.targetPropertyNames.Length; ++i) + { + this.targetPropertyNames[i] = targetPropertyNames[i].ToString(); + } + + this.sourcePropertyName = sourcePropertyName.ToString(); + this.inverse = inverse; + this.lastChangedPropertyName = this.targetPropertyNames[0]; + } + + protected override void OnInitialized() + { + // Verify the sourcePropertyName is not in targetPropertyNames + if (-1 != Array.IndexOf(this.targetPropertyNames, this.sourcePropertyName)) + { + throw new ArgumentException("sourceProperty may not be in the list of targetProperties"); + } + + // Verify that the intersection of this rule's targetProperty and that of all the other Link*'d rules is empty + Set ourTargetPropertyNamesSet = null; + + foreach (PropertyCollectionRule rule in Owner.Rules) + { + var asLinkRule = rule as LinkValuesBasedOnBooleanRule; + + if (asLinkRule != null && !object.ReferenceEquals(this, asLinkRule)) + { + if (ourTargetPropertyNamesSet == null) + { + ourTargetPropertyNamesSet = new Set(this.targetPropertyNames); + } + + Set theirTargetPropertyNamesSet = new Set(asLinkRule.targetPropertyNames); + + Set intersection = Set.Intersect(ourTargetPropertyNamesSet, theirTargetPropertyNamesSet); + + if (intersection.Count != 0) + { + throw new ArgumentException("Cannot assign a property to be linked with more than one LinkValuesBasedOnBooleanRule instance"); + } + } + } + + // Verify every property is of type TProperty + // That all the ranges are the same + // Sign up for events + TProperty firstProperty = (TProperty)this.Owner[this.targetPropertyNames[0]]; + + foreach (string targetPropertyName in this.targetPropertyNames) + { + TProperty targetProperty = (TProperty)this.Owner[targetPropertyName]; + + if (!(targetProperty is TProperty)) + { + throw new ArgumentException("All of the target properties must be of type TProperty (" + typeof(TProperty).FullName + ")"); + } + + if (!ScalarProperty.IsEqualTo(targetProperty.MinValue, firstProperty.MinValue) || + !ScalarProperty.IsEqualTo(targetProperty.MaxValue, firstProperty.MaxValue)) + { + throw new ArgumentException("All of the target properties must have the same min/max range"); + } + + targetProperty.ValueChanged += new EventHandler(TargetProperty_ValueChanged); + } + + BooleanProperty sourceProperty = (BooleanProperty)this.Owner[sourcePropertyName]; + + sourceProperty.ValueChanged += new EventHandler(SourceProperty_ValueChanged); + + Sync(); + } + + private void TargetProperty_ValueChanged(object sender, EventArgs e) + { + this.lastChangedPropertyName = ((TProperty)sender).Name; + Sync(); + } + + private void SourceProperty_ValueChanged(object sender, EventArgs e) + { + Sync(); + } + + private void Sync() + { + BooleanProperty sourceProperty = (BooleanProperty)Owner[this.sourcePropertyName]; + + if (sourceProperty.Value ^ this.inverse) + { + TProperty lastChangedProperty = (TProperty)Owner[this.lastChangedPropertyName]; + TValue newValue = lastChangedProperty.Value; + + foreach (string targetPropertyName in this.targetPropertyNames) + { + TProperty targetProperty = (TProperty)Owner[targetPropertyName]; + + if (targetProperty.ReadOnly) + { + targetProperty.ReadOnly = false; + targetProperty.Value = newValue; + targetProperty.ReadOnly = true; + } + else + { + targetProperty.Value = newValue; + } + } + } + } + + public override PropertyCollectionRule Clone() + { + return new LinkValuesBasedOnBooleanRule(this.targetPropertyNames, this.sourcePropertyName, this.inverse); + } + } +} \ No newline at end of file diff --git a/src/Base/PropertySystem/Property.cs b/src/Base/PropertySystem/Property.cs new file mode 100644 index 0000000..1252981 --- /dev/null +++ b/src/Base/PropertySystem/Property.cs @@ -0,0 +1,348 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace PaintDotNet.PropertySystem +{ + public abstract class Property + : ICloneable + { + public static ValueValidationFailureResult DefaultValueValidationFailureResult + { + get + { +#if DEBUG + return ValueValidationFailureResult.ThrowException; +#else + return ValueValidationFailureResult.Clamp; +#endif + } + } + + public static Property Create(Type valueType, object name) + { + return Create(valueType, name, null); + } + + public static Property Create(Type valueType, object name, object defaultValue) + { + // TODO: find some way to do this better, using attributes+reflection or something, yes? + + if (valueType == typeof(bool)) + { + return new BooleanProperty(name, (bool)(defaultValue ?? (object)false)); + } + else if (valueType == typeof(double)) + { + return new DoubleProperty(name, (double)(defaultValue ?? (object)0.0)); + } + else if (valueType == typeof(Pair)) + { + return new DoubleVectorProperty(name, (Pair)(defaultValue ?? (object)Pair.Create(0.0, 0.0))); + } + else if (valueType == typeof(int)) + { + return new Int32Property(name, (int)(defaultValue ?? (object)0)); + } + else if (valueType == typeof(string)) + { + return new StringProperty(name, (string)defaultValue); + } + else if (typeof(ImageResource).IsAssignableFrom(valueType)) + { + return new ImageProperty(name, (ImageResource)defaultValue); + } + else if (valueType.IsEnum) + { + return StaticListChoiceProperty.CreateForEnum( + valueType, + name, + defaultValue ?? ((object[])Enum.GetValues(valueType))[0], + false); + } + + throw new ArgumentException(string.Format("Not a valid type: {0}", valueType.FullName)); + } + + private string name; + private Type valueType; + private object ourValue; + private object defaultValue; + private bool readOnly; + private ValueValidationFailureResult vvfResult; + private EventHandler readOnlyChanged; + private EventHandler valueChanged; + private bool eventAddAllowed = true; + + internal IDisposable BeginEventAddMoratorium() + { + if (!this.eventAddAllowed) + { + throw new InvalidOperationException("An event add moratorium is already in effect"); + } + + IDisposable undoFn = new CallbackOnDispose(() => this.eventAddAllowed = true); + + this.eventAddAllowed = false; + + return undoFn; + } + + public event EventHandler ReadOnlyChanged + { + add + { + if (this.eventAddAllowed) + { + this.readOnlyChanged += value; + } + } + + remove + { + this.readOnlyChanged -= value; + } + } + + protected virtual void OnReadOnlyChanged() + { + if (this.readOnlyChanged != null) + { + this.readOnlyChanged(this, EventArgs.Empty); + } + } + + public event EventHandler ValueChanged + { + add + { + if (this.eventAddAllowed) + { + this.valueChanged += value; + } + } + + remove + { + this.valueChanged -= value; + } + } + + protected virtual void OnValueChanged() + { + if (this.valueChanged != null) + { + this.valueChanged(this, EventArgs.Empty); + } + } + + public ValueValidationFailureResult ValueValidationFailureResult + { + get + { + return this.vvfResult; + } + } + + public string Name + { + get + { + return this.name; + } + } + + [Obsolete("Use the ValueType property instead")] + public Type Type + { + get + { + return this.valueType; + } + } + + public Type ValueType + { + get + { + return this.valueType; + } + } + + public object Value + { + get + { + return this.ourValue; + } + + set + { + SetValueCore(value); + } + } + + protected virtual object OnCoerceValue(object newValue) + { + return newValue; + } + + private void SetValueCore(object value) + { + VerifyNotReadOnly(); + + object value2 = OnCoerceValue(value); + + if (!value2.Equals(this.ourValue)) + { + if (ValidateNewValue(value2)) + { + this.ourValue = value2; + OnValueChanged(); + } + else + { + switch (this.vvfResult) + { + case ValueValidationFailureResult.ThrowException: + throw new ArgumentOutOfRangeException(string.Format("{0} is not a valid value for {1}", value2.ToString(), this.name)); + + case ValueValidationFailureResult.Ignore: + System.Diagnostics.Debug.WriteLine(string.Format("{0} is not a valid value for {1}", value2.ToString(), this.name)); + OnValueChanged(); + break; + + case ValueValidationFailureResult.Clamp: + object clampedValue = ClampNewValue(value2); + Value = clampedValue; + break; + } + } + } + } + + private object ClampNewValue(object newValue) + { + return OnClampNewValue(newValue); + } + + protected abstract object OnClampNewValue(object newValue); + + public object DefaultValue + { + get + { + return this.defaultValue; + } + } + + public bool ReadOnly + { + get + { + return this.readOnly; + } + + set + { + if (this.readOnly != value) + { + this.readOnly = value; + OnReadOnlyChanged(); + } + } + } + + internal Property(object name, Type valueType, object defaultValue, bool readOnly, ValueValidationFailureResult vvfResult) + { + if (defaultValue != null) + { + Type defaultValueType = defaultValue.GetType(); + + if (!valueType.IsAssignableFrom(defaultValueType)) + { + throw new ArgumentOutOfRangeException( + "type", + string.Format( + "defaultValue is not of type specified in constructor. valueType.Name = {0}, defaultValue.GetType().Name = {1}", + valueType.Name, + defaultValue.GetType().Name)); + } + } + + this.name = name.ToString(); + this.valueType = valueType; + this.ourValue = defaultValue; + this.defaultValue = defaultValue; + this.readOnly = readOnly; + + switch (vvfResult) + { + case ValueValidationFailureResult.Clamp: + case ValueValidationFailureResult.Ignore: + case ValueValidationFailureResult.ThrowException: + this.vvfResult = vvfResult; + break; + + default: + throw new InvalidEnumArgumentException("vvfResult", (int)vvfResult, typeof(ValueValidationFailureResult)); + } + } + + internal Property(Property cloneMe, Property sentinelNotUsed) + { + // sentinelNotUsed is just there so that this constructor can be unambiguous from Property(object) + // the call to no op is so that code verifiers aren't thrown off + sentinelNotUsed.NoOp(); + + this.name = cloneMe.name; + this.valueType = cloneMe.valueType; + this.ourValue = cloneMe.ourValue; + this.defaultValue = cloneMe.defaultValue; + this.readOnly = cloneMe.readOnly; + this.vvfResult = cloneMe.vvfResult; + } + + protected void VerifyNotReadOnly() + { + if (this.readOnly) + { + throw new ReadOnlyException("This property is read only"); + } + + return; + } + + protected virtual bool ValidateNewValue(object newValue) + { + return true; + } + + protected virtual string PropertyValueToString(object value) + { + return value.ToString(); + } + + // NOTE: Cloning must NOT clone event subscriptions. + public abstract Property Clone(); + + object ICloneable.Clone() + { + return Clone(); + } + + // lets our use of sentinelNotUsed in the constructor to be FXCOP safe + protected void NoOp() + { + } + } +} diff --git a/src/Base/PropertySystem/PropertyCollection.cs b/src/Base/PropertySystem/PropertyCollection.cs new file mode 100644 index 0000000..da6a837 --- /dev/null +++ b/src/Base/PropertySystem/PropertyCollection.cs @@ -0,0 +1,280 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.Serialization; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public sealed class PropertyCollection + : INotifyPropertyChanged, + IEnumerable, + ICloneable + { + private bool eventAddAllowed = true; + private Dictionary properties = new Dictionary(); + private List rules = new List(); + + public static PropertyCollection CreateEmpty() + { + return new PropertyCollection(new Property[0]); + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public Property this[object propertyName] + { + get + { + string propertyNameString = propertyName.ToString(); + Property theProperty; + this.properties.TryGetValue(propertyNameString, out theProperty); + return theProperty; + } + } + + public int Count + { + get + { + return this.properties.Count; + } + } + + public IEnumerable Properties + { + get + { + return this.properties.Values; + } + } + + public IEnumerable Rules + { + get + { + return this.rules; + } + } + + public IEnumerable PropertyNames + { + get + { + return this.properties.Keys; + } + } + + public PropertyCollection(IEnumerable properties) + { + Initialize(properties, new PropertyCollectionRule[0]); + } + + public PropertyCollection( + IEnumerable properties, + IEnumerable rules) + { + Initialize(properties, rules); + } + + public IDisposable __Internal_BeginEventAddMoratorium() + { + if (!this.eventAddAllowed) + { + throw new InvalidOperationException("An event add moratorium is already in effect"); + } + + List propUndoFns = new List(this.properties.Count); + + foreach (Property property in this.properties.Values) + { + IDisposable propUndoFn = property.BeginEventAddMoratorium(); + propUndoFns.Add(propUndoFn); + } + + IDisposable undoFn = new CallbackOnDispose(() => + { + this.eventAddAllowed = true; + + foreach (IDisposable propUndoFn in propUndoFns) + { + propUndoFn.Dispose(); + } + }); + + this.eventAddAllowed = false; + + return undoFn; + } + + private void Initialize( + IEnumerable properties, + IEnumerable rules) + { + foreach (Property property in properties) + { + Property propertyClone = property.Clone(); + this.properties.Add(propertyClone.Name, propertyClone); + } + + foreach (PropertyCollectionRule rule in rules) + { + PropertyCollectionRule ruleClone = rule.Clone(); + this.rules.Add(ruleClone); + } + + foreach (PropertyCollectionRule rule in this.rules) + { + rule.Initialize(this); + } + + HookUpEvents(); + } + + private void Property_ValueChanged(object sender, EventArgs e) + { + OnPropertyChanged((sender as Property).Name); + } + + private void Property_ReadOnlyChanged(object sender, EventArgs e) + { + OnPropertyChanged((sender as Property).Name); + } + + private void HookUpEvents() + { + foreach (Property property in this.properties.Values) + { + property.ValueChanged -= Property_ValueChanged; + property.ValueChanged += Property_ValueChanged; + + property.ReadOnlyChanged -= Property_ReadOnlyChanged; + property.ReadOnlyChanged += Property_ReadOnlyChanged; + } + } + + public void CopyCompatibleValuesFrom(PropertyCollection srcProps) + { + CopyCompatibleValuesFrom(srcProps, false); + } + + public void CopyCompatibleValuesFrom(PropertyCollection srcProps, bool ignoreReadOnlyFlags) + { + foreach (Property srcProp in srcProps) + { + Property dstProp = this[srcProp.Name]; + + if (dstProp != null && dstProp.ValueType == srcProp.ValueType) + { + if (dstProp.ReadOnly && ignoreReadOnlyFlags) + { + dstProp.ReadOnly = false; + dstProp.Value = srcProp.Value; + dstProp.ReadOnly = true; + } + else + { + dstProp.Value = srcProp.Value; + } + } + } + } + + public static PropertyCollection CreateMerged(PropertyCollection pc1, PropertyCollection pc2) + { + foreach (Property p1 in pc1.Properties) + { + Property p2 = pc2[p1.Name]; + + if (p2 != null) + { + throw new ArgumentException("pc1 must not have any properties with the same name as in pc2"); + } + } + + Property[] allProps = new Property[pc1.Count + pc2.Count]; + int index = 0; + + foreach (Property p1 in pc1) + { + allProps[index] = p1.Clone(); + ++index; + } + + foreach (Property p2 in pc2) + { + allProps[index] = p2.Clone(); + ++index; + } + + List allRules = new List(); + + foreach (PropertyCollectionRule pcr1 in pc1.Rules) + { + allRules.Add(pcr1); + } + + foreach (PropertyCollectionRule pcr2 in pc2.Rules) + { + allRules.Add(pcr2); + } + + PropertyCollection mergedPC = new PropertyCollection(allProps, allRules); + return mergedPC; + } + + public PropertyCollection Clone() + { + List clonedProperties = new List(); + + foreach (Property property in this.Properties) + { + Property clonedProperty = property.Clone(); + clonedProperties.Add(clonedProperty); + } + + List clonedRules = new List(); + + foreach (PropertyCollectionRule rule in this.rules) + { + PropertyCollectionRule clonedRule = rule.Clone(); + clonedRules.Add(clonedRule); + } + + return new PropertyCollection(clonedProperties, clonedRules); + } + + object ICloneable.Clone() + { + return Clone(); + } + + public IEnumerator GetEnumerator() + { + return Properties.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Base/PropertySystem/PropertyCollectionRule.cs b/src/Base/PropertySystem/PropertyCollectionRule.cs new file mode 100644 index 0000000..7a7a56b --- /dev/null +++ b/src/Base/PropertySystem/PropertyCollectionRule.cs @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + public abstract class PropertyCollectionRule + : ICloneable + { + private PropertyCollection owner; + + protected PropertyCollection Owner + { + get + { + return this.owner; + } + } + + internal PropertyCollectionRule() + { + } + + internal void Initialize(PropertyCollection owner) + { + if (this.owner != null) + { + throw new InvalidOperationException("Already initialized"); + } + + this.owner = owner; + + OnInitialized(); + } + + internal bool IsInitialized + { + get + { + return (this.owner != null); + } + } + + internal void VerifyInitialized() + { + if (!IsInitialized) + { + throw new InvalidOperationException("This rule was never initialized into a PropertyCollection"); + } + } + + public abstract PropertyCollectionRule Clone(); + + object ICloneable.Clone() + { + return Clone(); + } + + protected abstract void OnInitialized(); + } +} diff --git a/src/Base/PropertySystem/Property`1.cs b/src/Base/PropertySystem/Property`1.cs new file mode 100644 index 0000000..dacbf7d --- /dev/null +++ b/src/Base/PropertySystem/Property`1.cs @@ -0,0 +1,89 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public abstract class Property + : Property + { + public new T Value + { + get + { + return (T)base.Value; + } + + set + { + base.Value = value; + } + } + + public new T DefaultValue + { + get + { + return (T)base.DefaultValue; + } + } + + internal Property(object name, T defaultValue, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, typeof(T), defaultValue, readOnly, vvfResult) + { + } + + internal Property(Property cloneMe, Property sentinelNotUsed) + : base(cloneMe, sentinelNotUsed) + { + // sentinelNotUsed is just there so that this constructor can be unambiguous from Property(object) + } + + protected virtual T OnCoerceValueT(object newValue) + { + return (T)newValue; + } + + protected override sealed object OnCoerceValue(object newValue) + { + return OnCoerceValueT(newValue); + } + + protected override sealed object OnClampNewValue(object newValue) + { + return OnClampNewValueT((T)newValue); + } + + protected abstract T OnClampNewValueT(T newValue); + + protected virtual bool ValidateNewValueT(T newValue) + { + return true; + } + + protected override sealed bool ValidateNewValue(object newValue) + { + T newValueT = (T)newValue; + return ValidateNewValueT(newValueT); + } + + protected virtual string PropertyValueToStringT(T value) + { + return value.ToString(); + } + + protected override sealed string PropertyValueToString(object value) + { + return base.PropertyValueToString(value); + } + } +} diff --git a/src/Base/PropertySystem/ReadOnlyBoundToBooleanRule.cs b/src/Base/PropertySystem/ReadOnlyBoundToBooleanRule.cs new file mode 100644 index 0000000..8d657ea --- /dev/null +++ b/src/Base/PropertySystem/ReadOnlyBoundToBooleanRule.cs @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + /// + /// Defines a rule that binds the ReadOnly flag of property A to the value of boolean property B. + /// A and B must be different properties. + /// + public sealed class ReadOnlyBoundToBooleanRule + : PropertyCollectionRule + { + private string targetPropertyName; + private string sourceBooleanPropertyName; + private bool inverse; + + public ReadOnlyBoundToBooleanRule(Property targetProperty, BooleanProperty sourceProperty, bool inverse) + : this(targetProperty.Name, sourceProperty.Name, inverse) + { + } + + public ReadOnlyBoundToBooleanRule(object targetPropertyName, object sourceBooleanPropertyName, bool inverse) + { + this.targetPropertyName = targetPropertyName.ToString(); + this.sourceBooleanPropertyName = sourceBooleanPropertyName.ToString(); + this.inverse = inverse; + } + + protected override void OnInitialized() + { + Property targetProperty = Owner[this.targetPropertyName]; + BooleanProperty sourceProperty = (BooleanProperty)Owner[this.sourceBooleanPropertyName]; + + if (0 == string.Compare(targetProperty.Name, sourceProperty.Name, StringComparison.InvariantCulture)) + { + throw new ArgumentException("source and target properties must be different"); + } + + Sync(); + + sourceProperty.ValueChanged += new EventHandler(SourceProperty_ValueChanged); + } + + private void SourceProperty_ValueChanged(object sender, EventArgs e) + { + Sync(); + } + + private void Sync() + { + Property targetProperty = Owner[this.targetPropertyName]; + BooleanProperty sourceProperty = (BooleanProperty)Owner[this.sourceBooleanPropertyName]; + + bool readOnly = sourceProperty.Value; + + targetProperty.ReadOnly = readOnly ^ this.inverse; + } + + public override PropertyCollectionRule Clone() + { + return new ReadOnlyBoundToBooleanRule(this.targetPropertyName, this.sourceBooleanPropertyName, this.inverse); + } + } +} \ No newline at end of file diff --git a/src/Base/PropertySystem/ReadOnlyBoundToValueRule`2.cs b/src/Base/PropertySystem/ReadOnlyBoundToValueRule`2.cs new file mode 100644 index 0000000..dd970a8 --- /dev/null +++ b/src/Base/PropertySystem/ReadOnlyBoundToValueRule`2.cs @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + public sealed class ReadOnlyBoundToValueRule + : PropertyCollectionRule + where TProperty : Property + { + private string targetPropertyName; + private string sourcePropertyName; + private bool inverse; + private TValue[] valuesForReadOnly; + + // (inverse = false) -> If sourceProperty.Value equals any of the values in valuesForReadOnly, then targetProperty.ReadOnly will be set to true, else it will be set to false. + // (inverse = true) -> If sourceProperty.Value equals any of the values in valuesForReadOnly, then targetProperty.ReadOnly will be set to false, else it will be set to true. + + public ReadOnlyBoundToValueRule(Property targetProperty, TProperty sourceProperty, TValue valueForReadOnly, bool inverse) + : this(targetProperty.Name, sourceProperty.Name, new TValue[1] { valueForReadOnly }, inverse) + { + } + + public ReadOnlyBoundToValueRule(Property targetProperty, TProperty sourceProperty, TValue[] valuesForReadOnly, bool inverse) + : this(targetProperty.Name, sourceProperty.Name, valuesForReadOnly, inverse) + { + } + + public ReadOnlyBoundToValueRule(object targetPropertyName, object sourcePropertyName, TValue valueForReadOnly, bool inverse) + : this(targetPropertyName, sourcePropertyName, new TValue[1] { valueForReadOnly }, inverse) + { + } + + public ReadOnlyBoundToValueRule(object targetPropertyName, object sourcePropertyName, TValue[] valuesForReadOnly, bool inverse) + { + this.targetPropertyName = targetPropertyName.ToString(); + this.sourcePropertyName = sourcePropertyName.ToString(); + this.valuesForReadOnly = (TValue[])valuesForReadOnly.Clone(); + this.inverse = inverse; + } + + protected override void OnInitialized() + { + Property targetProperty = Owner[this.targetPropertyName]; + TProperty sourceProperty = (TProperty)Owner[this.sourcePropertyName]; + + if (0 == string.Compare(targetProperty.Name, sourceProperty.Name, StringComparison.InvariantCulture)) + { + throw new ArgumentException("source and target properties must be different"); + } + + Sync(); + + sourceProperty.ValueChanged += new EventHandler(SourceProperty_ValueChanged); + } + + private void SourceProperty_ValueChanged(object sender, EventArgs e) + { + Sync(); + } + + private void Sync() + { + Property targetProperty = Owner[this.targetPropertyName]; + TProperty sourceProperty = (TProperty)Owner[this.sourcePropertyName]; + + bool readOnly = false; + + foreach (TValue valueForReadOnly in this.valuesForReadOnly) + { + if (valueForReadOnly.Equals(sourceProperty.Value)) + { + readOnly = true; + break; + } + } + + targetProperty.ReadOnly = readOnly ^ this.inverse; + } + + public override PropertyCollectionRule Clone() + { + return new ReadOnlyBoundToValueRule( + this.targetPropertyName, + this.sourcePropertyName, + this.valuesForReadOnly, + this.inverse); + } + } +} diff --git a/src/Base/PropertySystem/ScalarProperty`1.cs b/src/Base/PropertySystem/ScalarProperty`1.cs new file mode 100644 index 0000000..549fa26 --- /dev/null +++ b/src/Base/PropertySystem/ScalarProperty`1.cs @@ -0,0 +1,170 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public abstract class ScalarProperty + : Property + where T : struct, IComparable + { + private T minValue; + private T maxValue; + + public T MinValue + { + get + { + return this.minValue; + } + } + + public T MaxValue + { + get + { + return this.maxValue; + } + } + + public bool IsLessThan(ScalarProperty rhs) + { + return IsLessThan(this, rhs); + } + + public static bool IsLessThan(ScalarProperty lhs, ScalarProperty rhs) + { + return IsLessThan(lhs.Value, rhs.Value); + } + + public static bool IsLessThan(T lhs, T rhs) + { + return lhs.CompareTo(rhs) < 0; + } + + public bool IsGreaterThan(ScalarProperty rhs) + { + return IsGreaterThan(this, rhs); + } + + public static bool IsGreaterThan(ScalarProperty lhs, ScalarProperty rhs) + { + return IsGreaterThan(lhs.Value, rhs.Value); + } + + public static bool IsGreaterThan(T lhs, T rhs) + { + return lhs.CompareTo(rhs) > 0; + } + + public bool IsEqualTo(ScalarProperty rhs) + { + return IsEqualTo(this, rhs); + } + + public static bool IsEqualTo(ScalarProperty lhs, ScalarProperty rhs) + { + return IsEqualTo(lhs.Value, rhs.Value); + } + + public static bool IsEqualTo(T lhs, T rhs) + { + return lhs.CompareTo(rhs) == 0; + } + + public static T Clamp(T value, T min, T max) + { + T newValue = value; + + if (IsGreaterThan(min, max)) + { + throw new ArgumentOutOfRangeException("min must be less than or equal to max"); + } + + if (IsGreaterThan(value, max)) + { + newValue = max; + } + + if (IsLessThan(value, min)) + { + newValue = min; + } + + return newValue; + } + + public T ClampPotentialValue(T newValue) + { + return Clamp(newValue, this.minValue, this.maxValue); + } + + internal ScalarProperty(object name, T defaultValue, T minValue, T maxValue) + : this(name, defaultValue, minValue, maxValue, false) + { + } + + internal ScalarProperty(object name, T defaultValue, T minValue, T maxValue, bool readOnly) + : this(name, defaultValue, minValue, maxValue, readOnly, DefaultValueValidationFailureResult) + { + } + + internal ScalarProperty(object name, T defaultValue, T minValue, T maxValue, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValue, readOnly, vvfResult) + { + if (IsLessThan(maxValue, minValue)) + { + throw new ArgumentOutOfRangeException("maxValue < minValue"); + } + + if (IsLessThan(defaultValue, minValue)) + { + throw new ArgumentOutOfRangeException("defaultValue < minValue"); + } + + if (IsGreaterThan(defaultValue, maxValue)) + { + throw new ArgumentOutOfRangeException("defaultValue > maxValue"); + } + + this.minValue = minValue; + this.maxValue = maxValue; + } + + internal ScalarProperty(ScalarProperty copyMe, ScalarProperty sentinelNotUsed) + : base(copyMe, sentinelNotUsed) + { + this.minValue = copyMe.minValue; + this.maxValue = copyMe.maxValue; + } + + protected override T OnClampNewValueT(T newValue) + { + return ClampPotentialValue(newValue); + } + + protected override bool ValidateNewValueT(T newValue) + { + if (IsLessThan(newValue, this.minValue)) + { + return false; + } + + if (IsGreaterThan(newValue, this.maxValue)) + { + return false; + } + + return base.ValidateNewValueT(newValue); + } + } +} diff --git a/src/Base/PropertySystem/SoftMutuallyBoundMinMaxRule`2.cs b/src/Base/PropertySystem/SoftMutuallyBoundMinMaxRule`2.cs new file mode 100644 index 0000000..f702c33 --- /dev/null +++ b/src/Base/PropertySystem/SoftMutuallyBoundMinMaxRule`2.cs @@ -0,0 +1,101 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + /// + /// Defines a rule for two properties, A and B, such that A will always be less than or equal to B, + /// and B will always be greater than or equal to A. If A is set to a new value that is greater than B, + /// then B will be set equal to A. If B is set to a new value that is less than A, then A will be set + /// equal to B. + /// + public sealed class SoftMutuallyBoundMinMaxRule + : PropertyCollectionRule + where TProperty : ScalarProperty + where TValue : struct, IComparable + { + private string minPropertyName; + private string maxPropertyName; + + public SoftMutuallyBoundMinMaxRule(Property minProperty, Property maxProperty) + : this(minProperty.Name, maxProperty.Name) + { + } + + public SoftMutuallyBoundMinMaxRule(object minPropertyName, object maxPropertyName) + { + this.minPropertyName = minPropertyName.ToString(); + this.maxPropertyName = maxPropertyName.ToString(); + } + + protected override void OnInitialized() + { + TProperty minProperty = (TProperty)Owner[this.minPropertyName]; + TProperty maxProperty = (TProperty)Owner[this.maxPropertyName]; + + if (ScalarProperty.IsGreaterThan(minProperty.MinValue, maxProperty.MinValue)) + { + throw new ArgumentOutOfRangeException("MinProperty.MinValue must be less than or equal to MaxProperty.MinValue"); + } + + if (ScalarProperty.IsGreaterThan(minProperty.MaxValue, maxProperty.MaxValue)) + { + throw new ArgumentOutOfRangeException("MinProperty.MaxValue must be less than or equal to MaxProperty.MaxValue"); + } + + // Analyze the PropertyCollection we are bound to in order to ensure that we do not + // have any "infinite loops". It is safe to simply ensure that no other SoftMutuallyBoundMinMaxRule + // has minPropertyName as a maxPropertyName. + foreach (PropertyCollectionRule rule in this.Owner.Rules) + { + SoftMutuallyBoundMinMaxRule asOurRule = rule as SoftMutuallyBoundMinMaxRule; + + if (asOurRule != null) + { + if (asOurRule.maxPropertyName.ToString() == this.minPropertyName.ToString()) + { + throw new ArgumentException("The graph of SoftMutuallyBoundMinMaxRule's in the PropertyCollection has a cycle in it"); + } + } + } + + minProperty.ValueChanged += new EventHandler(MinProperty_ValueChanged); + maxProperty.ValueChanged += new EventHandler(MaxProperty_ValueChanged); + } + + private void MaxProperty_ValueChanged(object sender, EventArgs e) + { + TProperty minProperty = (TProperty)Owner[this.minPropertyName]; + TProperty maxProperty = (TProperty)Owner[this.maxPropertyName]; + + if (maxProperty.IsLessThan(minProperty)) + { + minProperty.Value = maxProperty.Value; + } + } + + private void MinProperty_ValueChanged(object sender, EventArgs e) + { + TProperty minProperty = (TProperty)Owner[this.minPropertyName]; + TProperty maxProperty = (TProperty)Owner[this.maxPropertyName]; + + if (minProperty.IsGreaterThan(maxProperty)) + { + maxProperty.Value = minProperty.Value; + } + } + + public override PropertyCollectionRule Clone() + { + return new SoftMutuallyBoundMinMaxRule(this.minPropertyName, this.maxPropertyName); + } + } +} diff --git a/src/Base/PropertySystem/StaticListChoiceProperty.cs b/src/Base/PropertySystem/StaticListChoiceProperty.cs new file mode 100644 index 0000000..abd4c50 --- /dev/null +++ b/src/Base/PropertySystem/StaticListChoiceProperty.cs @@ -0,0 +1,119 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace PaintDotNet.PropertySystem +{ + public class StaticListChoiceProperty + : Property + { + private object[] valueChoices; + + public object[] ValueChoices + { + get + { + return (object[])this.valueChoices.Clone(); + } + } + + public StaticListChoiceProperty(object name, object[] valueChoices) + : this(name, valueChoices, 0) + { + } + + public StaticListChoiceProperty(object name, object[] valueChoices, int defaultChoiceIndex) + : this(name, valueChoices, defaultChoiceIndex, false) + { + } + + public StaticListChoiceProperty(object name, object[] valueChoices, int defaultChoiceIndex, bool readOnly) + : this(name, valueChoices, defaultChoiceIndex, readOnly, DefaultValueValidationFailureResult) + { + } + + public StaticListChoiceProperty(object name, object[] valueChoices, int defaultChoiceIndex, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, valueChoices[defaultChoiceIndex], readOnly, vvfResult) + { + if (defaultChoiceIndex < 0 || defaultChoiceIndex >= valueChoices.Length) + { + throw new ArgumentOutOfRangeException("defaultChoiceIndex", "must be in the range [0, valueChoices.Length) (actual value: " + defaultChoiceIndex.ToString() + ")"); + } + + this.valueChoices = (object[])valueChoices.Clone(); + } + + protected StaticListChoiceProperty(StaticListChoiceProperty cloneMe, StaticListChoiceProperty sentinelNotUsed) + : base(cloneMe, sentinelNotUsed) + { + this.valueChoices = (object[])cloneMe.valueChoices.Clone(); + } + + public override Property Clone() + { + return new StaticListChoiceProperty(this, this); + } + + protected override bool ValidateNewValueT(object newValue) + { + int index = Array.IndexOf(this.valueChoices, newValue); + return (index != -1); + } + + public static StaticListChoiceProperty CreateForEnum(object name, TEnum defaultValue, bool readOnly) + where TEnum : struct + { + return CreateForEnum(typeof(TEnum), name, defaultValue, readOnly); + } + + public static StaticListChoiceProperty CreateForEnum(Type enumType, object name, object defaultValue, bool readOnly) + { + // [Flags] enums aren't currently supported + object[] flagsAttributes = enumType.GetCustomAttributes(typeof(FlagsAttribute), true); + + if (flagsAttributes.Length > 0) + { + throw new ArgumentOutOfRangeException("Enums with [Flags] are not currently supported"); + } + + Array enumChoices = Enum.GetValues(enumType); + int defaultChoiceIndex = Array.IndexOf(enumChoices, defaultValue); + + if (defaultChoiceIndex == -1) + { + throw new ArgumentOutOfRangeException(string.Format( + "defaultValue ({0}) is not a valid enum value for {1}", + defaultValue.ToString(), + enumType.FullName)); + } + + object[] enumChoicesObj = new object[enumChoices.Length]; + enumChoices.CopyTo(enumChoicesObj, 0); + + StaticListChoiceProperty enumProperty = new StaticListChoiceProperty(name, enumChoicesObj, defaultChoiceIndex, readOnly); + return enumProperty; + } + + protected override object OnClampNewValueT(object newValue) + { + object clampedValue = newValue; + int newValueIndex = Array.IndexOf(this.valueChoices, newValue); + + if (newValueIndex == -1) + { + clampedValue = this.DefaultValue; + } + + return clampedValue; + } + } +} diff --git a/src/Base/PropertySystem/StringProperty.cs b/src/Base/PropertySystem/StringProperty.cs new file mode 100644 index 0000000..c3a075a --- /dev/null +++ b/src/Base/PropertySystem/StringProperty.cs @@ -0,0 +1,108 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public sealed class StringProperty + : Property + { + private int maxLength; + + public static int MaxMaxLength + { + get + { + return 32767; + } + } + + public int MaxLength + { + get + { + return this.maxLength; + } + } + + public StringProperty(object name) + : this(name, string.Empty) + { + } + + public StringProperty(object name, string defaultValue) + : this(name, defaultValue, MaxMaxLength) + { + } + + public StringProperty(object name, string defaultValue, int maxLength) + : this(name, defaultValue, maxLength, false) + { + } + + public StringProperty(object name, string defaultValue, int maxLength, bool readOnly) + : this(name, defaultValue, maxLength, readOnly, DefaultValueValidationFailureResult) + { + } + + public StringProperty(object name, string defaultValue, int maxLength, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValue, readOnly, vvfResult) + { + if (defaultValue == null) + { + throw new ArgumentNullException("defaultValue must not be null"); + } + + if (maxLength < 0 || maxLength > MaxMaxLength) + { + throw new ArgumentOutOfRangeException(string.Format( + "maxLength was {0} but it must be greater than 0, and less than StringProperty.MaxMaxLength", + maxLength)); + } + + this.maxLength = maxLength; + } + + private StringProperty(StringProperty cloneMe, StringProperty sentinelNotUsed) + : base(cloneMe, sentinelNotUsed) + { + this.maxLength = cloneMe.maxLength; + } + + public override Property Clone() + { + return new StringProperty(this, this); + } + + protected override string PropertyValueToStringT(string value) + { + return value; + } + + protected override bool ValidateNewValueT(string newValue) + { + return (newValue.Length <= this.maxLength); + } + + protected override string OnClampNewValueT(string newValue) + { + string clampedValue = newValue; + + if (clampedValue.Length > this.MaxLength) + { + clampedValue = clampedValue.Substring(this.MaxLength); + } + + return clampedValue; + } + } +} diff --git a/src/Base/PropertySystem/ValueValidationFailureResult.cs b/src/Base/PropertySystem/ValueValidationFailureResult.cs new file mode 100644 index 0000000..19558dc --- /dev/null +++ b/src/Base/PropertySystem/ValueValidationFailureResult.cs @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.PropertySystem +{ + public enum ValueValidationFailureResult + { + /// + /// If an invalid value is set through a property's Value property, then it will be ignored + /// and the current value will be retained. A ValueChanged event will then be raised with + /// the property's retained value. + /// + Ignore, + + /// + /// If an invalid value is set through a property's Value property, then it will either be + /// clamped to within the valid range of the property, or it will be ignored. Clamping + /// behavior is property value type specific; for example, an integer will be clamped to + /// a certain range, whereas a string will be truncated past a certain length. + /// If the invalid value cannot be clamped, then the property's Value will not change. + /// A ValueChanged event will then be raised with the property's value, regardless of + /// whether the value was changed or not. + /// + Clamp, + + /// + /// If an invalid value is set through a property's Value property, then an exception will + /// be raised. + /// + ThrowException + } +} diff --git a/src/Base/PropertySystem/VectorProperty`1.cs b/src/Base/PropertySystem/VectorProperty`1.cs new file mode 100644 index 0000000..ec5e4a4 --- /dev/null +++ b/src/Base/PropertySystem/VectorProperty`1.cs @@ -0,0 +1,170 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.PropertySystem +{ + public abstract class VectorProperty + : Property> + where T : struct, IComparable + { + private Pair minValues; + private Pair maxValues; + + public Pair MinValues + { + get + { + return this.minValues; + } + } + + public Pair MaxValues + { + get + { + return this.maxValues; + } + } + + public T MinValueX + { + get + { + return MinValues.First; + } + } + + public T MaxValueX + { + get + { + return MaxValues.First; + } + } + + public T MinValueY + { + get + { + return MinValues.Second; + } + } + + public T MaxValueY + { + get + { + return MaxValues.Second; + } + } + + public T DefaultValueX + { + get + { + return DefaultValue.First; + } + } + + public T DefaultValueY + { + get + { + return DefaultValue.Second; + } + } + + public T ValueX + { + get + { + return Value.First; + } + + set + { + Value = Pair.Create(value, ValueY); + } + } + + public T ValueY + { + get + { + return Value.Second; + } + + set + { + Value = Pair.Create(ValueX, value); + } + } + + public bool IsEqualTo(VectorProperty rhs) + { + return IsEqualTo(this, rhs); + } + + public static bool IsEqualTo(VectorProperty lhs, VectorProperty rhs) + { + return IsEqualTo(lhs.Value, rhs.Value); + } + + public static bool IsEqualTo(Pair lhs, Pair rhs) + { + return (lhs.First.CompareTo(rhs.First) == 0) && (lhs.Second.CompareTo(rhs.Second) == 0); + } + + internal VectorProperty(object name, Pair defaultValues, Pair minValues, Pair maxValues) + : this(name, defaultValues, minValues, maxValues, false) + { + } + + internal VectorProperty(object name, Pair defaultValues, Pair minValues, Pair maxValues, bool readOnly) + : this(name, defaultValues, minValues, maxValues, readOnly, DefaultValueValidationFailureResult) + { + } + + internal VectorProperty(object name, Pair defaultValues, Pair minValues, Pair maxValues, bool readOnly, ValueValidationFailureResult vvfResult) + : base(name, defaultValues, readOnly, vvfResult) + { + this.minValues = minValues; + this.maxValues = maxValues; + } + + internal VectorProperty(VectorProperty cloneMe, VectorProperty sentinelNotUsed) + : base(cloneMe, sentinelNotUsed) + { + this.minValues = cloneMe.minValues; + this.maxValues = cloneMe.maxValues; + } + + public T ClampPotentialValueX(T newValue) + { + return ScalarProperty.Clamp(newValue, this.MinValueX, this.MaxValueX); + } + + public T ClampPotentialValueY(T newValue) + { + return ScalarProperty.Clamp(newValue, this.MinValueY, this.MaxValueY); + } + + public Pair ClampPotentialValue(Pair newValue) + { + return Pair.Create(ClampPotentialValueX(newValue.First), ClampPotentialValueY(newValue.Second)); + } + + protected override Pair OnClampNewValueT(Pair newValue) + { + return ClampPotentialValue(newValue); + } + } +} diff --git a/src/Base/Quadruple.cs b/src/Base/Quadruple.cs new file mode 100644 index 0000000..93a8629 --- /dev/null +++ b/src/Base/Quadruple.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public static class Quadruple + { + public static Quadruple Create(T first, U second, V third, W fourth) + { + return new Quadruple(first, second, third, fourth); + } + } +} diff --git a/src/Base/Quadruple`4.cs b/src/Base/Quadruple`4.cs new file mode 100644 index 0000000..a4e0db7 --- /dev/null +++ b/src/Base/Quadruple`4.cs @@ -0,0 +1,190 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + [Serializable] + public struct Quadruple + { + private T first; + private U second; + private V third; + private W fourth; + + public T First + { + get + { + return this.first; + } + } + + public U Second + { + get + { + return this.second; + } + } + + public V Third + { + get + { + return this.third; + } + } + + public W Fourth + { + get + { + return this.fourth; + } + } + + public override int GetHashCode() + { + int firstHash; + int secondHash; + int thirdHash; + int fourthHash; + + if (object.ReferenceEquals(this.first, null)) + { + firstHash = 0; + } + else + { + firstHash = this.first.GetHashCode(); + } + + if (object.ReferenceEquals(this.second, null)) + { + secondHash = 0; + } + else + { + secondHash = this.second.GetHashCode(); + } + + if (object.ReferenceEquals(this.third, null)) + { + thirdHash = 0; + } + else + { + thirdHash = this.third.GetHashCode(); + } + + if (object.ReferenceEquals(this.fourth, null)) + { + fourthHash = 0; + } + else + { + fourthHash = this.fourth.GetHashCode(); + } + + return firstHash ^ secondHash ^ thirdHash ^ fourthHash; + } + + public override bool Equals(object obj) + { + return ((obj != null) && (obj is Quadruple) && (this == (Quadruple)obj)); + } + + public static bool operator ==(Quadruple lhs, Quadruple rhs) + { + bool firstEqual; + bool secondEqual; + bool thirdEqual; + bool fourthEqual; + + if (object.ReferenceEquals(lhs.First, null) && object.ReferenceEquals(rhs.First, null)) + { + firstEqual = true; + } + else if (object.ReferenceEquals(lhs.First, null) || object.ReferenceEquals(rhs.First, null)) + { + firstEqual = false; + } + else + { + firstEqual = lhs.First.Equals(rhs.First); + } + + if (object.ReferenceEquals(lhs.Second, null) && object.ReferenceEquals(rhs.Second, null)) + { + secondEqual = true; + } + else if (object.ReferenceEquals(lhs.Second, null) || object.ReferenceEquals(rhs.Second, null)) + { + secondEqual = false; + } + else + { + secondEqual = lhs.Second.Equals(rhs.Second); + } + + if (object.ReferenceEquals(lhs.Third, null) && object.ReferenceEquals(rhs.Third, null)) + { + thirdEqual = true; + } + else if (object.ReferenceEquals(lhs.Third, null) || object.ReferenceEquals(rhs.Third, null)) + { + thirdEqual = false; + } + else + { + thirdEqual = lhs.Third.Equals(rhs.Third); + } + + if (object.ReferenceEquals(lhs.Fourth, null) && object.ReferenceEquals(rhs.Fourth, null)) + { + fourthEqual = true; + } + else if (object.ReferenceEquals(lhs.Fourth, null) || object.ReferenceEquals(rhs.Fourth, null)) + { + fourthEqual = false; + } + else + { + fourthEqual = lhs.Fourth.Equals(rhs.Fourth); + } + + return firstEqual && secondEqual && thirdEqual && fourthEqual; + } + + public static bool operator !=(Quadruple lhs, Quadruple rhs) + { + return !(lhs == rhs); + } + + public Triple GetTriple123() + { + return Triple.Create(this.first, this.second, this.third); + } + + public Triple GetTriple234() + { + return Triple.Create(this.second, this.third, this.fourth); + } + + public Quadruple(T first, U second, V third, W fourth) + { + this.first = first; + this.second = second; + this.third = third; + this.fourth = fourth; + } + } +} diff --git a/src/Base/ReadOnlyException.cs b/src/Base/ReadOnlyException.cs new file mode 100644 index 0000000..33c7222 --- /dev/null +++ b/src/Base/ReadOnlyException.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + [Serializable] + public class ReadOnlyException + : ApplicationException + { + public ReadOnlyException() + : base() + { + } + + public ReadOnlyException(string message) + : base(message) + { + } + + public ReadOnlyException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected ReadOnlyException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Base/Resource`1.cs b/src/Base/Resource`1.cs new file mode 100644 index 0000000..016b5ee --- /dev/null +++ b/src/Base/Resource`1.cs @@ -0,0 +1,48 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public abstract class Resource + { + protected T resource; + + public T Reference + { + get + { + if (this.resource == null) + { + this.resource = Load(); + } + + return this.resource; + } + } + + public T GetCopy() + { + return Load(); + } + + protected abstract T Load(); + + protected Resource() + { + this.resource = default(T); + } + + protected Resource(T resource) + { + this.resource = resource; + } + } +} diff --git a/src/Base/Set.cs b/src/Base/Set.cs new file mode 100644 index 0000000..974538c --- /dev/null +++ b/src/Base/Set.cs @@ -0,0 +1,271 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; + +namespace PaintDotNet +{ + /// + /// Represents an enumerable collection of items. Each item can only be present + /// in the collection once. An item's identity is determined by a combination + /// of the return values from its GetHashCode and Equals methods. + /// This class is analagous to C++'s std::set template class. + /// + [Serializable] + public class Set + : IEnumerable, + ICloneable, + ICollection + { + private Hashtable hashtable; + + /// + /// Adds an element to the set. + /// + /// The object reference to be included in the set. + /// item is a null reference + /// item is already in the Set + public void Add(object item) + { + try + { + hashtable.Add(item, null); + } + + catch (ArgumentNullException e1) + { + throw e1; + } + + catch (ArgumentException e2) + { + throw e2; + } + } + + /// + /// Removes an element from the set. + /// + /// The object reference to be excluded from the set. + /// item is a null reference + public void Remove(object item) + { + try + { + hashtable.Remove(item); + } + + catch (ArgumentNullException e1) + { + throw e1; + } + } + + /// + /// Determines whether the Set includes a specific element. + /// + /// The object reference to check for. + /// true if the Set includes item, false if it doesn't. + /// item is a null reference. + public bool Contains(object item) + { + try + { + return hashtable.ContainsKey(item); + } + + catch (ArgumentNullException e1) + { + throw e1; + } + } + + /// + /// Constructs an empty Set. + /// + public Set() + { + this.hashtable = new Hashtable(); + } + + /// + /// Constructs a Set with data copied from the given list. + /// + /// + public Set(IEnumerable cloneMe) + { + this.hashtable = new Hashtable(); + + foreach (object theObject in cloneMe) + { + Add(theObject); + } + } + + public static Set Create(params T[] items) + { + return new Set(items); + } + + /// + /// Constructs a copy of a Set. + /// + /// The Set to copy from. + private Set(Set copyMe) + { + hashtable = (Hashtable)copyMe.Clone(); + } + + #region IEnumerable Members + + /// + /// Returns an IEnumerator that can be used to enumerate through the items in the Set. + /// + /// An IEnumerator for the Set. + public IEnumerator GetEnumerator() + { + return hashtable.Keys.GetEnumerator(); + } + + #endregion + + #region ICloneable Members + + /// + /// Returns a copy of the Set. The elements in the Set are copied by-reference only. + /// + /// + public object Clone() + { + return new Set(this); + } + + #endregion + + #region ICollection Members + + /// + /// Gets a value indicating whether or not the Set is synchronized (thread-safe). + /// + public bool IsSynchronized + { + get + { + return false; + } + } + + /// + /// Gets a value indicating how many elements are contained within the Set. + /// + public int Count + { + get + { + return hashtable.Count; + } + } + + /// + /// Copies the Set elements to a one-dimensional Array instance at a specified index. + /// + /// The one-dimensional Array that is the destination of the objects copied from the Set. The Array must have zero-based indexing. + /// The zero-based index in array at which copying begins. + /// array is a null reference. + /// index is less than zero. + /// The array is not one-dimensional, or the array could not contain the objects copied to it. + /// The Array does not have enough space, starting from the given offset, to contain all the Set's objects. + public void CopyTo(Array array, int index) + { + int i = index; + + if (array == null) + { + throw new ArgumentNullException("array"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + foreach (object o in this) + { + try + { + array.SetValue(o, i); + } + + catch (ArgumentException e1) + { + throw e1; + } + + catch (IndexOutOfRangeException e2) + { + throw e2; + } + + ++i; + } + } + + /// + /// Gets an object that can be used to synchronize access to the Set. + /// + public object SyncRoot + { + get + { + return this; + } + } + + #endregion + + /// + /// Copies the elements of the Set to a new generic array. + /// + /// An array of object references. + public object[] ToArray() + { + object[] array = new object[Count]; + int index = 0; + + foreach (object o in this) + { + array[index] = o; + ++index; + } + + return array; + } + + /// + /// Copies the elements of the Set to a new array of the requested type. + /// + /// The Type of array to create and copy elements to. + /// An array of objects of the requested type. + public Array ToArray(Type type) + { + Array array = Array.CreateInstance(type, Count); + int index = 0; + + foreach (object o in this) + { + array.SetValue(o, index); + ++index; + } + + return array; + } + } +} diff --git a/src/Base/Set`1.cs b/src/Base/Set`1.cs new file mode 100644 index 0000000..28fe6fd --- /dev/null +++ b/src/Base/Set`1.cs @@ -0,0 +1,389 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; + +namespace PaintDotNet +{ + /// + /// Represents an enumerable collection of items. Each item can only be present + /// in the collection once. An item's identity is determined by a combination + /// of the return values from its GetHashCode and Equals methods. + /// This class is analagous to C++'s std::set template class. + /// + [Serializable] + public class Set + : ICloneable, + ICollection + { + private Dictionary dictionary; + + public static Set Intersect(Set set1, Set set2) + { + Set intersection = new Set(); + + foreach (T item in set1) + { + if (set2.Contains(item)) + { + intersection.Add(item); + } + } + + return intersection; + } + + public static Set Union(Set set1, Set set2) + { + Set union = new Set(set1); + + foreach (T item in set2) + { + if (!union.Contains(item)) + { + union.Add(item); + } + } + + return union; + } + + public static Set Without(Set withUs, Set withoutUs) + { + Set result = new Set(); + + foreach (T item in withUs) + { + if (!withoutUs.Contains(item)) + { + result.Add(item); + } + } + + return result; + } + + public static bool AreEqual(Set set1, Set set2) + { + if (set1.Count != set2.Count) + { + // Can't be equal if sizes are different + return false; + } + + if (set1.Count == 0) + { + // Empty sets are equal to each other. + // We know that set1.Count=set2.Count, so no need to check set2.Count for 0 as well. + return true; + } + + // At this point we know that either everything in set1 is in set2, or + // that there is something in set1 which is not in set2. + foreach (T item in set1) + { + if (!set2.Contains(item)) + { + return false; + } + } + + return true; + } + + public bool IsEqualTo(Set set2) + { + return AreEqual(this, set2); + } + + public bool IsSubsetOf(Set set2) + { + foreach (T item in this) + { + if (!set2.Contains(item)) + { + return false; + } + } + + return true; + } + + public Set Without(Set withoutUs) + { + return Set.Without(this, withoutUs); + } + + /// + /// Adds an element to the set. + /// + /// The object reference to be included in the set. + /// item is a null reference + /// item is already in the Set + public void Add(T item) + { + try + { + this.dictionary.Add(item, null); + } + + catch (ArgumentNullException e1) + { + throw e1; + } + + catch (ArgumentException e2) + { + throw e2; + } + } + + public void AddRange(IEnumerable items) + { + foreach (T item in items) + { + Add(item); + } + } + + public void AddRange(params T[] items) + { + AddRange((IEnumerable)items); + } + + /// + /// Removes an element from the set. + /// + /// The object reference to be excluded from the set. + /// item is a null reference + public bool Remove(T item) + { + try + { + this.dictionary.Remove(item); + return true; + } + + catch (ArgumentNullException) + { + return false; + } + } + + /// + /// Determines whether the Set includes a specific element. + /// + /// The object reference to check for. + /// true if the Set includes item, false if it doesn't. + /// item is a null reference. + public bool Contains(T item) + { + try + { + return this.dictionary.ContainsKey(item); + } + + catch (ArgumentNullException e1) + { + throw e1; + } + } + + /// + /// Constructs an empty Set. + /// + public Set() + { + this.dictionary = new Dictionary(); + } + + /// + /// Constructs a Set with data copied from the given list. + /// + /// + public Set(IEnumerable cloneMe) + { + this.dictionary = new Dictionary(); + + foreach (T theObject in cloneMe) + { + Add(theObject); + } + } + + public Set(params T[] items) + : this((IEnumerable)items) + { + } + + /// + /// Constructs a copy of a Set. + /// + /// The Set to copy from. + private Set(Set copyMe) + { + this.dictionary = new Dictionary(copyMe.dictionary); + } + + #region IEnumerable Members + + /// + /// Returns an IEnumerator that can be used to enumerate through the items in the Set. + /// + /// An IEnumerator for the Set. + IEnumerator IEnumerable.GetEnumerator() + { + return this.dictionary.Keys.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return this.dictionary.Keys.GetEnumerator(); + } + + #endregion + + public Set Clone() + { + return new Set(this); + } + + #region ICloneable Members + + /// + /// Returns a copy of the Set. The elements in the Set are copied by-reference only. + /// + /// + object ICloneable.Clone() + { + return Clone(); + } + + #endregion + + #region ICollection Members + + /// + /// Gets a value indicating whether or not the Set is synchronized (thread-safe). + /// + public bool IsSynchronized + { + get + { + return false; + } + } + + /// + /// Gets a value indicating how many elements are contained within the Set. + /// + public int Count + { + get + { + return this.dictionary.Count; + } + } + + /// + /// Copies the Set elements to a one-dimensional Array instance at a specified index. + /// + /// The one-dimensional Array that is the destination of the objects copied from the Set. The Array must have zero-based indexing. + /// The zero-based index in array at which copying begins. + /// array is a null reference. + /// index is less than zero. + /// The array is not one-dimensional, or the array could not contain the objects copied to it. + /// The Array does not have enough space, starting from the given offset, to contain all the Set's objects. + public void CopyTo(T[] array, int index) + { + int i = index; + + if (array == null) + { + throw new ArgumentNullException("array"); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException("index"); + } + + foreach (T o in this) + { + try + { + array.SetValue(o, i); + } + + catch (ArgumentException e1) + { + throw e1; + } + + catch (IndexOutOfRangeException e2) + { + throw e2; + } + + ++i; + } + } + + /// + /// Gets an object that can be used to synchronize access to the Set. + /// + public object SyncRoot + { + get + { + return this; + } + } + + #endregion + + /// + /// Copies the elements of the Set to a new generic array. + /// + /// An array of object references. + public T[] ToArray() + { + T[] array = new T[Count]; + int index = 0; + + foreach (T o in this) + { + array[index] = o; + ++index; + } + + return array; + } + + #region ICollection Members + + public void Clear() + { + this.dictionary = new Dictionary(); + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + #endregion + } +} diff --git a/src/Base/SiphonStream.cs b/src/Base/SiphonStream.cs new file mode 100644 index 0000000..df20b73 --- /dev/null +++ b/src/Base/SiphonStream.cs @@ -0,0 +1,238 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; + +namespace PaintDotNet +{ + /// + /// This was written as a workaround for a bug in SharpZipLib that prevents it + /// from working right with huge Write() commands. So we split the incoming + /// requests into smaller requests, like 4KB each or so. + /// + /// However, this didn't work around the bug. But now I use this class so that + /// I can keep tabs on a serialization or deserialization operation and have a + /// dialog box with a progress bar. + /// + public sealed class SiphonStream + : Stream + { + private Exception throwMe; + + private Stream stream; + private int siphonSize; + + private object tag = null; + public object Tag + { + get + { + return this.tag; + } + + set + { + this.tag = value; + } + } + + /// + /// Causes the next call to Read() or Write() to throw an IOException instead. The + /// exception passed to this method will be used as the InnerException. + /// + /// + public void Abort(Exception newThrowMe) + { + if (newThrowMe == null) + { + throw new ArgumentException("throwMe may not be null", "throwMe"); + } + + this.throwMe = newThrowMe; + } + + public event IOEventHandler IOFinished; + private void OnIOFinished(IOEventArgs e) + { + if (IOFinished != null) + { + IOFinished(this, e); + } + } + + int readAccumulator = 0; + int writeAccumulator = 0; + + private void ReadAccumulate(int count) + { + if (count == -1) + { + if (this.readAccumulator > 0) + { + OnIOFinished(new IOEventArgs(IOOperationType.Read, this.Position, this.readAccumulator)); + this.readAccumulator = 0; + } + } + else + { + WriteAccumulate(-1); + this.readAccumulator += count; + + while (this.readAccumulator > this.siphonSize) + { + OnIOFinished(new IOEventArgs(IOOperationType.Read, this.Position - this.readAccumulator + this.siphonSize, this.siphonSize)); + this.readAccumulator -= this.siphonSize; + } + } + } + + private void WriteAccumulate(int count) + { + if (count == -1) + { + if (this.writeAccumulator > 0) + { + OnIOFinished(new IOEventArgs(IOOperationType.Write, this.Position, writeAccumulator)); + this.writeAccumulator = 0; + } + } + else + { + ReadAccumulate(-1); + this.writeAccumulator += count; + + while (this.writeAccumulator > this.siphonSize) + { + OnIOFinished(new IOEventArgs(IOOperationType.Write, this.Position - this.writeAccumulator + this.siphonSize, this.siphonSize)); + this.writeAccumulator -= this.siphonSize; + } + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + if (throwMe != null) + { + throw new IOException("Aborted", this.throwMe); + } + + int countLeft = count; + int cursor = offset; + int totalAmountRead = 0; + + while (cursor < offset + count) + { + int count2 = Math.Min(this.siphonSize, countLeft); + int amountRead = stream.Read(buffer, cursor, count2); + ReadAccumulate(amountRead); + countLeft -= amountRead; + cursor += amountRead; + totalAmountRead += amountRead; + + if (amountRead == 0) + { + break; + } + } + + return totalAmountRead; + } + + public override void Write(byte[] buffer, int offset, int count) + { + if (this.throwMe != null) + { + throw new IOException("Aborted", this.throwMe); + } + + int countLeft = count; + int cursor = offset; + + while (cursor < offset + count) + { + int count2 = Math.Min(this.siphonSize, countLeft); + stream.Write(buffer, cursor, count2); + WriteAccumulate(count2); + countLeft -= count2; + cursor += count2; + } + } + + public override bool CanRead + { + get + { + return this.stream.CanRead; + } + } + + public override bool CanWrite + { + get + { + return this.stream.CanWrite; + } + } + + public override bool CanSeek + { + get + { + return this.stream.CanSeek; + } + } + + public override void Flush() + { + this.stream.Flush(); + } + + public override long Length + { + get + { + return this.stream.Length; + } + } + + public override long Position + { + get + { + return this.stream.Position; + } + set + { + this.stream.Position = value; + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + return this.stream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + this.stream.SetLength(value); + } + + public SiphonStream(Stream underlyingStream) + : this(underlyingStream, 65536) + { + } + + public SiphonStream(Stream underlyingStream, int siphonSize) + { + this.stream = underlyingStream; + this.siphonSize = siphonSize; + } + } +} diff --git a/src/Base/Triple.cs b/src/Base/Triple.cs new file mode 100644 index 0000000..d485fac --- /dev/null +++ b/src/Base/Triple.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public static class Triple + { + public static Triple Create(T first, U second, V third) + { + return new Triple(first, second, third); + } + } +} diff --git a/src/Base/Triple`3.cs b/src/Base/Triple`3.cs new file mode 100644 index 0000000..1c9f010 --- /dev/null +++ b/src/Base/Triple`3.cs @@ -0,0 +1,183 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; + +namespace PaintDotNet +{ + [Serializable] + public struct Triple + { + private T first; + private U second; + private V third; + + public T First + { + get + { + return this.first; + } + } + + public U Second + { + get + { + return this.second; + } + } + + public V Third + { + get + { + return this.third; + } + } + + public override int GetHashCode() + { + int firstHash; + int secondHash; + int thirdHash; + + if (object.ReferenceEquals(this.first, null)) + { + firstHash = 0; + } + else + { + firstHash = this.first.GetHashCode(); + } + + if (object.ReferenceEquals(this.second, null)) + { + secondHash = 0; + } + else + { + secondHash = this.second.GetHashCode(); + } + + if (object.ReferenceEquals(this.third, null)) + { + thirdHash = 0; + } + else + { + thirdHash = this.third.GetHashCode(); + } + + return firstHash ^ secondHash ^ thirdHash; + } + + public override bool Equals(object obj) + { + return ((obj != null) && (obj is Triple) && (this == (Triple)obj)); + } + + public static bool operator ==(Triple lhs, Triple rhs) + { + bool firstEqual; + bool secondEqual; + bool thirdEqual; + + if (object.ReferenceEquals(lhs.First, null) && object.ReferenceEquals(rhs.First, null)) + { + firstEqual = true; + } + else if (object.ReferenceEquals(lhs.First, null) || object.ReferenceEquals(rhs.First, null)) + { + firstEqual = false; + } + else + { + firstEqual = lhs.First.Equals(rhs.First); + } + + if (object.ReferenceEquals(lhs.Second, null) && object.ReferenceEquals(rhs.Second, null)) + { + secondEqual = true; + } + else if (object.ReferenceEquals(lhs.Second, null) || object.ReferenceEquals(rhs.Second, null)) + { + secondEqual = false; + } + else + { + secondEqual = lhs.Second.Equals(rhs.Second); + } + + if (object.ReferenceEquals(lhs.Third, null) && object.ReferenceEquals(rhs.Third, null)) + { + thirdEqual = true; + } + else if (object.ReferenceEquals(lhs.Third, null) || object.ReferenceEquals(rhs.Third, null)) + { + thirdEqual = false; + } + else + { + thirdEqual = lhs.Third.Equals(rhs.Third); + } + + return firstEqual && secondEqual && thirdEqual; + } + + public static bool operator !=(Triple lhs, Triple rhs) + { + return !(lhs == rhs); + } + + public Triple(T first, U second, V third) + { + this.first = first; + this.second = second; + this.third = third; + } + + private sealed class TripleComparer + : IEqualityComparer> + { + private IEqualityComparer tComparer; + private IEqualityComparer uComparer; + private IEqualityComparer vComparer; + + public TripleComparer(IEqualityComparer tComparer, IEqualityComparer uComparer, IEqualityComparer vComparer) + { + this.tComparer = tComparer; + this.uComparer = uComparer; + this.vComparer = vComparer; + } + + public bool Equals(Triple x, Triple y) + { + return this.tComparer.Equals(x.First, y.First) && this.uComparer.Equals(x.Second, y.Second) && this.vComparer.Equals(x.Third, y.Third); + } + + public int GetHashCode(Triple obj) + { + return this.tComparer.GetHashCode(obj.First) ^ this.uComparer.GetHashCode(obj.Second) ^ this.vComparer.GetHashCode(obj.Third); + } + } + + public static IEqualityComparer> CreateComparer( + IEqualityComparer tComparer, + IEqualityComparer uComparer, + IEqualityComparer vComparer) + { + return new TripleComparer( + tComparer ?? EqualityComparer.Default, + uComparer ?? EqualityComparer.Default, + vComparer ?? EqualityComparer.Default); + } + } +} diff --git a/src/Base/Vector`1.cs b/src/Base/Vector`1.cs new file mode 100644 index 0000000..c07d590 --- /dev/null +++ b/src/Base/Vector`1.cs @@ -0,0 +1,181 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class Vector + { + private int count = 0; + private T[] array; + + public Vector() + : this(10) + { + } + + public Vector(int capacity) + { + this.array = new T[capacity]; + } + + public Vector(IEnumerable copyMe) + { + foreach (T t in copyMe) + { + Add(t); + } + } + + public void Add(T pt) + { + if (this.count >= this.array.Length) + { + Grow(this.count + 1); + } + + this.array[this.count] = pt; + ++this.count; + } + + public void Insert(int index, T item) + { + if (this.count >= this.array.Length) + { + Grow(this.count + 1); + } + + ++this.count; + + for (int i = this.count - 1; i >= index + 1; --i) + { + this.array[i] = this.array[i - 1]; + } + + this.array[index] = item; + } + + public void Clear() + { + this.count = 0; + } + + public T this[int index] + { + get + { + return Get(index); + } + + set + { + Set(index, value); + } + } + + public T Get(int index) + { + if (index < 0 || index >= this.count) + { + throw new ArgumentOutOfRangeException("index", index, "0 <= index < count"); + } + + return this.array[index]; + } + + public unsafe T GetUnchecked(int index) + { + return this.array[index]; + } + + public void Set(int index, T pt) + { + if (index < 0) + { + throw new ArgumentOutOfRangeException("index", index, "0 <= index"); + } + + if (index >= this.array.Length) + { + Grow(index + 1); + } + + this.array[index] = pt; + } + + public int Count + { + get + { + return this.count; + } + } + + private void Grow(int min) + { + int newSize = this.array.Length; + + if (newSize <= 0) + { + newSize = 1; + } + + while (newSize < min) + { + newSize = 1 + ((newSize * 10) / 8); + } + + T[] replacement = new T[newSize]; + + for (int i = 0; i < this.count; i++) + { + replacement[i] = this.array[i]; + } + + this.array = replacement; + } + + public T[] ToArray() + { + T[] ret = new T[this.count]; + + for (int i = 0; i < this.count; i++) + { + ret[i] = this.array[i]; + } + + return ret; + } + + public unsafe T[] UnsafeArray + { + get + { + return this.array; + } + } + + /// + /// Gets direct access to the array held by the Vector. + /// The caller must not modify the array. + /// + /// The array. + /// The actual number of items stored in the array. This number will be less than or equal to array.Length. + /// This method is supplied strictly for performance-critical purposes. + public unsafe void GetArrayReadOnly(out T[] arrayResult, out int lengthResult) + { + arrayResult = this.array; + lengthResult = this.count; + } + } +} diff --git a/src/Base/WeakReference`1.cs b/src/Base/WeakReference`1.cs new file mode 100644 index 0000000..f9581b6 --- /dev/null +++ b/src/Base/WeakReference`1.cs @@ -0,0 +1,43 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + [Serializable] + public class WeakReference + : WeakReference + { + public WeakReference(T target) + : base(target) + { + } + + public WeakReference(T target, bool trackResurrection) + : base(target, trackResurrection) + { + } + + protected WeakReference(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public new T Target + { + get + { + return (T)base.Target; + } + } + } +} diff --git a/src/Base/Win32Window.cs b/src/Base/Win32Window.cs new file mode 100644 index 0000000..dc00c51 --- /dev/null +++ b/src/Base/Win32Window.cs @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class Win32Window + : IWin32Window + { + private IntPtr handle; + private object container; + + public IntPtr Handle + { + get + { + return this.handle; + } + } + + public Win32Window(IntPtr handle, object container) + { + this.handle = handle; + this.container = container; + } + } +} diff --git a/src/Base/WorkItemFailureAction.cs b/src/Base/WorkItemFailureAction.cs new file mode 100644 index 0000000..ea226c3 --- /dev/null +++ b/src/Base/WorkItemFailureAction.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum WorkItemFailureAction + { + RetryItem, + SkipItem, + CancelOperation + } +} + diff --git a/src/Base/WorkItemResult.cs b/src/Base/WorkItemResult.cs new file mode 100644 index 0000000..43fb29a --- /dev/null +++ b/src/Base/WorkItemResult.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum WorkItemResult + { + Finished, + Skipped, + } +} diff --git a/src/BrushInfo.cs b/src/BrushInfo.cs new file mode 100644 index 0000000..69dd11a --- /dev/null +++ b/src/BrushInfo.cs @@ -0,0 +1,86 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + /// + /// Carries information about the subset of Brush configuration details that we support. + /// Does not carry color information. + /// + [Serializable] + internal class BrushInfo + : ICloneable + { + private BrushType brushType; + private HatchStyle hatchStyle; + + public BrushType BrushType + { + get + { + return brushType; + } + + set + { + brushType = value; + } + } + + /// + /// If BrushType is equal to BrushType.Hatch, then this info is pertinent. + /// + public HatchStyle HatchStyle + { + get + { + return hatchStyle; + } + + set + { + hatchStyle = value; + } + } + + public Brush CreateBrush(Color foreColor, Color backColor) + { + if (brushType == BrushType.Solid) + { + return new SolidBrush(foreColor); + } + else if (brushType == BrushType.Hatch) + { + return new HatchBrush(hatchStyle, foreColor, backColor); + } + + throw new InvalidOperationException("BrushType is invalid"); + } + + public BrushInfo(BrushType brushType, HatchStyle hatchStyle) + { + this.brushType = brushType; + this.hatchStyle = hatchStyle; + } + + public BrushInfo Clone() + { + return new BrushInfo(this.brushType, this.hatchStyle); + } + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/BrushPreviewRenderer.cs b/src/BrushPreviewRenderer.cs new file mode 100644 index 0000000..0551626 --- /dev/null +++ b/src/BrushPreviewRenderer.cs @@ -0,0 +1,128 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + /// + /// Renders a preview of the brush + /// + internal class BrushPreviewRenderer + : SurfaceBoxRenderer + { + private float brushSize; + public float BrushSize + { + get + { + return this.brushSize; + } + + set + { + RectangleF rect1 = GetInvalidateBrushRect(); + this.brushSize = value; + RectangleF rect2 = GetInvalidateBrushRect(); + Invalidate(RectangleF.Union(rect1, rect2)); + } + } + + private PointF brushLocation = new PointF(-500.0f, -500.0f); + public PointF BrushLocation + { + get + { + return this.brushLocation; + } + + set + { + RectangleF rect1 = GetInvalidateBrushRect(); + this.brushLocation = value; + RectangleF rect2 = GetInvalidateBrushRect(); + Invalidate(RectangleF.Union(rect1, rect2)); + } + } + + private int brushAlpha = 255; + public int BrushAlpha + { + get + { + return this.brushAlpha; + } + + set + { + this.brushAlpha = value; + InvalidateBrushLocation(); + } + } + + protected override void OnVisibleChanged() + { + InvalidateBrushLocation(); + } + + private RectangleF GetInvalidateBrushRect() + { + float ratio = (float)this.OwnerList.ScaleFactor.Ratio; + PointF location = this.BrushLocation; + RectangleF rectF = Utility.RectangleFromCenter(location, this.brushSize); + rectF.Inflate(Math.Max(4.0f, 4.0f / ratio), Math.Max(4.0f, 4.0f / ratio)); + return rectF; + } + + private void InvalidateBrushLocation() + { + RectangleF rectF = GetInvalidateBrushRect(); + Invalidate(rectF); + } + + public override void Render(Surface dst, System.Drawing.Point offset) + { + using (RenderArgs ra = new RenderArgs(dst)) + { + ra.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + PointF ptF = this.BrushLocation; + + if (this.BrushSize == 0.5f) + { + ptF.X += 0.5f; + ptF.Y += 0.5f; + } + + ptF.X *= (float)OwnerList.ScaleFactor.Ratio; + ptF.Y *= (float)OwnerList.ScaleFactor.Ratio; + + ra.Graphics.TranslateTransform(-offset.X, -offset.Y, MatrixOrder.Append); + RectangleF brushRect = Utility.RectangleFromCenter(ptF, this.BrushSize * (float)OwnerList.ScaleFactor.Ratio); + + using (Pen white = new Pen(Color.FromArgb(this.brushAlpha, Color.White), -1.0f), + black = new Pen(Color.FromArgb(this.brushAlpha, Color.Black), -1.0f)) + { + brushRect.Inflate(-2, -2); + ra.Graphics.DrawEllipse(white, brushRect); + brushRect.Inflate(1, 1); + ra.Graphics.DrawEllipse(black, brushRect); + brushRect.Inflate(1, 1); + ra.Graphics.DrawEllipse(white, brushRect); + } + } + } + + public BrushPreviewRenderer(SurfaceBoxRendererList ownerList) + : base(ownerList) + { + } + } +} diff --git a/src/BrushType.cs b/src/BrushType.cs new file mode 100644 index 0000000..f985e4d --- /dev/null +++ b/src/BrushType.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum BrushType + { + Solid, + Hatch, + None + } +} diff --git a/src/BuildTools/7z.dll b/src/BuildTools/7z.dll new file mode 100644 index 0000000..7e62921 Binary files /dev/null and b/src/BuildTools/7z.dll differ diff --git a/src/BuildTools/7z.exe b/src/BuildTools/7z.exe new file mode 100644 index 0000000..f1f56e3 Binary files /dev/null and b/src/BuildTools/7z.exe differ diff --git a/src/BuildTools/ResGen.exe b/src/BuildTools/ResGen.exe new file mode 100644 index 0000000..a58d2a5 Binary files /dev/null and b/src/BuildTools/ResGen.exe differ diff --git a/src/BuildTools/Stubs/bzip2 b/src/BuildTools/Stubs/bzip2 new file mode 100644 index 0000000..7731f74 Binary files /dev/null and b/src/BuildTools/Stubs/bzip2 differ diff --git a/src/BuildTools/Stubs/bzip2_solid b/src/BuildTools/Stubs/bzip2_solid new file mode 100644 index 0000000..83c20a0 Binary files /dev/null and b/src/BuildTools/Stubs/bzip2_solid differ diff --git a/src/BuildTools/Stubs/lzma b/src/BuildTools/Stubs/lzma new file mode 100644 index 0000000..44a8c7c Binary files /dev/null and b/src/BuildTools/Stubs/lzma differ diff --git a/src/BuildTools/Stubs/lzma_solid b/src/BuildTools/Stubs/lzma_solid new file mode 100644 index 0000000..6d66165 Binary files /dev/null and b/src/BuildTools/Stubs/lzma_solid differ diff --git a/src/BuildTools/Stubs/uninst b/src/BuildTools/Stubs/uninst new file mode 100644 index 0000000..90d7d22 Binary files /dev/null and b/src/BuildTools/Stubs/uninst differ diff --git a/src/BuildTools/Stubs/zlib b/src/BuildTools/Stubs/zlib new file mode 100644 index 0000000..89f19c8 Binary files /dev/null and b/src/BuildTools/Stubs/zlib differ diff --git a/src/BuildTools/Stubs/zlib_solid b/src/BuildTools/Stubs/zlib_solid new file mode 100644 index 0000000..80e156a Binary files /dev/null and b/src/BuildTools/Stubs/zlib_solid differ diff --git a/src/BuildTools/compress.bat b/src/BuildTools/compress.bat new file mode 100644 index 0000000..e329579 --- /dev/null +++ b/src/BuildTools/compress.bat @@ -0,0 +1,2 @@ +@rem Usage: compress buildToolsDir output.zip filespec +"%1\7z" u -tzip %2 %3 -mx9 -mfb257 -mmt -mpass15 diff --git a/src/BuildTools/cygintl-1.dll b/src/BuildTools/cygintl-1.dll new file mode 100644 index 0000000..cb225f1 Binary files /dev/null and b/src/BuildTools/cygintl-1.dll differ diff --git a/src/BuildTools/cygwin1.dll b/src/BuildTools/cygwin1.dll new file mode 100644 index 0000000..f1a22fe Binary files /dev/null and b/src/BuildTools/cygwin1.dll differ diff --git a/src/BuildTools/makensis.exe b/src/BuildTools/makensis.exe new file mode 100644 index 0000000..d51037d Binary files /dev/null and b/src/BuildTools/makensis.exe differ diff --git a/src/BuildTools/unzip.exe b/src/BuildTools/unzip.exe new file mode 100644 index 0000000..094b7e4 Binary files /dev/null and b/src/BuildTools/unzip.exe differ diff --git a/src/BuildTools/wc.exe b/src/BuildTools/wc.exe new file mode 100644 index 0000000..da52ca2 Binary files /dev/null and b/src/BuildTools/wc.exe differ diff --git a/src/BuildTools/zip.exe b/src/BuildTools/zip.exe new file mode 100644 index 0000000..0fda7fa Binary files /dev/null and b/src/BuildTools/zip.exe differ diff --git a/src/CallbackWithProgressDialog.cs b/src/CallbackWithProgressDialog.cs new file mode 100644 index 0000000..1809644 --- /dev/null +++ b/src/CallbackWithProgressDialog.cs @@ -0,0 +1,252 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal abstract class CallbackWithProgressDialog + { + private ProgressDialog dialog; + private string dialogTitle; + private string dialogDescription; + private int progress; + private Thread thread; + private Control owner; + private ThreadStart threadCallback; + private Exception exception = null; + private Point startPos = Point.Empty; + private bool setStartPos = false; + private Icon icon = null; + private bool cancelled = false; + + /// + /// Used to define the top center of the dialog window when it is created. + /// If this property is not set, then a Windows-chosen location will be used. + /// + public Point StartPos + { + get + { + return startPos; + } + + set + { + setStartPos = true; + startPos = value; + } + } + + public Icon Icon + { + get + { + return icon; + } + + set + { + icon = value; + } + } + + protected Control Owner + { + get + { + return this.owner; + } + } + + protected int Progress + { + get + { + return progress; + } + + set + { + progress = value; + + if (dialog.IsHandleCreated && dialog.InvokeRequired) + { + dialog.BeginInvoke(new Procedure(DoProgressUpdate), null); + } + else if (dialog.IsHandleCreated && !dialog.InvokeRequired) + { + DoProgressUpdate(); + } + } + } + + public bool Cancelled + { + get + { + return this.dialog.Cancelled; + } + } + + public string Description + { + get + { + return this.dialogDescription; + } + + set + { + this.dialogDescription = value; + + if (this.dialog != null) + { + this.dialog.Description = value; + } + } + } + + public bool MarqueeMode + { + get + { + return this.dialog.MarqueeMode; + } + + set + { + this.dialog.MarqueeMode = value; + } + } + + private void DoProgressUpdate() + { + dialog.Value = progress; + dialog.Update(); + } + + private void BackgroundCallback() + { + this.exception = null; + + try + { + threadCallback(); + } + + catch (Exception ex) + { + this.exception = ex; + } + + finally + { + try + { + dialog.BeginInvoke(new Procedure(dialog.ExternalFinish), null); + } + + catch (Exception) + { + } + } + } + + public CallbackWithProgressDialog(Control owner, string dialogTitle, string dialogDescription) + { + this.owner = owner; + this.dialogTitle = dialogTitle; + this.dialogDescription = dialogDescription; + } + + protected DialogResult ShowDialog(bool cancellable, bool marqueeProgress, ThreadStart callback) + { + this.threadCallback = callback; + DialogResult dr = DialogResult.Cancel; + + using (this.dialog = new ProgressDialog()) + { + dialog.Text = dialogTitle; + dialog.Description = dialogDescription; + + if (marqueeProgress) + { + dialog.PercentTextVisible = false; + dialog.MarqueeMode = true; + } + + if (icon != null) + { + dialog.Icon = icon; + } + + EventHandler leh = new EventHandler(dialog_Load); + dialog.Load += leh; + dialog.Cancellable = cancellable; + + if (cancellable) + { + dialog.CancelClick += new EventHandler(dialog_CancelClick); + } + + thread = new Thread(new ThreadStart(BackgroundCallback)); + this.Progress = 0; + + if (setStartPos) + { + dialog.Location = new Point(StartPos.X - (dialog.Width / 2), StartPos.Y);; + dialog.StartPosition = FormStartPosition.Manual; + } + else + { + dialog.StartPosition = FormStartPosition.CenterParent; + } + + dr = dialog.ShowDialog(owner); + dialog.Load -= leh; + + if (cancellable) + { + this.cancelled = dialog.Cancelled; + dialog.CancelClick -= new EventHandler(dialog_CancelClick); + } + + if (exception != null) + { + throw new WorkerThreadException("Worker thread threw an exception", exception); + } + } + + return dr; + } + + private void dialog_Load(object sender, EventArgs e) + { + thread.Start(); + } + + private void dialog_CancelClick(object sender, EventArgs e) + { + OnCancelClick(); + + using (new WaitCursorChanger(this.dialog)) + { + thread.Join(); + } + } + + protected virtual void OnCancelClick() + { + } + } +} diff --git a/src/CanvasControl.cs b/src/CanvasControl.cs new file mode 100644 index 0000000..a0524a7 --- /dev/null +++ b/src/CanvasControl.cs @@ -0,0 +1,268 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal abstract class CanvasControl + : SurfaceBoxGraphicsRenderer + { + private PointF location; + private SizeF size; + private Cursor cursor; + + public event EventHandler CursorChanged; + protected virtual void OnCursorChanged() + { + if (CursorChanged != null) + { + CursorChanged(this, EventArgs.Empty); + } + } + + public Cursor Cursor + { + get + { + return this.cursor; + } + + set + { + if (this.cursor != value) + { + this.cursor = value; + OnCursorChanged(); + } + } + } + + public event EventHandler LocationChanging; + protected virtual void OnLocationChanging() + { + if (LocationChanging != null) + { + LocationChanging(this, EventArgs.Empty); + } + } + + public event EventHandler LocationChanged; + protected virtual void OnLocationChanged() + { + if (LocationChanged != null) + { + LocationChanged(this, EventArgs.Empty); + } + } + + public PointF Location + { + get + { + return this.location; + } + + set + { + if (this.location != value) + { + OnLocationChanging(); + this.location = value; + OnLocationChanged(); + } + } + } + + public event EventHandler SizeChanging; + protected virtual void OnSizeChanging() + { + if (SizeChanging != null) + { + SizeChanging(this, EventArgs.Empty); + } + } + + public event EventHandler SizeChanged; + protected virtual void OnSizeChanged() + { + if (SizeChanged != null) + { + SizeChanged(this, EventArgs.Empty); + } + } + + public SizeF Size + { + get + { + return this.size; + } + + set + { + if (this.size != value) + { + OnSizeChanging(); + this.size = value; + OnSizeChanged(); + } + } + } + + public float Width + { + get + { + return Size.Width; + } + + set + { + Size = new SizeF(value, Size.Height); + } + } + + public float Height + { + get + { + return Size.Height; + } + + set + { + Size = new SizeF(Size.Width, value); + } + } + + public RectangleF Bounds + { + get + { + return new RectangleF(this.location, this.size); + } + + set + { + Location = value.Location; + Size = value.Size; + } + } + + public PointF CanvasPointToControlPoint(PointF canvasPtF) + { + return new PointF(canvasPtF.X - this.location.X, canvasPtF.Y - this.location.Y); + } + + public PointF ControlPointToCanvasPoint(PointF controlPtF) + { + return new PointF(controlPtF.X + this.location.X, controlPtF.Y + this.location.Y); + } + + public RectangleF CanvasRectToControlRect(RectangleF canvasRectF) + { + return new RectangleF(CanvasPointToControlPoint(canvasRectF.Location), canvasRectF.Size); + } + + public RectangleF ControlRectToCanvasRect(RectangleF controlRectF) + { + return new RectangleF(ControlPointToCanvasPoint(controlRectF.Location), controlRectF.Size); + } + + public void PerformMouseEnter() + { + MouseEnter(); + } + + private void MouseEnter() + { + OnMouseEnter(); + } + + protected virtual void OnMouseEnter() + { + } + + public void PerformMouseDown(MouseEventArgs e) + { + MouseDown(e); + } + + private void MouseDown(MouseEventArgs e) + { + MouseDown(e); + } + + protected virtual void OnMouseDown(MouseEventArgs e) + { + } + + public void PerformMouseUp(MouseEventArgs e) + { + MouseUp(e); + } + + private void MouseUp(MouseEventArgs e) + { + OnMouseUp(e); + } + + protected virtual void OnMouseUp(MouseEventArgs e) + { + } + + public void PerformMouseLeave() + { + MouseLeave(); + } + + private void MouseLeave() + { + OnMouseLeave(); + } + + protected virtual void OnMouseLeave() + { + } + + public void PerformPulse() + { + Pulse(); + } + + private void Pulse() + { + OnPulse(); + } + + protected virtual void OnPulse() + { + } + + public override sealed void RenderToGraphics(Graphics g, Point offset) + { + OnRender(g, offset); + } + + protected virtual void OnRender(Graphics g, Point offset) + { + } + + protected CanvasControl(SurfaceBoxRendererList ownerList) + : base(ownerList) + { + } + } +} diff --git a/src/CanvasSizeDialog.cs b/src/CanvasSizeDialog.cs new file mode 100644 index 0000000..768ee73 --- /dev/null +++ b/src/CanvasSizeDialog.cs @@ -0,0 +1,364 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class CanvasSizeDialog + : ResizeDialog + { + private EnumLocalizer anchorEdgeNames = EnumLocalizer.Create(typeof(AnchorEdge)); + private AnchorChooserControl anchorChooserControl; + private System.Windows.Forms.Label newSpaceLabel; + private PaintDotNet.HeaderLabel anchorHeader; + private System.Windows.Forms.ComboBox anchorEdgeCB; + private System.ComponentModel.IContainer components = null; + + [DefaultValue(AnchorEdge.TopLeft)] + public AnchorEdge AnchorEdge + { + get + { + return anchorChooserControl.AnchorEdge; + } + + set + { + anchorChooserControl.AnchorEdge = value; + } + } + + public CanvasSizeDialog() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuImageCanvasSizeIcon.png").Reference, Utility.TransparentKey); + + this.Text = PdnResources.GetString("CanvasSizeDialog.Text"); // "Canvas Size"; + this.anchorHeader.Text = PdnResources.GetString("CanvasSizeDialog.AnchorHeader.Text"); //"Anchor"; + this.newSpaceLabel.Text = PdnResources.GetString("CanvasSizeDialog.NewSpaceLabel.Text"); //"The new space will be filled with the currently selected background color."; + + foreach (string name in Enum.GetNames(typeof(AnchorEdge))) + { + AnchorEdge value = (AnchorEdge)Enum.Parse(typeof(AnchorEdge), name, true); + string itemName = this.anchorEdgeNames.EnumValueToLocalizedName(value); + this.anchorEdgeCB.Items.Add(itemName); + + if (value == this.AnchorEdge) + { + this.anchorEdgeCB.SelectedItem = itemName; + } + } + + anchorChooserControl_AnchorEdgeChanged(anchorChooserControl, EventArgs.Empty); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.anchorChooserControl = new PaintDotNet.AnchorChooserControl(); + this.newSpaceLabel = new System.Windows.Forms.Label(); + this.anchorHeader = new PaintDotNet.HeaderLabel(); + this.anchorEdgeCB = new System.Windows.Forms.ComboBox(); + ((System.ComponentModel.ISupportInitialize)(this.percentUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.resolutionUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelWidthUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelHeightUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.printWidthUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.printHeightUpDown)).BeginInit(); + this.SuspendLayout(); + // + // constrainCheckBox + // + this.constrainCheckBox.Location = new System.Drawing.Point(27, 74); + this.constrainCheckBox.Name = "constrainCheckBox"; + // + // okButton + // + this.okButton.Location = new System.Drawing.Point(142, 366); + this.okButton.Name = "okButton"; + this.okButton.TabIndex = 18; + // + // cancelButton + // + this.cancelButton.Location = new System.Drawing.Point(220, 366); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 19; + // + // percentSignLabel + // + this.percentSignLabel.Location = new System.Drawing.Point(200, 28); + this.percentSignLabel.Name = "percentSignLabel"; + this.percentSignLabel.TabIndex = 23; + // + // percentUpDown + // + this.percentUpDown.Location = new System.Drawing.Point(120, 27); + this.percentUpDown.Name = "percentUpDown"; + this.percentUpDown.TabIndex = 22; + // + // absoluteRB + // + this.absoluteRB.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.absoluteRB.Location = new System.Drawing.Point(8, 51); + this.absoluteRB.Name = "absoluteRB"; + // + // percentRB + // + this.percentRB.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.percentRB.Location = new System.Drawing.Point(8, 24); + this.percentRB.Name = "percentRB"; + this.percentRB.TabIndex = 21; + // + // asteriskTextLabel + // + this.asteriskTextLabel.Enabled = false; + this.asteriskTextLabel.Location = new System.Drawing.Point(400, 72); + this.asteriskTextLabel.Name = "asteriskTextLabel"; + this.asteriskTextLabel.Visible = true; + // + // asteriskLabel + // + this.asteriskLabel.Enabled = false; + this.asteriskLabel.Location = new System.Drawing.Point(648, 32); + this.asteriskLabel.Name = "asteriskLabel"; + this.asteriskLabel.Visible = true; + // + // resizedImageHeader + // + this.resizedImageHeader.Name = "resizedImageHeader"; + this.resizedImageHeader.TabIndex = 20; + // + // resolutionLabel + // + this.resolutionLabel.Location = new System.Drawing.Point(32, 166); + this.resolutionLabel.Name = "resolutionLabel"; + // + // unitsComboBox2 + // + this.unitsComboBox2.Location = new System.Drawing.Point(200, 165); + this.unitsComboBox2.Name = "unitsComboBox2"; + // + // unitsComboBox1 + // + this.unitsComboBox1.Location = new System.Drawing.Point(200, 208); + this.unitsComboBox1.Name = "unitsComboBox1"; + // + // resolutionUpDown + // + this.resolutionUpDown.Location = new System.Drawing.Point(120, 165); + this.resolutionUpDown.Name = "resolutionUpDown"; + // + // newWidthLabel1 + // + this.newWidthLabel1.Location = new System.Drawing.Point(32, 118); + this.newWidthLabel1.Name = "newWidthLabel1"; + // + // newHeightLabel1 + // + this.newHeightLabel1.Location = new System.Drawing.Point(32, 142); + this.newHeightLabel1.Name = "newHeightLabel1"; + // + // pixelsLabel1 + // + this.pixelsLabel1.Location = new System.Drawing.Point(200, 118); + this.pixelsLabel1.Name = "pixelsLabel1"; + // + // newWidthLabel2 + // + this.newWidthLabel2.Location = new System.Drawing.Point(32, 209); + this.newWidthLabel2.Name = "newWidthLabel2"; + // + // newHeightLabel2 + // + this.newHeightLabel2.Location = new System.Drawing.Point(32, 233); + this.newHeightLabel2.Name = "newHeightLabel2"; + // + // pixelsLabel2 + // + this.pixelsLabel2.Location = new System.Drawing.Point(200, 142); + this.pixelsLabel2.Name = "pixelsLabel2"; + // + // unitsLabel1 + // + this.unitsLabel1.Location = new System.Drawing.Point(200, 234); + this.unitsLabel1.Name = "unitsLabel1"; + // + // pixelWidthUpDown + // + this.pixelWidthUpDown.Location = new System.Drawing.Point(120, 117); + this.pixelWidthUpDown.Name = "pixelWidthUpDown"; + // + // pixelHeightUpDown + // + this.pixelHeightUpDown.Location = new System.Drawing.Point(120, 141); + this.pixelHeightUpDown.Name = "pixelHeightUpDown"; + // + // printWidthUpDown + // + this.printWidthUpDown.Location = new System.Drawing.Point(120, 208); + this.printWidthUpDown.Name = "printWidthUpDown"; + // + // printHeightUpDown + // + this.printHeightUpDown.Location = new System.Drawing.Point(120, 232); + this.printHeightUpDown.Name = "printHeightUpDown"; + // + // pixelSizeHeader + // + this.pixelSizeHeader.Location = new System.Drawing.Point(25, 98); + this.pixelSizeHeader.Name = "pixelSizeHeader"; + // + // printSizeHeader + // + this.printSizeHeader.Location = new System.Drawing.Point(25, 189); + this.printSizeHeader.Name = "printSizeHeader"; + // + // resamplingLabel + // + this.resamplingLabel.Enabled = false; + this.resamplingLabel.Location = new System.Drawing.Point(384, 40); + this.resamplingLabel.Name = "resamplingLabel"; + this.resamplingLabel.Visible = false; + // + // resamplingAlgorithmComboBox + // + this.resamplingAlgorithmComboBox.Enabled = false; + this.resamplingAlgorithmComboBox.Location = new System.Drawing.Point(496, 32); + this.resamplingAlgorithmComboBox.Name = "resamplingAlgorithmComboBox"; + this.resamplingAlgorithmComboBox.Visible = false; + // + // anchorChooserControl + // + this.anchorChooserControl.Location = new System.Drawing.Point(177, 275); + this.anchorChooserControl.Name = "anchorChooserControl"; + this.anchorChooserControl.Size = new System.Drawing.Size(81, 81); + this.anchorChooserControl.TabIndex = 17; + this.anchorChooserControl.TabStop = false; + this.anchorChooserControl.AnchorEdgeChanged += new System.EventHandler(this.anchorChooserControl_AnchorEdgeChanged); + // + // newSpaceLabel + // + this.newSpaceLabel.Location = new System.Drawing.Point(376, 296); + this.newSpaceLabel.Name = "newSpaceLabel"; + this.newSpaceLabel.Size = new System.Drawing.Size(234, 32); + this.newSpaceLabel.TabIndex = 20; + // anchorHeader + // + this.anchorHeader.Location = new System.Drawing.Point(8, 256); + this.anchorHeader.Name = "anchorHeader"; + this.anchorHeader.Size = new System.Drawing.Size(288, 14); + this.anchorHeader.TabIndex = 15; + this.anchorHeader.TabStop = false; + // + // + // anchorEdgeCB + // + this.anchorEdgeCB.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.anchorEdgeCB.Location = new System.Drawing.Point(32, 275); + this.anchorEdgeCB.Name = "anchorEdgeCB"; + this.anchorEdgeCB.Size = new System.Drawing.Size(120, 21); + this.anchorEdgeCB.TabIndex = 16; + this.anchorEdgeCB.SelectedIndexChanged += new System.EventHandler(this.anchorEdgeCB_SelectedIndexChanged); + // + // CanvasSizeDialog + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(298, 395); + this.Controls.Add(this.anchorEdgeCB); + this.Controls.Add(this.anchorHeader); + this.Controls.Add(this.anchorChooserControl); + this.Controls.Add(this.newSpaceLabel); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "CanvasSizeDialog"; + this.Controls.SetChildIndex(this.pixelsLabel1, 0); + this.Controls.SetChildIndex(this.unitsLabel1, 0); + this.Controls.SetChildIndex(this.newWidthLabel1, 0); + this.Controls.SetChildIndex(this.resamplingLabel, 0); + this.Controls.SetChildIndex(this.resolutionLabel, 0); + this.Controls.SetChildIndex(this.asteriskTextLabel, 0); + this.Controls.SetChildIndex(this.asteriskLabel, 0); + this.Controls.SetChildIndex(this.pixelsLabel2, 0); + this.Controls.SetChildIndex(this.percentSignLabel, 0); + this.Controls.SetChildIndex(this.newSpaceLabel, 0); + this.Controls.SetChildIndex(this.newHeightLabel1, 0); + this.Controls.SetChildIndex(this.newWidthLabel2, 0); + this.Controls.SetChildIndex(this.newHeightLabel2, 0); + this.Controls.SetChildIndex(this.resizedImageHeader, 0); + this.Controls.SetChildIndex(this.resolutionUpDown, 0); + this.Controls.SetChildIndex(this.unitsComboBox2, 0); + this.Controls.SetChildIndex(this.unitsComboBox1, 0); + this.Controls.SetChildIndex(this.printWidthUpDown, 0); + this.Controls.SetChildIndex(this.printHeightUpDown, 0); + this.Controls.SetChildIndex(this.pixelSizeHeader, 0); + this.Controls.SetChildIndex(this.printSizeHeader, 0); + this.Controls.SetChildIndex(this.pixelHeightUpDown, 0); + this.Controls.SetChildIndex(this.pixelWidthUpDown, 0); + this.Controls.SetChildIndex(this.anchorChooserControl, 0); + this.Controls.SetChildIndex(this.constrainCheckBox, 0); + this.Controls.SetChildIndex(this.resamplingAlgorithmComboBox, 0); + this.Controls.SetChildIndex(this.percentRB, 0); + this.Controls.SetChildIndex(this.absoluteRB, 0); + this.Controls.SetChildIndex(this.percentUpDown, 0); + this.Controls.SetChildIndex(this.anchorHeader, 0); + this.Controls.SetChildIndex(this.anchorEdgeCB, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + ((System.ComponentModel.ISupportInitialize)(this.percentUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.resolutionUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelWidthUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelHeightUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.printWidthUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.printHeightUpDown)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + private void anchorChooserControl_AnchorEdgeChanged(object sender, System.EventArgs e) + { + string newItem = this.anchorEdgeNames.EnumValueToLocalizedName(anchorChooserControl.AnchorEdge); + this.anchorEdgeCB.SelectedItem = newItem; + } + + private void anchorEdgeCB_SelectedIndexChanged(object sender, System.EventArgs e) + { + AnchorEdge newAnchorEdge = (AnchorEdge)this.anchorEdgeNames.LocalizedNameToEnumValue((string)this.anchorEdgeCB.SelectedItem); + this.AnchorEdge = newAnchorEdge; + } + } +} + diff --git a/src/CanvasSizeDialog.resx b/src/CanvasSizeDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ChooseToolDefaultsDialog.cs b/src/ChooseToolDefaultsDialog.cs new file mode 100644 index 0000000..c8917fa --- /dev/null +++ b/src/ChooseToolDefaultsDialog.cs @@ -0,0 +1,465 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ChooseToolDefaultsDialog + : PdnBaseForm + { + private Button cancelButton; + private Button saveButton; + private Label introText; + private Label defaultToolText; + private Button resetButton; + private Button loadFromToolBarButton; + private ToolChooserStrip toolChooserStrip; + private Type toolType = Tool.DefaultToolType; + private AppEnvironment toolBarAppEnvironment; + private Type toolBarToolType; + private HeaderLabel bottomSeparator; + private List toolConfigRows = new List(); + + private sealed class ToolConfigRow + { + private ToolConfigStrip toolConfigStrip; + private HeaderLabel headerLabel; + private ToolBarConfigItems toolBarConfigItems; + + public ToolBarConfigItems ToolBarConfigItems + { + get + { + return this.toolBarConfigItems; + } + } + + public HeaderLabel HeaderLabel + { + get + { + return this.headerLabel; + } + } + + public ToolConfigStrip ToolConfigStrip + { + get + { + return this.toolConfigStrip; + } + } + + private string GetHeaderResourceName() + { + string resName1 = this.toolBarConfigItems.ToString(); + string resName2 = resName1.Replace(", ", ""); + return "ChooseToolDefaultsDialog.ToolConfigRow." + resName2 + ".HeaderLabel.Text"; + } + + public ToolConfigRow(ToolBarConfigItems toolBarConfigItems) + { + this.toolBarConfigItems = toolBarConfigItems; + + this.headerLabel = new HeaderLabel(); + this.headerLabel.Name = "headerLabel:" + toolBarConfigItems.ToString(); + this.headerLabel.Text = PdnResources.GetString(GetHeaderResourceName()); + this.headerLabel.RightMargin = 0; + + this.toolConfigStrip = new ToolConfigStrip(); + this.toolConfigStrip.Name = "toolConfigStrip:" + toolBarConfigItems.ToString(); + this.toolConfigStrip.AutoSize = true; + this.toolConfigStrip.Dock = DockStyle.None; + this.toolConfigStrip.GripStyle = ToolStripGripStyle.Hidden; + this.toolConfigStrip.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow; + this.toolConfigStrip.ToolBarConfigItems = this.toolBarConfigItems; + } + } + + public void SetToolBarSettings(Type newToolType, AppEnvironment newToolBarAppEnvironment) + { + this.toolBarToolType = newToolType; + this.toolBarAppEnvironment = newToolBarAppEnvironment.Clone(); + } + + public void LoadUIFromAppEnvironment(AppEnvironment newAppEnvironment) + { + SuspendLayout(); + + foreach (ToolConfigRow row in this.toolConfigRows) + { + row.ToolConfigStrip.LoadFromAppEnvironment(newAppEnvironment); + } + + ResumeLayout(); + } + + public void SetDefaultToolType(Type newDefaultToolType) + { + this.toolChooserStrip.SelectTool(newDefaultToolType); + } + + public AppEnvironment CreateAppEnvironmentFromUI() + { + AppEnvironment newAppEnvironment = new AppEnvironment(); + + foreach (ToolConfigRow row in this.toolConfigRows) + { + if ((row.ToolBarConfigItems & ToolBarConfigItems.AlphaBlending) != 0) + { + newAppEnvironment.AlphaBlending = row.ToolConfigStrip.AlphaBlending; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Antialiasing) != 0) + { + newAppEnvironment.AntiAliasing = row.ToolConfigStrip.AntiAliasing; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Brush) != 0) + { + newAppEnvironment.BrushInfo = row.ToolConfigStrip.BrushInfo; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.ColorPickerBehavior) != 0) + { + newAppEnvironment.ColorPickerClickBehavior = row.ToolConfigStrip.ColorPickerClickBehavior; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.FloodMode) != 0) + { + newAppEnvironment.FloodMode = row.ToolConfigStrip.FloodMode; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Gradient) != 0) + { + newAppEnvironment.GradientInfo = row.ToolConfigStrip.GradientInfo; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Pen) != 0 || + (row.ToolBarConfigItems & ToolBarConfigItems.PenCaps) != 0) + { + newAppEnvironment.PenInfo = row.ToolConfigStrip.PenInfo; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Resampling) != 0) + { + newAppEnvironment.ResamplingAlgorithm = row.ToolConfigStrip.ResamplingAlgorithm; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.SelectionCombineMode) != 0) + { + newAppEnvironment.SelectionCombineMode = row.ToolConfigStrip.SelectionCombineMode; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.SelectionDrawMode) != 0) + { + newAppEnvironment.SelectionDrawModeInfo = row.ToolConfigStrip.SelectionDrawModeInfo; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.ShapeType) != 0) + { + newAppEnvironment.ShapeDrawType = row.ToolConfigStrip.ShapeDrawType; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Text) != 0) + { + newAppEnvironment.FontInfo = row.ToolConfigStrip.FontInfo; + newAppEnvironment.FontSmoothing = row.ToolConfigStrip.FontSmoothing; + newAppEnvironment.TextAlignment = row.ToolConfigStrip.FontAlignment; + } + + if ((row.ToolBarConfigItems & ToolBarConfigItems.Tolerance) != 0) + { + newAppEnvironment.Tolerance = row.ToolConfigStrip.Tolerance; + } + } + + return newAppEnvironment; + } + + public Type ToolType + { + get + { + return this.toolType; + } + + set + { + this.toolChooserStrip.SelectTool(value); + this.toolType = value; + } + } + + public ChooseToolDefaultsDialog() + { + UI.InitScaling(this); + SuspendLayout(); + + InitializeComponent(); + + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.ShapeType | ToolBarConfigItems.Brush | ToolBarConfigItems.Pen | ToolBarConfigItems.PenCaps)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.SelectionCombineMode | ToolBarConfigItems.SelectionDrawMode)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.Text)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.Gradient)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.Tolerance | ToolBarConfigItems.FloodMode)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.ColorPickerBehavior)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.Resampling)); + this.toolConfigRows.Add(new ToolConfigRow(ToolBarConfigItems.AlphaBlending | ToolBarConfigItems.Antialiasing)); + + for (int i = 0; i < this.toolConfigRows.Count; ++i) + { + Controls.Add(this.toolConfigRows[i].HeaderLabel); + Controls.Add(this.toolConfigRows[i].ToolConfigStrip); + } + + ResumeLayout(); + PerformLayout(); + + this.toolChooserStrip.SetTools(DocumentWorkspace.ToolInfos); + + PdnBaseForm.RegisterFormHotKey( + Keys.Escape, + delegate(Keys keys) + { + this.cancelButton.PerformClick(); + return true; + }); + } + + protected override void OnLoad(EventArgs e) + { + this.saveButton.Select(); + base.OnLoad(e); + } + + public override void LoadResources() + { + this.Text = PdnResources.GetString("ChooseToolDefaultsDialog.Text"); + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuLayersLayerPropertiesIcon.png").Reference); + + this.introText.Text = PdnResources.GetString("ChooseToolDefaultsDialog.IntroText.Text"); + this.defaultToolText.Text = PdnResources.GetString("ChooseToolDefaultsDialog.DefaultToolText.Text"); + + this.loadFromToolBarButton.Text = PdnResources.GetString("ChooseToolDefaultsDialog.LoadFromToolBarButton.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.saveButton.Text = PdnResources.GetString("Form.SaveButton.Text"); + this.resetButton.Text = PdnResources.GetString("Form.ResetButton.Text"); + + base.LoadResources(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int leftMargin = UI.ScaleWidth(8); + int rightMargin = UI.ScaleWidth(8); + int topMargin = UI.ScaleHeight(8); + int bottomMargin = UI.ScaleHeight(8); + int buttonHMargin = UI.ScaleWidth(7); + int afterIntroTextVMargin = UI.ScaleHeight(16); + int afterHeaderVMargin = UI.ScaleHeight(3); + int hMargin = UI.ScaleWidth(7); + int vMargin = UI.ScaleHeight(7); + int insetWidth = ClientSize.Width - leftMargin - rightMargin; + + this.introText.Location = new Point(leftMargin, topMargin); + this.introText.Width = insetWidth; + this.introText.Height = this.introText.GetPreferredSize(this.introText.Size).Height; + + this.defaultToolText.Location = new Point( + leftMargin, + this.introText.Bottom + afterIntroTextVMargin); + + this.toolChooserStrip.Location = new Point( + this.defaultToolText.Right + hMargin, + this.defaultToolText.Top + (this.defaultToolText.Height - this.toolChooserStrip.Height) / 2); + + int y = vMargin + Math.Max(this.defaultToolText.Bottom, this.toolChooserStrip.Bottom); + int maxInsetWidth = insetWidth; + + for (int i = 0; i < this.toolConfigRows.Count; ++i) + { + this.toolConfigRows[i].HeaderLabel.Location = new Point(leftMargin, y); + this.toolConfigRows[i].HeaderLabel.Width = insetWidth; + y = this.toolConfigRows[i].HeaderLabel.Bottom + afterHeaderVMargin; + + this.toolConfigRows[i].ToolConfigStrip.Location = new Point(leftMargin + 3, y); + Size preferredSize = this.toolConfigRows[i].ToolConfigStrip.GetPreferredSize( + new Size(this.toolConfigRows[i].ToolConfigStrip.Width, 1)); + + this.toolConfigRows[i].ToolConfigStrip.Size = preferredSize; + + maxInsetWidth = Math.Max(maxInsetWidth, this.toolConfigRows[i].ToolConfigStrip.Width); + + y = this.toolConfigRows[i].ToolConfigStrip.Bottom + vMargin; + } + + y += vMargin; + + this.bottomSeparator.Location = new Point(leftMargin, y); + this.bottomSeparator.Width = insetWidth; + this.bottomSeparator.Visible = false; + + y += this.bottomSeparator.Height; + + this.cancelButton.Location = new Point(ClientSize.Width - rightMargin - this.cancelButton.Width, y); + + this.saveButton.Location = new Point( + this.cancelButton.Left - buttonHMargin - this.saveButton.Width, + this.cancelButton.Top); + + this.resetButton.Location = new Point(leftMargin, this.saveButton.Top); + + this.loadFromToolBarButton.Location = new Point(this.resetButton.Right + buttonHMargin, this.resetButton.Top); + + y = this.resetButton.Bottom + bottomMargin; + + this.ClientSize = new Size(leftMargin + maxInsetWidth + rightMargin, y); + + if (IsHandleCreated && maxInsetWidth > insetWidth) + { + BeginInvoke(new Procedure(PerformLayout), null); + } + + base.OnLayout(levent); + } + + private void InitializeComponent() + { + this.cancelButton = new Button(); + this.saveButton = new Button(); + this.introText = new Label(); + this.defaultToolText = new Label(); + this.resetButton = new Button(); + this.loadFromToolBarButton = new Button(); + this.toolChooserStrip = new ToolChooserStrip(); + this.bottomSeparator = new HeaderLabel(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.Name = "cancelButton"; + this.cancelButton.AutoSize = true; + this.cancelButton.Click += new EventHandler(CancelButton_Click); + this.cancelButton.TabIndex = 3; + this.cancelButton.FlatStyle = FlatStyle.System; + // + // saveButton + // + this.saveButton.Name = "saveButton"; + this.saveButton.AutoSize = true; + this.saveButton.Click += new EventHandler(SaveButton_Click); + this.saveButton.TabIndex = 2; + this.saveButton.FlatStyle = FlatStyle.System; + // + // introText + // + this.introText.Name = "introText"; + this.introText.TabStop = false; + // + // defaultToolText + // + this.defaultToolText.Name = "defaultToolText"; + this.defaultToolText.AutoSize = true; + this.defaultToolText.TabStop = false; + // + // resetButton + // + this.resetButton.Name = "resetButton"; + this.resetButton.AutoSize = true; + this.resetButton.Click += new EventHandler(ResetButton_Click); + this.resetButton.TabIndex = 0; + this.resetButton.FlatStyle = FlatStyle.System; + // + // loadFromToolBarButton + // + this.loadFromToolBarButton.Name = "loadFromToolBarButton"; + this.loadFromToolBarButton.AutoSize = true; + this.loadFromToolBarButton.Click += new EventHandler(LoadFromToolBarButton_Click); + this.loadFromToolBarButton.FlatStyle = FlatStyle.System; + this.loadFromToolBarButton.TabIndex = 1; + // + // toolChooserStrip + // + this.toolChooserStrip.Name = "toolChooserStrip"; + this.toolChooserStrip.Dock = DockStyle.None; + this.toolChooserStrip.GripStyle = ToolStripGripStyle.Hidden; + this.toolChooserStrip.ShowChooseDefaults = false; + this.toolChooserStrip.UseToolNameForLabel = true; + this.toolChooserStrip.ToolClicked += new ToolClickedEventHandler(ToolChooserStrip_ToolClicked); + // + // bottomSeparator + // + this.bottomSeparator.Name = "bottomSeparator"; + this.bottomSeparator.RightMargin = 0; + // + // ChooseToolDefaultsDialog + // + this.AcceptButton = this.saveButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(448, 173); + this.Controls.Add(this.resetButton); + this.Controls.Add(this.loadFromToolBarButton); + this.Controls.Add(this.introText); + this.Controls.Add(this.defaultToolText); + this.Controls.Add(this.saveButton); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.toolChooserStrip); + this.Controls.Add(this.bottomSeparator); + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.Location = new System.Drawing.Point(0, 0); + this.MinimizeBox = false; + this.MaximizeBox = false; + this.Name = "ChooseToolDefaultsDialog"; + this.ShowInTaskbar = false; + this.StartPosition = FormStartPosition.CenterParent; + this.ResumeLayout(false); + this.PerformLayout(); + } + + private void LoadFromToolBarButton_Click(object sender, EventArgs e) + { + ToolType = this.toolBarToolType; + LoadUIFromAppEnvironment(this.toolBarAppEnvironment); + } + + private void ToolChooserStrip_ToolClicked(object sender, ToolClickedEventArgs e) + { + ToolType = e.ToolType; + } + + private void ResetButton_Click(object sender, EventArgs e) + { + AppEnvironment defaults = new AppEnvironment(); + defaults.SetToDefaults(); + ToolType = Tool.DefaultToolType; + LoadUIFromAppEnvironment(defaults); + } + + private void CancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void SaveButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Yes; + Close(); + } + } +} diff --git a/src/ChooseToolDefaultsDialog.resx b/src/ChooseToolDefaultsDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ColorDisplayWidget.cs b/src/ColorDisplayWidget.cs new file mode 100644 index 0000000..03d0555 --- /dev/null +++ b/src/ColorDisplayWidget.cs @@ -0,0 +1,273 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ColorDisplayWidget + : System.Windows.Forms.UserControl + { + private System.ComponentModel.IContainer components; + + private ColorRectangleControl primaryColorRectangle; + private ColorRectangleControl secondaryColorRectangle; + private IconBox blackAndWhiteIconBox; + private ToolTip toolTip; + private IconBox swapIconBox; + + protected override Size DefaultSize + { + get + { + return new Size(48, 48); + } + } + + public event EventHandler UserPrimaryColorChanged; + protected virtual void OnUserPrimaryColorChanged() + { + if (UserPrimaryColorChanged != null) + { + UserPrimaryColorChanged(this, EventArgs.Empty); + } + } + + private ColorBgra userPrimaryColor; + public ColorBgra UserPrimaryColor + { + get + { + return this.userPrimaryColor; + } + + set + { + ColorBgra oldColor = this.userPrimaryColor; + this.userPrimaryColor = value; + this.primaryColorRectangle.RectangleColor = value.ToColor(); + Invalidate(); + Update(); + } + } + + public event EventHandler UserSecondaryColorChanged; + protected virtual void OnUserSecondaryColorChanged() + { + if (UserSecondaryColorChanged != null) + { + UserSecondaryColorChanged(this, EventArgs.Empty); + } + } + + private ColorBgra userSecondaryColor; + public ColorBgra UserSecondaryColor + { + get + { + return userSecondaryColor; + } + + set + { + ColorBgra oldColor = this.userSecondaryColor; + this.userSecondaryColor = value; + this.secondaryColorRectangle.RectangleColor = value.ToColor(); + Invalidate(); + Update(); + } + } + + public ColorDisplayWidget() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + this.swapIconBox.Icon = new Bitmap(PdnResources.GetImageResource("Icons.SwapIcon.png").Reference); + this.blackAndWhiteIconBox.Icon = new Bitmap(PdnResources.GetImageResource("Icons.BlackAndWhiteIcon.png").Reference); + + if (!DesignMode) + { + this.toolTip.SetToolTip(swapIconBox, PdnResources.GetString("ColorDisplayWidget.SwapIconBox.ToolTipText")); + this.toolTip.SetToolTip(blackAndWhiteIconBox, PdnResources.GetString("ColorDisplayWidget.BlackAndWhiteIconBox.ToolTipText")); + this.toolTip.SetToolTip(primaryColorRectangle, PdnResources.GetString("ColorDisplayWidget.ForeColorRectangle.ToolTipText")); + this.toolTip.SetToolTip(secondaryColorRectangle, PdnResources.GetString("ColorDisplayWidget.BackColorRectangle.ToolTipText")); + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int ulX = (this.ClientRectangle.Width - UI.ScaleWidth(this.DefaultSize.Width)) / 2; + int ulY = (this.ClientRectangle.Height - UI.ScaleHeight(this.DefaultSize.Height)) / 2; + + this.primaryColorRectangle.Location = new System.Drawing.Point(UI.ScaleWidth(ulX + 2), UI.ScaleHeight(ulY + 2)); + this.secondaryColorRectangle.Location = new System.Drawing.Point(UI.ScaleWidth(ulX + 18), UI.ScaleHeight(ulY + 18)); + this.swapIconBox.Location = new System.Drawing.Point(UI.ScaleWidth(ulX + 30), UI.ScaleHeight(ulY + 2)); + this.blackAndWhiteIconBox.Location = new System.Drawing.Point(UI.ScaleWidth(ulX + 2), UI.ScaleHeight(ulY + 31)); + + base.OnLayout(levent); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.primaryColorRectangle = new PaintDotNet.ColorRectangleControl(); + this.secondaryColorRectangle = new PaintDotNet.ColorRectangleControl(); + this.swapIconBox = new PaintDotNet.IconBox(); + this.blackAndWhiteIconBox = new PaintDotNet.IconBox(); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.SuspendLayout(); + // + // foreColorRectangle + // + this.primaryColorRectangle.Name = "foreColorRectangle"; + this.primaryColorRectangle.RectangleColor = System.Drawing.Color.FromArgb(((System.Byte)(0)), ((System.Byte)(0)), ((System.Byte)(192))); + this.primaryColorRectangle.Size = new System.Drawing.Size(28, 28); + this.primaryColorRectangle.TabIndex = 0; + this.primaryColorRectangle.Click += new System.EventHandler(this.PrimaryColorRectangle_Click); + this.primaryColorRectangle.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Control_KeyUp); + // + // backColorRectangle + // + this.secondaryColorRectangle.Name = "backColorRectangle"; + this.secondaryColorRectangle.RectangleColor = System.Drawing.Color.Magenta; + this.secondaryColorRectangle.Size = new System.Drawing.Size(28, 28); + this.secondaryColorRectangle.TabIndex = 1; + this.secondaryColorRectangle.Click += new System.EventHandler(this.SecondaryColorRectangle_Click); + this.secondaryColorRectangle.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Control_KeyUp); + // + // swapIconBox + // + this.swapIconBox.Icon = null; + this.swapIconBox.Name = "swapIconBox"; + this.swapIconBox.Size = new System.Drawing.Size(15, 15); + this.swapIconBox.TabIndex = 2; + this.swapIconBox.TabStop = false; + this.swapIconBox.Click += new System.EventHandler(this.SwapIconBox_Click); + this.swapIconBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Control_KeyUp); + this.swapIconBox.DoubleClick += new System.EventHandler(this.SwapIconBox_Click); + // + // blackAndWhiteIconBox + // + this.blackAndWhiteIconBox.Icon = null; + this.blackAndWhiteIconBox.Name = "blackAndWhiteIconBox"; + this.blackAndWhiteIconBox.Size = new System.Drawing.Size(15, 15); + this.blackAndWhiteIconBox.TabIndex = 3; + this.blackAndWhiteIconBox.TabStop = false; + this.blackAndWhiteIconBox.Click += new System.EventHandler(this.BlackAndWhiteIconBox_Click); + this.blackAndWhiteIconBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.Control_KeyUp); + this.blackAndWhiteIconBox.DoubleClick += new System.EventHandler(this.BlackAndWhiteIconBox_Click); + // + // toolTip + // + this.toolTip.ShowAlways = true; + // + // ColorDisplayWidget + // + this.Controls.Add(this.blackAndWhiteIconBox); + this.Controls.Add(this.swapIconBox); + this.Controls.Add(this.primaryColorRectangle); + this.Controls.Add(this.secondaryColorRectangle); + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = AutoScaleMode.Dpi; + this.Name = "ColorDisplayWidget"; + this.Size = new System.Drawing.Size(48, 48); + this.ResumeLayout(false); + + } + #endregion + + public event EventHandler SwapColorsClicked; + protected virtual void OnSwapColorsClicked() + { + if (SwapColorsClicked != null) + { + SwapColorsClicked(this, EventArgs.Empty); + } + } + + private void SwapIconBox_Click(object sender, System.EventArgs e) + { + OnSwapColorsClicked(); + } + + public event EventHandler BlackAndWhiteButtonClicked; + protected virtual void OnBlackAndWhiteButtonClicked() + { + if (BlackAndWhiteButtonClicked != null) + { + BlackAndWhiteButtonClicked(this, EventArgs.Empty); + } + } + + private void BlackAndWhiteIconBox_Click(object sender, System.EventArgs e) + { + OnBlackAndWhiteButtonClicked(); + } + + public event EventHandler UserPrimaryColorClick; + protected virtual void OnUserPrimaryColorClick() + { + if (UserPrimaryColorClick != null) + { + UserPrimaryColorClick(this, EventArgs.Empty); + } + } + + private void PrimaryColorRectangle_Click(object sender, System.EventArgs e) + { + OnUserPrimaryColorClick(); + } + + public event EventHandler UserSecondaryColorClick; + protected virtual void OnUserSecondaryColorClick() + { + if (UserSecondaryColorClick != null) + { + UserSecondaryColorClick(this, EventArgs.Empty); + } + } + + private void SecondaryColorRectangle_Click(object sender, System.EventArgs e) + { + OnUserSecondaryColorClick(); + } + + private void Control_KeyUp(object sender, KeyEventArgs e) + { + this.OnKeyUp(e); + } + } +} diff --git a/src/ColorDisplayWidget.resx b/src/ColorDisplayWidget.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ColorEventArgs.cs b/src/ColorEventArgs.cs new file mode 100644 index 0000000..84f5d06 --- /dev/null +++ b/src/ColorEventArgs.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + [Serializable] + internal class ColorEventArgs + : System.EventArgs + { + private ColorBgra color; + public ColorBgra Color + { + get + { + return color; + } + } + + public ColorEventArgs(ColorBgra color) + { + this.color = color; + } + } +} diff --git a/src/ColorEventHandler.cs b/src/ColorEventHandler.cs new file mode 100644 index 0000000..367d28a --- /dev/null +++ b/src/ColorEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal delegate void ColorEventHandler(object sender, ColorEventArgs ce); +} diff --git a/src/ColorPickerClickBehavior.cs b/src/ColorPickerClickBehavior.cs new file mode 100644 index 0000000..185b91c --- /dev/null +++ b/src/ColorPickerClickBehavior.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum ColorPickerClickBehavior + { + NoToolSwitch, + SwitchToLastTool, + SwitchToPencilTool, + } +} diff --git a/src/ColorRectangleControl.cs b/src/ColorRectangleControl.cs new file mode 100644 index 0000000..d5b33e5 --- /dev/null +++ b/src/ColorRectangleControl.cs @@ -0,0 +1,49 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ColorRectangleControl + : UserControl + { + private Color rectangleColor; + public Color RectangleColor + { + get + { + return rectangleColor; + } + + set + { + rectangleColor = value; + Invalidate(true); + } + } + + public ColorRectangleControl() + { + this.ResizeRedraw = true; + this.DoubleBuffered = true; + } + + protected override void OnPaint(PaintEventArgs e) + { + Utility.DrawColorRectangle(e.Graphics, this.ClientRectangle, rectangleColor, true); + base.OnPaint(e); + } + } +} diff --git a/src/ColorRectangleControl.resx b/src/ColorRectangleControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ColorWheel.cs b/src/ColorWheel.cs new file mode 100644 index 0000000..ea6b3d1 --- /dev/null +++ b/src/ColorWheel.cs @@ -0,0 +1,339 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ColorWheel + : UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + private Bitmap renderBitmap = null; + private bool tracking = false; + private Point lastMouseXY; + + // this number controls what you might call the tesselation of the color wheel. higher #'s = slower, lower #'s = looks worse + private const int colorTesselation = 60; + + private PictureBox wheelPictureBox; + + private HsvColor hsvColor; + public HsvColor HsvColor + { + get + { + return hsvColor; + } + + set + { + if (hsvColor != value) + { + HsvColor oldColor = hsvColor; + hsvColor = value; + this.OnColorChanged(); + Refresh(); + } + } + } + + public ColorWheel() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + //wheelRegion = new PdnRegion(); + hsvColor = new HsvColor(0, 0, 0); + } + + private static PointF SphericalToCartesian(float r, float theta) + { + float x; + float y; + + x = r * (float)Math.Cos(theta); + y = r * (float)Math.Sin(theta); + + return new PointF(x,y); + } + + private static PointF[] GetCirclePoints(float r, PointF center) + { + PointF[] points = new PointF[colorTesselation]; + + for (int i = 0; i < colorTesselation; i++) + { + float theta = ((float)i / (float)colorTesselation) * 2 * (float)Math.PI; + points[i] = SphericalToCartesian(r, theta); + points[i].X += center.X; + points[i].Y += center.Y; + } + + return points; + } + + private Color[] GetColors() + { + Color[] colors = new Color[colorTesselation]; + + for (int i = 0; i < colorTesselation; i++) + { + int hue = (i * 360) / colorTesselation; + colors[i] = new HsvColor(hue, 100, 100).ToColor(); + } + + return colors; + } + + protected override void OnLoad(EventArgs e) + { + InitRendering(); + base.OnLoad(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + InitRendering(); + base.OnPaint(e); + } + + private void InitRendering() + { + if (this.renderBitmap == null) + { + InitRenderSurface(); + this.wheelPictureBox.SizeMode = PictureBoxSizeMode.StretchImage; + int size = (int)Math.Ceiling(ComputeDiameter(this.Size)); + this.wheelPictureBox.Size = new Size(size, size); + this.wheelPictureBox.Image = this.renderBitmap; + } + } + + private void WheelPictureBox_Paint(object sender, System.Windows.Forms.PaintEventArgs e) + { + float radius = ComputeRadius(Size); + float theta = ((float)HsvColor.Hue / 360.0f) * 2.0f * (float)Math.PI; + float alpha = ((float)HsvColor.Saturation / 100.0f); + float x = (alpha * (radius - 1) * (float)Math.Cos(theta)) + radius; + float y = (alpha * (radius - 1) * (float)Math.Sin(theta)) + radius; + int ix = (int)x; + int iy = (int)y; + + // Draw the 'target rectangle' + GraphicsContainer container = e.Graphics.BeginContainer(); + e.Graphics.PixelOffsetMode = PixelOffsetMode.None; + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + e.Graphics.DrawRectangle(Pens.Black, ix - 1, iy - 1, 3, 3); + e.Graphics.DrawRectangle(Pens.White, ix, iy, 1, 1); + e.Graphics.EndContainer(container); + } + + private void InitRenderSurface() + { + if (renderBitmap != null) + { + renderBitmap.Dispose(); + } + + int wheelDiameter = (int)ComputeDiameter(Size); + + renderBitmap = new Bitmap(Math.Max(1, (wheelDiameter * 4) / 3), + Math.Max(1, (wheelDiameter * 4) / 3), PixelFormat.Format24bppRgb); + + using (Graphics g1 = Graphics.FromImage(renderBitmap)) + { + g1.Clear(this.BackColor); + DrawWheel(g1, renderBitmap.Width, renderBitmap.Height); + } + } + + private void DrawWheel(Graphics g, int width, int height) + { + float radius = ComputeRadius(new Size(width, height)); + PointF[] points = GetCirclePoints(Math.Max(1.0f, (float)radius - 1), new PointF(radius, radius)); + + using (PathGradientBrush pgb = new PathGradientBrush(points)) + { + pgb.CenterColor = new HsvColor(0, 0, 100).ToColor(); + pgb.CenterPoint = new PointF(radius, radius); + pgb.SurroundColors = GetColors(); + + g.FillEllipse(pgb, 0, 0, radius * 2, radius * 2); + } + } + + private static float ComputeRadius(Size size) + { + return Math.Min((float)size.Width / 2, (float)size.Height / 2); + } + + private static float ComputeDiameter(Size size) + { + return Math.Min((float)size.Width, (float)size.Height); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize (e); + + if (renderBitmap != null && (ComputeRadius(Size) != ComputeRadius(renderBitmap.Size))) + { + renderBitmap.Dispose(); + renderBitmap = null; + } + + Invalidate(); + } + + public event EventHandler ColorChanged; + protected virtual void OnColorChanged() + { + if (ColorChanged != null) + { + ColorChanged(this, EventArgs.Empty); + } + } + + private void GrabColor(Point mouseXY) + { + // center our coordinate system so the middle is (0,0), and positive Y is facing up + int cx = mouseXY.X - (Width / 2); + int cy = mouseXY.Y - (Height / 2); + + double theta = Math.Atan2(cy, cx); + + if (theta < 0) + { + theta += 2 * Math.PI; + } + + double alpha = Math.Sqrt((cx * cx) + (cy * cy)); + + int h = (int)((theta / (Math.PI * 2)) * 360.0); + int s = (int)Math.Min(100.0, (alpha / (double)(Width / 2)) * 100); + int v = 100; + + hsvColor = new HsvColor(h, s, v); + OnColorChanged(); + Invalidate(true); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown (e); + + if (e.Button == MouseButtons.Left) + { + tracking = true; + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp (e); + + if (tracking) + { + GrabColor(new Point(e.X, e.Y)); + } + + tracking = false; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove (e); + + lastMouseXY = new Point(e.X, e.Y); + + if (tracking) + { + GrabColor(new Point(e.X, e.Y)); + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.wheelPictureBox = new System.Windows.Forms.PictureBox(); + this.SuspendLayout(); + // + // wheelPictureBox + // + this.wheelPictureBox.Location = new System.Drawing.Point(0, 0); + this.wheelPictureBox.Name = "wheelPictureBox"; + this.wheelPictureBox.TabIndex = 0; + this.wheelPictureBox.TabStop = false; + this.wheelPictureBox.Click += new System.EventHandler(this.wheelPictureBox_Click); + this.wheelPictureBox.Paint += new System.Windows.Forms.PaintEventHandler(this.WheelPictureBox_Paint); + this.wheelPictureBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.wheelPictureBox_MouseUp); + this.wheelPictureBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.wheelPictureBox_MouseMove); + this.wheelPictureBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.wheelPictureBox_MouseDown); + // + // ColorWheel + // + this.Controls.Add(this.wheelPictureBox); + this.Name = "ColorWheel"; + this.ResumeLayout(false); + + } + #endregion + + private void wheelPictureBox_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) + { + OnMouseMove(e); + } + + private void wheelPictureBox_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) + { + OnMouseUp(e); + } + + private void wheelPictureBox_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) + { + OnMouseDown(e); + } + + private void wheelPictureBox_Click(object sender, System.EventArgs e) + { + OnClick(e); + } + } +} diff --git a/src/ColorWheel.resx b/src/ColorWheel.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ColorsForm.cs b/src/ColorsForm.cs new file mode 100644 index 0000000..b96cac6 --- /dev/null +++ b/src/ColorsForm.cs @@ -0,0 +1,2005 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + // TODO: rewrite this ... the code is out of control here as it has grown organically, + // and it's impossible to maintain. post-3.0 + internal class ColorsForm + : FloatingToolForm + { + // We want some buttons that don't have a gradient background or fancy border + private sealed class OurToolStripRenderer + : ToolStripProfessionalRenderer + { + protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) + { + if (e.ToolStrip is ToolStripDropDown) + { + base.OnRenderToolStripBackground(e); + } + else + { + using (SolidBrush backBrush = new SolidBrush(e.BackColor)) + { + e.Graphics.FillRectangle(backBrush, e.AffectedBounds); + } + } + } + + protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) + { + // Do not render a border. + } + } + + private Label redLabel; + private Label blueLabel; + private Label greenLabel; + private Label hueLabel; + + private NumericUpDown redUpDown; + private NumericUpDown greenUpDown; + private NumericUpDown blueUpDown; + private NumericUpDown hueUpDown; + private NumericUpDown valueUpDown; + private NumericUpDown saturationUpDown; + + private System.ComponentModel.Container components = null; + private Label saturationLabel; + private Label valueLabel; + private ComboBox whichUserColorBox; + private ColorGradientControl valueGradientControl; + private NumericUpDown alphaUpDown; + private ColorGradientControl alphaGradientControl; + private ColorWheel colorWheel; + + private int ignoreChangedEvents = 0; + private ColorBgra lastPrimaryColor; + private Button moreLessButton; + private ColorBgra lastSecondaryColor; + + private int suspendSetWhichUserColor; + private string lessText; + private string moreText; + private Size moreSize; + private Size lessSize; + private Control lessModeButtonSentinel; + private Control moreModeButtonSentinel; + private Control lessModeHeaderSentinel; + private Control moreModeHeaderSentinel; + private bool inMoreState = true; + private System.Windows.Forms.Label hexLabel; + private System.Windows.Forms.TextBox hexBox; + private uint ignore = 0; + private PaintDotNet.HeaderLabel rgbHeader; + private PaintDotNet.HeaderLabel hsvHeader; + private HeaderLabel swatchHeader; + private SwatchControl swatchControl; + private ColorDisplayWidget colorDisplayWidget; + private ToolStripEx toolStrip; + private ToolStripButton colorAddButton; + private PaintDotNet.HeaderLabel alphaHeader; + + private Image colorAddOverlay; + private ToolStripSplitButton colorPalettesButton; + private Bitmap colorAddIcon; + private ColorGradientControl hueGradientControl; + private ColorGradientControl saturationGradientControl; + private ColorGradientControl redGradientControl; + private ColorGradientControl greenGradientControl; + private ColorGradientControl blueGradientControl; + + private PaletteCollection paletteCollection = null; + + public PaletteCollection PaletteCollection + { + get + { + return this.paletteCollection; + } + + set + { + this.paletteCollection = value; + } + } + + private bool IgnoreChangedEvents + { + get + { + return this.ignoreChangedEvents != 0; + } + } + + private class WhichUserColorWrapper + { + private WhichUserColor whichUserColor; + + public WhichUserColor WhichUserColor + { + get + { + return this.whichUserColor; + } + } + + public override int GetHashCode() + { + return this.whichUserColor.GetHashCode(); + } + + public override bool Equals(object obj) + { + WhichUserColorWrapper rhs = obj as WhichUserColorWrapper; + + if (rhs == null) + { + return false; + } + + if (rhs.whichUserColor == this.whichUserColor) + { + return true; + } + + return false; + } + + public override string ToString() + { + return PdnResources.GetString("WhichUserColor." + this.whichUserColor.ToString()); + } + + public WhichUserColorWrapper(WhichUserColor whichUserColor) + { + this.whichUserColor = whichUserColor; + } + } + + public void SuspendSetWhichUserColor() + { + ++this.suspendSetWhichUserColor; + } + + public void ResumeSetWhichUserColor() + { + --this.suspendSetWhichUserColor; + } + + public WhichUserColor WhichUserColor + { + get + { + return ((WhichUserColorWrapper)whichUserColorBox.SelectedItem).WhichUserColor; + } + + set + { + if (this.suspendSetWhichUserColor <= 0) + { + whichUserColorBox.SelectedItem = new WhichUserColorWrapper(value); + } + } + } + + public void ToggleWhichUserColor() + { + switch (WhichUserColor) + { + case WhichUserColor.Primary: + WhichUserColor = WhichUserColor.Secondary; + break; + + case WhichUserColor.Secondary: + WhichUserColor = WhichUserColor.Primary; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + + public void SetColorControlsRedraw(bool enabled) + { + Control[] controls = + new Control[] + { + this.colorWheel, + this.whichUserColorBox, + this.hueGradientControl, + this.saturationGradientControl, + this.valueGradientControl, + this.redGradientControl, + this.greenGradientControl, + this.blueGradientControl, + this.alphaGradientControl, + this.hueUpDown, + this.saturationUpDown, + this.valueUpDown, + this.redUpDown, + this.greenUpDown, + this.blueUpDown, + this.alphaUpDown, + this.toolStrip + }; + + foreach (Control control in controls) + { + if (enabled) + { + UI.ResumeControlPainting(control); + control.Invalidate(true); + } + else + { + UI.SuspendControlPainting(control); + } + } + } + + public event ColorEventHandler UserPrimaryColorChanged; + protected virtual void OnUserPrimaryColorChanged(ColorBgra newColor) + { + if (UserPrimaryColorChanged != null && ignore == 0) + { + this.userPrimaryColor = newColor; + UserPrimaryColorChanged(this, new ColorEventArgs(newColor)); + this.lastPrimaryColor = newColor; + this.colorDisplayWidget.UserPrimaryColor = newColor; + } + + RenderColorAddIcon(newColor); + } + + private ColorBgra userPrimaryColor; + public ColorBgra UserPrimaryColor + { + get + { + return userPrimaryColor; + } + + set + { + if (IgnoreChangedEvents) + { + return; + } + + if (userPrimaryColor != value) + { + userPrimaryColor = value; + OnUserPrimaryColorChanged(value); + + if (WhichUserColor != WhichUserColor.Primary) + { + this.WhichUserColor = WhichUserColor.Primary; + } + + ignore++; + + // only do the update on the last one, so partial RGB info isn't parsed. + Utility.SetNumericUpDownValue(alphaUpDown, value.A); + Utility.SetNumericUpDownValue(redUpDown, value.R); + Utility.SetNumericUpDownValue(greenUpDown, value.G); + SetColorGradientValuesRgb(value.R, value.G, value.B); + SetColorGradientMinMaxColorsRgb(value.R, value.G, value.B); + SetColorGradientMinMaxColorsAlpha(value.A); + + ignore--; + Utility.SetNumericUpDownValue(blueUpDown, value.B); + Update(); + + string hexText = GetHexNumericUpDownValue(value.R, value.G, value.B); + hexBox.Text = hexText; + + SyncHsvFromRgb(value); + this.colorDisplayWidget.UserPrimaryColor = this.userPrimaryColor; + } + } + } + + private string GetHexNumericUpDownValue(int red, int green, int blue) + { + int newHexNumber = (red << 16) | (green << 8) | blue; + string newHexText = System.Convert.ToString(newHexNumber, 16); + + while (newHexText.Length < 6) + { + newHexText = "0" + newHexText; + } + + return newHexText.ToUpper(); + } + + public event ColorEventHandler UserSecondaryColorChanged; + protected virtual void OnUserSecondaryColorChanged(ColorBgra newColor) + { + if (UserSecondaryColorChanged != null && ignore == 0) + { + this.userSecondaryColor = newColor; + UserSecondaryColorChanged(this, new ColorEventArgs(newColor)); + this.lastSecondaryColor = newColor; + this.colorDisplayWidget.UserSecondaryColor = newColor; + } + + RenderColorAddIcon(newColor); + } + + private ColorBgra userSecondaryColor; + public ColorBgra UserSecondaryColor + { + get + { + return userSecondaryColor; + } + + set + { + if (IgnoreChangedEvents) + { + return; + } + + if (userSecondaryColor != value) + { + userSecondaryColor = value; + OnUserSecondaryColorChanged(value); + + if (WhichUserColor != WhichUserColor.Secondary) + { + this.WhichUserColor = WhichUserColor.Secondary; + } + + ignore++; + + //only do the update on the last one, so partial RGB info isn't parsed. + Utility.SetNumericUpDownValue(alphaUpDown, value.A); + Utility.SetNumericUpDownValue(redUpDown, value.R); + Utility.SetNumericUpDownValue(greenUpDown, value.G); + + SetColorGradientValuesRgb(value.R, value.G, value.B); + SetColorGradientMinMaxColorsRgb(value.R, value.G, value.B); + SetColorGradientMinMaxColorsAlpha(value.A); + + ignore--; + Utility.SetNumericUpDownValue(blueUpDown, value.B); + Update(); + + string hexText = GetHexNumericUpDownValue(value.R, value.G, value.B); + hexBox.Text = hexText; + + SyncHsvFromRgb(value); + this.colorDisplayWidget.UserSecondaryColor = this.userSecondaryColor; + } + } + } + + /// + /// Convenience function for ColorsForm internals. Checks the value of the + /// WhichUserColor property and raises either the UserPrimaryColorChanged or + /// the UserSecondaryColorChanged events. + /// + /// The new color to notify clients about. + private void OnUserColorChanged(ColorBgra newColor) + { + switch (WhichUserColor) + { + case WhichUserColor.Primary: + OnUserPrimaryColorChanged(newColor); + break; + + case WhichUserColor.Secondary: + OnUserSecondaryColorChanged(newColor); + break; + + default: + throw new InvalidEnumArgumentException("WhichUserColor property"); + } + } + + /// + /// Whenever a color is changed via RGB methods, call this and the HSV + /// counterparts will be sync'd up. + /// + /// The RGB color that should be converted to HSV. + private void SyncHsvFromRgb(ColorBgra newColor) + { + if (ignore == 0) + { + ignore++; + HsvColor hsvColor = HsvColor.FromColor(newColor.ToColor()); + + Utility.SetNumericUpDownValue(hueUpDown, hsvColor.Hue); + Utility.SetNumericUpDownValue(saturationUpDown, hsvColor.Saturation); + Utility.SetNumericUpDownValue(valueUpDown, hsvColor.Value); + + SetColorGradientValuesHsv(hsvColor.Hue, hsvColor.Saturation, hsvColor.Value); + SetColorGradientMinMaxColorsHsv(hsvColor.Hue, hsvColor.Saturation, hsvColor.Value); + + colorWheel.HsvColor = hsvColor; + ignore--; + } + } + + private void SetColorGradientValuesRgb(int r, int g, int b) + { + PushIgnoreChangedEvents(); + + if (redGradientControl.Value != r) + { + redGradientControl.Value = r; + } + + if (greenGradientControl.Value != g) + { + greenGradientControl.Value = g; + } + + if (blueGradientControl.Value != b) + { + blueGradientControl.Value = b; + } + + PopIgnoreChangedEvents(); + } + + private void SetColorGradientValuesHsv(int h, int s, int v) + { + PushIgnoreChangedEvents(); + + if (((hueGradientControl.Value * 360) / 255) != h) + { + hueGradientControl.Value = (255 * h) / 360; + } + + if (((saturationGradientControl.Value * 100) / 255) != s) + { + saturationGradientControl.Value = (255 * s) / 100; + } + + if (((valueGradientControl.Value * 100) / 255) != v) + { + valueGradientControl.Value = (255 * v) / 100; + } + + PopIgnoreChangedEvents(); + } + + /// + /// Whenever a color is changed via HSV methods, call this and the RGB + /// counterparts will be sync'd up. + /// + /// The HSV color that should be converted to RGB. + private void SyncRgbFromHsv(HsvColor newColor) + { + if (ignore == 0) + { + ignore++; + RgbColor rgbColor = newColor.ToRgb(); + + Utility.SetNumericUpDownValue(redUpDown, rgbColor.Red); + Utility.SetNumericUpDownValue(greenUpDown, rgbColor.Green); + Utility.SetNumericUpDownValue(blueUpDown, rgbColor.Blue); + + string hexText = GetHexNumericUpDownValue(rgbColor.Red, rgbColor.Green, rgbColor.Blue); + hexBox.Text = hexText; + + SetColorGradientValuesRgb(rgbColor.Red, rgbColor.Green, rgbColor.Blue); + SetColorGradientMinMaxColorsRgb(rgbColor.Red, rgbColor.Green, rgbColor.Blue); + SetColorGradientMinMaxColorsAlpha((int)alphaUpDown.Value); + + ignore--; + } + } + + private void RenderColorAddIcon(ColorBgra newColor) + { + if (this.colorAddIcon == null) + { + this.colorAddIcon = new Bitmap(16, 16, PixelFormat.Format32bppArgb); + } + + using (Graphics g = Graphics.FromImage(this.colorAddIcon)) + { + Rectangle rect = new Rectangle(0, 0, this.colorAddIcon.Width - 2, this.colorAddIcon.Height - 2); + Utility.DrawColorRectangle(g, rect, newColor.ToColor(), true); + g.DrawImage(this.colorAddOverlay, 0, 0); + } + + this.colorAddButton.Image = this.colorAddIcon; + this.colorAddButton.Invalidate(); + } + + public ColorsForm() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + whichUserColorBox.Items.Add(new WhichUserColorWrapper(WhichUserColor.Primary)); + whichUserColorBox.Items.Add(new WhichUserColorWrapper(WhichUserColor.Secondary)); + whichUserColorBox.SelectedIndex = 0; + + moreSize = this.ClientSize; + lessSize = new Size(this.swatchHeader.Width + SystemLayer.UI.ScaleWidth(16), moreSize.Height); + + this.Text = PdnResources.GetString("ColorsForm.Text"); + this.redLabel.Text = PdnResources.GetString("ColorsForm.RedLabel.Text"); + this.blueLabel.Text = PdnResources.GetString("ColorsForm.BlueLabel.Text"); + this.greenLabel.Text = PdnResources.GetString("ColorsForm.GreenLabel.Text"); + this.saturationLabel.Text = PdnResources.GetString("ColorsForm.SaturationLabel.Text"); + this.valueLabel.Text = PdnResources.GetString("ColorsForm.ValueLabel.Text"); + this.hueLabel.Text = PdnResources.GetString("ColorsForm.HueLabel.Text"); + this.rgbHeader.Text = PdnResources.GetString("ColorsForm.RgbHeader.Text"); + this.hexLabel.Text = PdnResources.GetString("ColorsForm.HexLabel.Text"); + this.hsvHeader.Text = PdnResources.GetString("ColorsForm.HsvHeader.Text"); + this.alphaHeader.Text = PdnResources.GetString("ColorsForm.AlphaHeader.Text"); + + this.lessText = "<< " + PdnResources.GetString("ColorsForm.MoreLessButton.Text.Less"); + this.moreText = PdnResources.GetString("ColorsForm.MoreLessButton.Text.More") + " >>"; + this.moreLessButton.Text = lessText; + + this.toolStrip.Renderer = new OurToolStripRenderer(); + + this.colorAddOverlay = PdnResources.GetImageResource("Icons.ColorAddOverlay.png").Reference; + this.colorPalettesButton.Image = PdnResources.GetImageResource("Icons.ColorPalettes.png").Reference; + + RenderColorAddIcon(this.UserPrimaryColor); + + this.colorAddButton.ToolTipText = PdnResources.GetString("ColorsForm.ColorAddButton.ToolTipText"); + this.colorPalettesButton.ToolTipText = PdnResources.GetString("ColorsForm.ColorPalettesButton.ToolTipText"); + + // Load the current palette + string currentPaletteString; + + try + { + currentPaletteString = Settings.CurrentUser.GetString(SettingNames.CurrentPalette, null); + } + + catch (Exception) + { + currentPaletteString = null; + } + + if (currentPaletteString == null) + { + string defaultPaletteString = PaletteCollection.GetPaletteSaveString(PaletteCollection.DefaultPalette); + currentPaletteString = defaultPaletteString; + } + + ColorBgra[] currentPalette = PaletteCollection.ParsePaletteString(currentPaletteString); + + this.swatchControl.Colors = currentPalette; + } + + protected override void OnLoad(EventArgs e) + { + this.inMoreState = true; + this.moreLessButton.PerformClick(); + base.OnLoad(e); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.valueGradientControl = new PaintDotNet.ColorGradientControl(); + this.colorWheel = new PaintDotNet.ColorWheel(); + this.redUpDown = new System.Windows.Forms.NumericUpDown(); + this.greenUpDown = new System.Windows.Forms.NumericUpDown(); + this.blueUpDown = new System.Windows.Forms.NumericUpDown(); + this.redLabel = new System.Windows.Forms.Label(); + this.blueLabel = new System.Windows.Forms.Label(); + this.greenLabel = new System.Windows.Forms.Label(); + this.saturationLabel = new System.Windows.Forms.Label(); + this.valueLabel = new System.Windows.Forms.Label(); + this.hueLabel = new System.Windows.Forms.Label(); + this.valueUpDown = new System.Windows.Forms.NumericUpDown(); + this.saturationUpDown = new System.Windows.Forms.NumericUpDown(); + this.hueUpDown = new System.Windows.Forms.NumericUpDown(); + this.hexBox = new System.Windows.Forms.TextBox(); + this.hexLabel = new System.Windows.Forms.Label(); + this.whichUserColorBox = new System.Windows.Forms.ComboBox(); + this.alphaUpDown = new System.Windows.Forms.NumericUpDown(); + this.moreLessButton = new System.Windows.Forms.Button(); + this.lessModeButtonSentinel = new System.Windows.Forms.Control(); + this.moreModeButtonSentinel = new System.Windows.Forms.Control(); + this.lessModeHeaderSentinel = new System.Windows.Forms.Control(); + this.moreModeHeaderSentinel = new System.Windows.Forms.Control(); + this.rgbHeader = new PaintDotNet.HeaderLabel(); + this.hsvHeader = new PaintDotNet.HeaderLabel(); + this.alphaHeader = new PaintDotNet.HeaderLabel(); + this.swatchHeader = new PaintDotNet.HeaderLabel(); + this.swatchControl = new PaintDotNet.SwatchControl(); + this.colorDisplayWidget = new PaintDotNet.ColorDisplayWidget(); + this.toolStrip = new PaintDotNet.SystemLayer.ToolStripEx(); + this.colorAddButton = new System.Windows.Forms.ToolStripButton(); + this.colorPalettesButton = new System.Windows.Forms.ToolStripSplitButton(); + this.hueGradientControl = new PaintDotNet.ColorGradientControl(); + this.saturationGradientControl = new PaintDotNet.ColorGradientControl(); + this.alphaGradientControl = new PaintDotNet.ColorGradientControl(); + this.redGradientControl = new PaintDotNet.ColorGradientControl(); + this.greenGradientControl = new PaintDotNet.ColorGradientControl(); + this.blueGradientControl = new PaintDotNet.ColorGradientControl(); + ((System.ComponentModel.ISupportInitialize)(this.redUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.greenUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.blueUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.valueUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.saturationUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.hueUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.alphaUpDown)).BeginInit(); + this.toolStrip.SuspendLayout(); + this.SuspendLayout(); + // + // valueGradientControl + // + this.valueGradientControl.Count = 1; + this.valueGradientControl.CustomGradient = null; + this.valueGradientControl.DrawFarNub = true; + this.valueGradientControl.DrawNearNub = false; + this.valueGradientControl.Location = new System.Drawing.Point(243, 185); + this.valueGradientControl.MaxColor = System.Drawing.Color.White; + this.valueGradientControl.MinColor = System.Drawing.Color.Black; + this.valueGradientControl.Name = "valueGradientControl"; + this.valueGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.valueGradientControl.Size = new System.Drawing.Size(73, 19); + this.valueGradientControl.TabIndex = 2; + this.valueGradientControl.TabStop = false; + this.valueGradientControl.Value = 0; + this.valueGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.HsvGradientControl_ValueChanged); + // + // colorWheel + // + this.colorWheel.Location = new System.Drawing.Point(65, 35); + this.colorWheel.Name = "colorWheel"; + this.colorWheel.Size = new System.Drawing.Size(129, 130); + this.colorWheel.TabIndex = 3; + this.colorWheel.TabStop = false; + this.colorWheel.ColorChanged += new System.EventHandler(this.ColorWheel_ColorChanged); + // + // redUpDown + // + this.redUpDown.Location = new System.Drawing.Point(320, 24); + this.redUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.redUpDown.Name = "redUpDown"; + this.redUpDown.Size = new System.Drawing.Size(56, 20); + this.redUpDown.TabIndex = 2; + this.redUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.redUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.redUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.redUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.redUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // greenUpDown + // + this.greenUpDown.Location = new System.Drawing.Point(320, 48); + this.greenUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.greenUpDown.Name = "greenUpDown"; + this.greenUpDown.Size = new System.Drawing.Size(56, 20); + this.greenUpDown.TabIndex = 3; + this.greenUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.greenUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.greenUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.greenUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.greenUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // blueUpDown + // + this.blueUpDown.Location = new System.Drawing.Point(320, 72); + this.blueUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.blueUpDown.Name = "blueUpDown"; + this.blueUpDown.Size = new System.Drawing.Size(56, 20); + this.blueUpDown.TabIndex = 4; + this.blueUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.blueUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.blueUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.blueUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.blueUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // redLabel + // + this.redLabel.AutoSize = true; + this.redLabel.Location = new System.Drawing.Point(222, 28); + this.redLabel.Name = "redLabel"; + this.redLabel.Size = new System.Drawing.Size(15, 13); + this.redLabel.TabIndex = 7; + this.redLabel.Text = "R"; + this.redLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // blueLabel + // + this.blueLabel.AutoSize = true; + this.blueLabel.Location = new System.Drawing.Point(222, 76); + this.blueLabel.Name = "blueLabel"; + this.blueLabel.Size = new System.Drawing.Size(14, 13); + this.blueLabel.TabIndex = 8; + this.blueLabel.Text = "B"; + this.blueLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // greenLabel + // + this.greenLabel.AutoSize = true; + this.greenLabel.Location = new System.Drawing.Point(222, 52); + this.greenLabel.Name = "greenLabel"; + this.greenLabel.Size = new System.Drawing.Size(15, 13); + this.greenLabel.TabIndex = 9; + this.greenLabel.Text = "G"; + this.greenLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // saturationLabel + // + this.saturationLabel.AutoSize = true; + this.saturationLabel.Location = new System.Drawing.Point(222, 164); + this.saturationLabel.Name = "saturationLabel"; + this.saturationLabel.Size = new System.Drawing.Size(17, 13); + this.saturationLabel.TabIndex = 16; + this.saturationLabel.Text = "S:"; + this.saturationLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // valueLabel + // + this.valueLabel.AutoSize = true; + this.valueLabel.Location = new System.Drawing.Point(222, 188); + this.valueLabel.Name = "valueLabel"; + this.valueLabel.Size = new System.Drawing.Size(17, 13); + this.valueLabel.TabIndex = 15; + this.valueLabel.Text = "V:"; + this.valueLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // hueLabel + // + this.hueLabel.AutoSize = true; + this.hueLabel.Location = new System.Drawing.Point(222, 140); + this.hueLabel.Name = "hueLabel"; + this.hueLabel.Size = new System.Drawing.Size(18, 13); + this.hueLabel.TabIndex = 14; + this.hueLabel.Text = "H:"; + this.hueLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // valueUpDown + // + this.valueUpDown.Location = new System.Drawing.Point(320, 184); + this.valueUpDown.Name = "valueUpDown"; + this.valueUpDown.Size = new System.Drawing.Size(56, 20); + this.valueUpDown.TabIndex = 8; + this.valueUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.valueUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.valueUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.valueUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.valueUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // saturationUpDown + // + this.saturationUpDown.Location = new System.Drawing.Point(320, 160); + this.saturationUpDown.Name = "saturationUpDown"; + this.saturationUpDown.Size = new System.Drawing.Size(56, 20); + this.saturationUpDown.TabIndex = 7; + this.saturationUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.saturationUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.saturationUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.saturationUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.saturationUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // hueUpDown + // + this.hueUpDown.Location = new System.Drawing.Point(320, 136); + this.hueUpDown.Maximum = new decimal(new int[] { + 360, + 0, + 0, + 0}); + this.hueUpDown.Name = "hueUpDown"; + this.hueUpDown.Size = new System.Drawing.Size(56, 20); + this.hueUpDown.TabIndex = 6; + this.hueUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.hueUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.hueUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.hueUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.hueUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // hexBox + // + this.hexBox.Location = new System.Drawing.Point(320, 96); + this.hexBox.Name = "hexBox"; + this.hexBox.Size = new System.Drawing.Size(56, 20); + this.hexBox.TabIndex = 5; + this.hexBox.Text = "000000"; + this.hexBox.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.hexBox.Enter += new System.EventHandler(this.HexUpDown_Enter); + this.hexBox.Leave += new System.EventHandler(this.HexUpDown_Leave); + this.hexBox.KeyUp += new System.Windows.Forms.KeyEventHandler(this.HexUpDown_KeyUp); + this.hexBox.TextChanged += new System.EventHandler(this.UpDown_ValueChanged); + // + // hexLabel + // + this.hexLabel.AutoSize = true; + this.hexLabel.Location = new System.Drawing.Point(222, 99); + this.hexLabel.Name = "hexLabel"; + this.hexLabel.Size = new System.Drawing.Size(26, 13); + this.hexLabel.TabIndex = 13; + this.hexLabel.Text = "Hex"; + this.hexLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // whichUserColorBox + // + this.whichUserColorBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.whichUserColorBox.Location = new System.Drawing.Point(8, 8); + this.whichUserColorBox.Name = "whichUserColorBox"; + this.whichUserColorBox.Size = new System.Drawing.Size(112, 21); + this.whichUserColorBox.TabIndex = 0; + this.whichUserColorBox.SelectedIndexChanged += new System.EventHandler(this.WhichUserColorBox_SelectedIndexChanged); + // + // alphaUpDown + // + this.alphaUpDown.Location = new System.Drawing.Point(320, 228); + this.alphaUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.alphaUpDown.Name = "alphaUpDown"; + this.alphaUpDown.Size = new System.Drawing.Size(56, 20); + this.alphaUpDown.TabIndex = 10; + this.alphaUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.alphaUpDown.Enter += new System.EventHandler(this.UpDown_Enter); + this.alphaUpDown.ValueChanged += new System.EventHandler(this.UpDown_ValueChanged); + this.alphaUpDown.Leave += new System.EventHandler(this.UpDown_Leave); + this.alphaUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.UpDown_KeyUp); + // + // moreLessButton + // + this.moreLessButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.moreLessButton.Location = new System.Drawing.Point(126, 7); + this.moreLessButton.Name = "moreLessButton"; + this.moreLessButton.Size = new System.Drawing.Size(75, 23); + this.moreLessButton.TabIndex = 1; + this.moreLessButton.Click += new System.EventHandler(this.MoreLessButton_Click); + // + // lessModeButtonSentinel + // + this.lessModeButtonSentinel.Location = new System.Drawing.Point(128, 7); + this.lessModeButtonSentinel.Name = "lessModeButtonSentinel"; + this.lessModeButtonSentinel.Size = new System.Drawing.Size(0, 0); + this.lessModeButtonSentinel.TabIndex = 22; + this.lessModeButtonSentinel.Text = "we put the lessMore control here when in \"Less\" mode"; + this.lessModeButtonSentinel.Visible = false; + // + // moreModeButtonSentinel + // + this.moreModeButtonSentinel.Location = new System.Drawing.Point(165, 7); + this.moreModeButtonSentinel.Name = "moreModeButtonSentinel"; + this.moreModeButtonSentinel.Size = new System.Drawing.Size(0, 0); + this.moreModeButtonSentinel.TabIndex = 23; + this.moreModeButtonSentinel.Visible = false; + // + // lessModeHeaderSentinel + // + this.lessModeHeaderSentinel.Location = new System.Drawing.Point(8, 40); + this.lessModeHeaderSentinel.Name = "lessModeHeaderSentinel"; + this.lessModeHeaderSentinel.Size = new System.Drawing.Size(195, 185); + this.lessModeHeaderSentinel.TabIndex = 24; + this.lessModeHeaderSentinel.Visible = false; + // + // moreModeHeaderSentinel + // + this.moreModeHeaderSentinel.Location = new System.Drawing.Point(8, 40); + this.moreModeHeaderSentinel.Name = "moreModeHeaderSentinel"; + this.moreModeHeaderSentinel.Size = new System.Drawing.Size(232, 216); + this.moreModeHeaderSentinel.TabIndex = 25; + this.moreModeHeaderSentinel.TabStop = false; + this.moreModeHeaderSentinel.Visible = false; + // + // rgbHeader + // + this.rgbHeader.Location = new System.Drawing.Point(222, 8); + this.rgbHeader.Name = "rgbHeader"; + this.rgbHeader.RightMargin = 0; + this.rgbHeader.Size = new System.Drawing.Size(154, 14); + this.rgbHeader.TabIndex = 27; + this.rgbHeader.TabStop = false; + // + // hsvHeader + // + this.hsvHeader.Location = new System.Drawing.Point(222, 120); + this.hsvHeader.Name = "hsvHeader"; + this.hsvHeader.RightMargin = 0; + this.hsvHeader.Size = new System.Drawing.Size(154, 14); + this.hsvHeader.TabIndex = 28; + this.hsvHeader.TabStop = false; + // + // alphaHeader + // + this.alphaHeader.Location = new System.Drawing.Point(222, 212); + this.alphaHeader.Name = "alphaHeader"; + this.alphaHeader.RightMargin = 0; + this.alphaHeader.Size = new System.Drawing.Size(154, 14); + this.alphaHeader.TabIndex = 29; + this.alphaHeader.TabStop = false; + // + // swatchHeader + // + this.swatchHeader.Location = new System.Drawing.Point(8, 177); + this.swatchHeader.Name = "swatchHeader"; + this.swatchHeader.RightMargin = 0; + this.swatchHeader.Size = new System.Drawing.Size(193, 14); + this.swatchHeader.TabIndex = 30; + this.swatchHeader.TabStop = false; + // + // swatchControl + // + this.swatchControl.BlinkHighlight = false; + this.swatchControl.Colors = new PaintDotNet.ColorBgra[0]; + this.swatchControl.Location = new System.Drawing.Point(8, 189); + this.swatchControl.Name = "swatchControl"; + this.swatchControl.Size = new System.Drawing.Size(192, 74); + this.swatchControl.TabIndex = 31; + this.swatchControl.Text = "swatchControl1"; + this.swatchControl.ColorsChanged += this.SwatchControl_ColorsChanged; + this.swatchControl.ColorClicked += this.SwatchControl_ColorClicked; + // + // colorDisplayWidget + // + this.colorDisplayWidget.Location = new System.Drawing.Point(4, 32); + this.colorDisplayWidget.Name = "colorDisplayWidget"; + this.colorDisplayWidget.Size = new System.Drawing.Size(52, 52); + this.colorDisplayWidget.TabIndex = 32; + this.colorDisplayWidget.BlackAndWhiteButtonClicked += ColorDisplayWidget_BlackAndWhiteButtonClicked; + this.colorDisplayWidget.SwapColorsClicked += ColorDisplayWidget_SwapColorsClicked; + this.colorDisplayWidget.UserPrimaryColorClick += ColorDisplay_PrimaryColorClicked; + this.colorDisplayWidget.UserSecondaryColorClick += ColorDisplay_SecondaryColorClicked; + // + // toolStrip + // + this.toolStrip.ClickThrough = true; + this.toolStrip.Dock = System.Windows.Forms.DockStyle.None; + this.toolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.colorAddButton, + this.colorPalettesButton}); + this.toolStrip.Location = new System.Drawing.Point(5, 157); + this.toolStrip.ManagedFocus = true; + this.toolStrip.Name = "toolStrip"; + this.toolStrip.Size = new System.Drawing.Size(65, 25); + this.toolStrip.TabIndex = 33; + this.toolStrip.Text = "toolStrip"; + // + // colorAddButton + // + this.colorAddButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.colorAddButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.colorAddButton.Name = "colorAddButton"; + this.colorAddButton.Size = new System.Drawing.Size(23, 22); + this.colorAddButton.Text = "colorAddButton"; + this.colorAddButton.Click += new System.EventHandler(this.ColorAddButton_Click); + // + // colorPalettesButton + // + this.colorPalettesButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.colorPalettesButton.Name = "colorPalettesButton"; + this.colorPalettesButton.Size = new System.Drawing.Size(16, 22); + this.colorPalettesButton.Click += new System.EventHandler(this.ColorPalettesButton_Click); + this.colorPalettesButton.DropDownOpening += new System.EventHandler(this.ColorPalettesButton_DropDownOpening); + // + // hueGradientControl + // + this.hueGradientControl.Count = 1; + this.hueGradientControl.CustomGradient = null; + this.hueGradientControl.DrawFarNub = true; + this.hueGradientControl.DrawNearNub = false; + this.hueGradientControl.Location = new System.Drawing.Point(243, 137); + this.hueGradientControl.MaxColor = System.Drawing.Color.White; + this.hueGradientControl.MinColor = System.Drawing.Color.Black; + this.hueGradientControl.Name = "hueGradientControl"; + this.hueGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.hueGradientControl.Size = new System.Drawing.Size(73, 19); + this.hueGradientControl.TabIndex = 34; + this.hueGradientControl.TabStop = false; + this.hueGradientControl.Value = 0; + this.hueGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.HsvGradientControl_ValueChanged); + // + // saturationGradientControl + // + this.saturationGradientControl.Count = 1; + this.saturationGradientControl.CustomGradient = null; + this.saturationGradientControl.DrawFarNub = true; + this.saturationGradientControl.DrawNearNub = false; + this.saturationGradientControl.Location = new System.Drawing.Point(243, 161); + this.saturationGradientControl.MaxColor = System.Drawing.Color.White; + this.saturationGradientControl.MinColor = System.Drawing.Color.Black; + this.saturationGradientControl.Name = "saturationGradientControl"; + this.saturationGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.saturationGradientControl.Size = new System.Drawing.Size(73, 19); + this.saturationGradientControl.TabIndex = 35; + this.saturationGradientControl.TabStop = false; + this.saturationGradientControl.Value = 0; + this.saturationGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.HsvGradientControl_ValueChanged); + // + // alphaGradientControl + // + this.alphaGradientControl.Count = 1; + this.alphaGradientControl.CustomGradient = null; + this.alphaGradientControl.DrawFarNub = true; + this.alphaGradientControl.DrawNearNub = false; + this.alphaGradientControl.Location = new System.Drawing.Point(243, 229); + this.alphaGradientControl.MaxColor = System.Drawing.Color.White; + this.alphaGradientControl.MinColor = System.Drawing.Color.Black; + this.alphaGradientControl.Name = "alphaGradientControl"; + this.alphaGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.alphaGradientControl.Size = new System.Drawing.Size(73, 19); + this.alphaGradientControl.TabIndex = 36; + this.alphaGradientControl.TabStop = false; + this.alphaGradientControl.Value = 0; + this.alphaGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.UpDown_ValueChanged); + // + // redGradientControl + // + this.redGradientControl.Count = 1; + this.redGradientControl.CustomGradient = null; + this.redGradientControl.DrawFarNub = true; + this.redGradientControl.DrawNearNub = false; + this.redGradientControl.Location = new System.Drawing.Point(243, 25); + this.redGradientControl.MaxColor = System.Drawing.Color.White; + this.redGradientControl.MinColor = System.Drawing.Color.Black; + this.redGradientControl.Name = "redGradientControl"; + this.redGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.redGradientControl.Size = new System.Drawing.Size(73, 19); + this.redGradientControl.TabIndex = 37; + this.redGradientControl.TabStop = false; + this.redGradientControl.Value = 0; + this.redGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.RgbGradientControl_ValueChanged); + // + // greenGradientControl + // + this.greenGradientControl.Count = 1; + this.greenGradientControl.CustomGradient = null; + this.greenGradientControl.DrawFarNub = true; + this.greenGradientControl.DrawNearNub = false; + this.greenGradientControl.Location = new System.Drawing.Point(243, 49); + this.greenGradientControl.MaxColor = System.Drawing.Color.White; + this.greenGradientControl.MinColor = System.Drawing.Color.Black; + this.greenGradientControl.Name = "greenGradientControl"; + this.greenGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.greenGradientControl.Size = new System.Drawing.Size(73, 19); + this.greenGradientControl.TabIndex = 38; + this.greenGradientControl.TabStop = false; + this.greenGradientControl.Value = 0; + this.greenGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.RgbGradientControl_ValueChanged); + // + // blueGradientControl + // + this.blueGradientControl.Count = 1; + this.blueGradientControl.CustomGradient = null; + this.blueGradientControl.DrawFarNub = true; + this.blueGradientControl.DrawNearNub = false; + this.blueGradientControl.Location = new System.Drawing.Point(243, 73); + this.blueGradientControl.MaxColor = System.Drawing.Color.White; + this.blueGradientControl.MinColor = System.Drawing.Color.Black; + this.blueGradientControl.Name = "blueGradientControl"; + this.blueGradientControl.Orientation = System.Windows.Forms.Orientation.Horizontal; + this.blueGradientControl.Size = new System.Drawing.Size(73, 19); + this.blueGradientControl.TabIndex = 39; + this.blueGradientControl.TabStop = false; + this.blueGradientControl.Value = 0; + this.blueGradientControl.ValueChanged += new PaintDotNet.IndexEventHandler(this.RgbGradientControl_ValueChanged); + // + // ColorsForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(386, 266); + this.Controls.Add(this.valueLabel); + this.Controls.Add(this.saturationLabel); + this.Controls.Add(this.hueLabel); + this.Controls.Add(this.greenLabel); + this.Controls.Add(this.blueLabel); + this.Controls.Add(this.redLabel); + this.Controls.Add(this.hexLabel); + this.Controls.Add(this.blueGradientControl); + this.Controls.Add(this.greenGradientControl); + this.Controls.Add(this.redGradientControl); + this.Controls.Add(this.alphaGradientControl); + this.Controls.Add(this.saturationGradientControl); + this.Controls.Add(this.hueGradientControl); + this.Controls.Add(this.toolStrip); + this.Controls.Add(this.colorWheel); + this.Controls.Add(this.colorDisplayWidget); + this.Controls.Add(this.swatchControl); + this.Controls.Add(this.swatchHeader); + this.Controls.Add(this.alphaHeader); + this.Controls.Add(this.hsvHeader); + this.Controls.Add(this.rgbHeader); + this.Controls.Add(this.valueGradientControl); + this.Controls.Add(this.moreModeButtonSentinel); + this.Controls.Add(this.lessModeButtonSentinel); + this.Controls.Add(this.moreLessButton); + this.Controls.Add(this.whichUserColorBox); + this.Controls.Add(this.lessModeHeaderSentinel); + this.Controls.Add(this.moreModeHeaderSentinel); + this.Controls.Add(this.blueUpDown); + this.Controls.Add(this.greenUpDown); + this.Controls.Add(this.redUpDown); + this.Controls.Add(this.hexBox); + this.Controls.Add(this.hueUpDown); + this.Controls.Add(this.saturationUpDown); + this.Controls.Add(this.valueUpDown); + this.Controls.Add(this.alphaUpDown); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Name = "ColorsForm"; + this.Controls.SetChildIndex(this.alphaUpDown, 0); + this.Controls.SetChildIndex(this.valueUpDown, 0); + this.Controls.SetChildIndex(this.saturationUpDown, 0); + this.Controls.SetChildIndex(this.hueUpDown, 0); + this.Controls.SetChildIndex(this.hexBox, 0); + this.Controls.SetChildIndex(this.redUpDown, 0); + this.Controls.SetChildIndex(this.greenUpDown, 0); + this.Controls.SetChildIndex(this.blueUpDown, 0); + this.Controls.SetChildIndex(this.moreModeHeaderSentinel, 0); + this.Controls.SetChildIndex(this.lessModeHeaderSentinel, 0); + this.Controls.SetChildIndex(this.whichUserColorBox, 0); + this.Controls.SetChildIndex(this.moreLessButton, 0); + this.Controls.SetChildIndex(this.lessModeButtonSentinel, 0); + this.Controls.SetChildIndex(this.moreModeButtonSentinel, 0); + this.Controls.SetChildIndex(this.valueGradientControl, 0); + this.Controls.SetChildIndex(this.rgbHeader, 0); + this.Controls.SetChildIndex(this.hsvHeader, 0); + this.Controls.SetChildIndex(this.alphaHeader, 0); + this.Controls.SetChildIndex(this.swatchHeader, 0); + this.Controls.SetChildIndex(this.swatchControl, 0); + this.Controls.SetChildIndex(this.colorDisplayWidget, 0); + this.Controls.SetChildIndex(this.colorWheel, 0); + this.Controls.SetChildIndex(this.toolStrip, 0); + this.Controls.SetChildIndex(this.hueGradientControl, 0); + this.Controls.SetChildIndex(this.saturationGradientControl, 0); + this.Controls.SetChildIndex(this.alphaGradientControl, 0); + this.Controls.SetChildIndex(this.redGradientControl, 0); + this.Controls.SetChildIndex(this.greenGradientControl, 0); + this.Controls.SetChildIndex(this.blueGradientControl, 0); + this.Controls.SetChildIndex(this.hexLabel, 0); + this.Controls.SetChildIndex(this.redLabel, 0); + this.Controls.SetChildIndex(this.blueLabel, 0); + this.Controls.SetChildIndex(this.greenLabel, 0); + this.Controls.SetChildIndex(this.hueLabel, 0); + this.Controls.SetChildIndex(this.saturationLabel, 0); + this.Controls.SetChildIndex(this.valueLabel, 0); + ((System.ComponentModel.ISupportInitialize)(this.redUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.greenUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.blueUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.valueUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.saturationUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.hueUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.alphaUpDown)).EndInit(); + this.toolStrip.ResumeLayout(false); + this.toolStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + #endregion + + public void SetUserColors(ColorBgra primary, ColorBgra secondary) + { + SetColorControlsRedraw(false); + WhichUserColor which = WhichUserColor; + UserPrimaryColor = primary; + UserSecondaryColor = secondary; + WhichUserColor = which; + SetColorControlsRedraw(true); + } + + public void SwapUserColors() + { + ColorBgra primary = this.UserPrimaryColor; + ColorBgra secondary = this.UserSecondaryColor; + SetUserColors(secondary, primary); + } + + public void SetUserColorsToBlackAndWhite() + { + SetUserColors(ColorBgra.Black, ColorBgra.White); + } + + private void ColorDisplayWidget_SwapColorsClicked(object sender, EventArgs e) + { + SwapUserColors(); + OnRelinquishFocus(); + } + + private void ColorDisplayWidget_BlackAndWhiteButtonClicked(object sender, EventArgs e) + { + SetUserColorsToBlackAndWhite(); + OnRelinquishFocus(); + } + + private void ColorDisplay_PrimaryColorClicked(object sender, System.EventArgs e) + { + WhichUserColor = WhichUserColor.Primary; + OnRelinquishFocus(); + } + + private void ColorDisplay_SecondaryColorClicked(object sender, System.EventArgs e) + { + WhichUserColor = WhichUserColor.Secondary; + OnRelinquishFocus(); + } + + private void WhichUserColorBox_SelectedIndexChanged(object sender, System.EventArgs e) + { + ColorBgra color; + + switch (WhichUserColor) + { + case WhichUserColor.Primary: + color = userPrimaryColor; + break; + + case WhichUserColor.Secondary: + color = userSecondaryColor; + break; + + default: + throw new InvalidEnumArgumentException("WhichUserColor property"); + } + + PushIgnoreChangedEvents(); + Utility.SetNumericUpDownValue(redUpDown, color.R); + Utility.SetNumericUpDownValue(greenUpDown, color.G); + Utility.SetNumericUpDownValue(blueUpDown, color.B); + + string hexText = GetHexNumericUpDownValue(color.R, color.G, color.B); + hexBox.Text = hexText; + + Utility.SetNumericUpDownValue(alphaUpDown, color.A); + PopIgnoreChangedEvents(); + + SetColorGradientMinMaxColorsRgb(color.R, color.G, color.B); + SetColorGradientValuesRgb(color.R, color.G, color.B); + SetColorGradientMinMaxColorsAlpha(color.A); + + SyncHsvFromRgb(color); + + OnRelinquishFocus(); + } + + private void ColorWheel_ColorChanged(object sender, EventArgs e) + { + if (IgnoreChangedEvents) + { + return; + } + + PushIgnoreChangedEvents(); + + HsvColor hsvColor = colorWheel.HsvColor; + RgbColor rgbColor = hsvColor.ToRgb(); + ColorBgra color = ColorBgra.FromBgra((byte)rgbColor.Blue, (byte)rgbColor.Green, (byte)rgbColor.Red, (byte)alphaUpDown.Value); + + Utility.SetNumericUpDownValue(hueUpDown, hsvColor.Hue); + Utility.SetNumericUpDownValue(saturationUpDown, hsvColor.Saturation); + Utility.SetNumericUpDownValue(valueUpDown, hsvColor.Value); + + Utility.SetNumericUpDownValue(redUpDown, color.R); + Utility.SetNumericUpDownValue(greenUpDown, color.G); + Utility.SetNumericUpDownValue(blueUpDown, color.B); + + string hexText = GetHexNumericUpDownValue(color.R, color.G, color.B); + hexBox.Text = hexText; + + Utility.SetNumericUpDownValue(alphaUpDown, color.A); + + SetColorGradientValuesHsv(hsvColor.Hue, hsvColor.Saturation, hsvColor.Value); + SetColorGradientMinMaxColorsHsv(hsvColor.Hue, hsvColor.Saturation, hsvColor.Value); + + SetColorGradientValuesRgb(color.R, color.G, color.B); + SetColorGradientMinMaxColorsRgb(color.R, color.G, color.B); + SetColorGradientMinMaxColorsAlpha(color.A); + + switch (WhichUserColor) + { + case WhichUserColor.Primary: + //UserPrimaryColor = color; + this.userPrimaryColor = color; + OnUserPrimaryColorChanged(color); + OnRelinquishFocus(); + break; + + case WhichUserColor.Secondary: + //UserSecondaryColor = color; + this.userSecondaryColor = color; + OnUserSecondaryColorChanged(color); + OnRelinquishFocus(); + break; + + default: + throw new InvalidEnumArgumentException("WhichUserColor property"); + } + + PopIgnoreChangedEvents(); + + Update(); + } + + private void SetColorGradientMinMaxColorsHsv(int h, int s, int v) + { + Color[] hueColors = new Color[361]; + + for (int newH = 0; newH <= 360; ++newH) + { + HsvColor hsv = new HsvColor(newH, 100, 100); + hueColors[newH] = hsv.ToColor(); + } + + this.hueGradientControl.CustomGradient = hueColors; + + Color[] satColors = new Color[101]; + + for (int newS = 0; newS <= 100; ++newS) + { + HsvColor hsv = new HsvColor(h, newS, v); + satColors[newS] = hsv.ToColor(); + } + + this.saturationGradientControl.CustomGradient = satColors; + + this.valueGradientControl.MaxColor = new HsvColor(h, s, 100).ToColor(); + this.valueGradientControl.MinColor = new HsvColor(h, s, 0).ToColor(); + } + + private void SetColorGradientMinMaxColorsRgb(int r, int g, int b) + { + this.redGradientControl.MaxColor = Color.FromArgb(255, g, b); + this.redGradientControl.MinColor = Color.FromArgb(0, g, b); + this.greenGradientControl.MaxColor = Color.FromArgb(r, 255, b); + this.greenGradientControl.MinColor = Color.FromArgb(r, 0, b); + this.blueGradientControl.MaxColor = Color.FromArgb(r, g, 255); + this.blueGradientControl.MinColor = Color.FromArgb(r, g, 0); + } + + private void SetColorGradientMinMaxColorsAlpha(int a) + { + Color[] colors = new Color[256]; + + for (int newA = 0; newA <= 255; ++newA) + { + colors[newA] = Color.FromArgb(newA, this.redGradientControl.Value, + this.greenGradientControl.Value, this.blueGradientControl.Value); + } + + this.alphaGradientControl.CustomGradient = colors; + } + + private void RgbGradientControl_ValueChanged(object sender, IndexEventArgs ce) + { + if (IgnoreChangedEvents) + { + return; + } + + int red; + if (sender == redGradientControl) + { + red = redGradientControl.Value; + } + else + { + red = (int)redUpDown.Value; + } + + int green; + if (sender == greenGradientControl) + { + green = greenGradientControl.Value; + } + else + { + green = (int)greenUpDown.Value; + } + + int blue; + if (sender == blueGradientControl) + { + blue = blueGradientControl.Value; + } + else + { + blue = (int)blueUpDown.Value; + } + + int alpha; + if (sender == alphaGradientControl) + { + alpha = alphaGradientControl.Value; + } + else + { + alpha = (int)alphaUpDown.Value; + } + + Color rgbColor = Color.FromArgb(alpha, red, green, blue); + HsvColor hsvColor = HsvColor.FromColor(rgbColor); + + PushIgnoreChangedEvents(); + Utility.SetNumericUpDownValue(hueUpDown, hsvColor.Hue); + Utility.SetNumericUpDownValue(saturationUpDown, hsvColor.Saturation); + Utility.SetNumericUpDownValue(valueUpDown, hsvColor.Value); + + Utility.SetNumericUpDownValue(redUpDown, rgbColor.R); + Utility.SetNumericUpDownValue(greenUpDown, rgbColor.G); + Utility.SetNumericUpDownValue(blueUpDown, rgbColor.B); + PopIgnoreChangedEvents(); + Utility.SetNumericUpDownValue(alphaUpDown, rgbColor.A); + + string hexText = GetHexNumericUpDownValue(rgbColor.R, rgbColor.G, rgbColor.B); + hexBox.Text = hexText; + + ColorBgra color = ColorBgra.FromColor(rgbColor); + + switch (WhichUserColor) + { + case WhichUserColor.Primary: + UserPrimaryColor = color; + OnRelinquishFocus(); + break; + + case WhichUserColor.Secondary: + UserSecondaryColor = color; + OnRelinquishFocus(); + break; + + default: + throw new InvalidEnumArgumentException("WhichUserColor property"); + } + + Update(); + } + + private void HsvGradientControl_ValueChanged(object sender, IndexEventArgs e) + { + if (IgnoreChangedEvents) + { + return; + } + + int hue; + if (sender == hueGradientControl) + { + hue = (hueGradientControl.Value * 360) / 255; + } + else + { + hue = (int)hueUpDown.Value; + } + + int saturation; + if (sender == saturationGradientControl) + { + saturation = (saturationGradientControl.Value * 100) / 255; + } + else + { + saturation = (int)saturationUpDown.Value; + } + + int value; + if (sender == valueGradientControl) + { + value = (valueGradientControl.Value * 100) / 255; + } + else + { + value = (int)valueUpDown.Value; + } + + HsvColor hsvColor = new HsvColor(hue, saturation, value); + colorWheel.HsvColor = hsvColor; + RgbColor rgbColor = hsvColor.ToRgb(); + ColorBgra color = ColorBgra.FromBgra((byte)rgbColor.Blue, (byte)rgbColor.Green, (byte)rgbColor.Red, (byte)alphaUpDown.Value); + + Utility.SetNumericUpDownValue(hueUpDown, hsvColor.Hue); + Utility.SetNumericUpDownValue(saturationUpDown, hsvColor.Saturation); + Utility.SetNumericUpDownValue(valueUpDown, hsvColor.Value); + + Utility.SetNumericUpDownValue(redUpDown, rgbColor.Red); + Utility.SetNumericUpDownValue(greenUpDown, rgbColor.Green); + Utility.SetNumericUpDownValue(blueUpDown, rgbColor.Blue); + + string hexText = GetHexNumericUpDownValue(rgbColor.Red, rgbColor.Green, rgbColor.Blue); + hexBox.Text = hexText; + + switch (WhichUserColor) + { + case WhichUserColor.Primary: + UserPrimaryColor = color; + OnRelinquishFocus(); + break; + + case WhichUserColor.Secondary: + UserSecondaryColor = color; + OnRelinquishFocus(); + break; + + default: + throw new InvalidEnumArgumentException("WhichUserColor property"); + } + + Update(); + } + + private void UpDown_Enter(object sender, System.EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + nud.Select(0, nud.Text.Length); + } + + private void UpDown_Leave(object sender, System.EventArgs e) + { + UpDown_ValueChanged(sender, e); + } + + private void HexUpDown_Enter(object sender, System.EventArgs e) + { + TextBox tb = (TextBox)sender; + tb.Select(0, tb.Text.Length); + } + + private void HexUpDown_Leave(object sender, System.EventArgs e) + { + hexBox.Text = hexBox.Text.ToUpper(); + UpDown_ValueChanged(sender, e); + } + + private void HexUpDown_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) + { + TextBox tb = (TextBox)sender; + + if (CheckHexBox(tb.Text)) + { + UpDown_ValueChanged(sender, e); + } + } + + private bool CheckHexBox(String checkHex) + { + int num; + + try + { + num = int.Parse(checkHex, System.Globalization.NumberStyles.HexNumber); + } + + catch (FormatException) + { + return false; + } + + catch (OverflowException) + { + return false; + } + + if ((num <= 16777215) && (num >= 0)) + { + return true; + } + else + { + return false; + } + } + + private void UpDown_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + + if (Utility.CheckNumericUpDown(nud)) + { + UpDown_ValueChanged(sender, e); + } + } + + private void UpDown_ValueChanged(object sender, System.EventArgs e) + { + if (sender == alphaUpDown || sender == alphaGradientControl) + { + if (sender == alphaGradientControl) + { + if (alphaUpDown.Value != (decimal)alphaGradientControl.Value) + { + alphaUpDown.Value = (decimal)alphaGradientControl.Value; + } + } + else + { + if (alphaGradientControl.Value != (int)alphaUpDown.Value) + { + alphaGradientControl.Value = (int)alphaUpDown.Value; + } + } + + PushIgnoreChangedEvents(); + + switch (WhichUserColor) + { + case WhichUserColor.Primary: + ColorBgra newPrimaryColor = ColorBgra.FromBgra(lastPrimaryColor.B, lastPrimaryColor.G, lastPrimaryColor.R, (byte)alphaGradientControl.Value); + this.userPrimaryColor = newPrimaryColor; + OnUserPrimaryColorChanged(newPrimaryColor); + break; + + case WhichUserColor.Secondary: + ColorBgra newSecondaryColor = ColorBgra.FromBgra(lastSecondaryColor.B, lastSecondaryColor.G, lastSecondaryColor.R, (byte)alphaGradientControl.Value); + this.userSecondaryColor = newSecondaryColor; + OnUserSecondaryColorChanged(newSecondaryColor); + break; + + default: + throw new InvalidEnumArgumentException("WhichUserColor property"); + } + + PopIgnoreChangedEvents(); + Update(); + } + else if (IgnoreChangedEvents) + { + return; + } + else + { + PushIgnoreChangedEvents(); + + if (sender == redUpDown || sender == greenUpDown || sender == blueUpDown) + { + string hexText = GetHexNumericUpDownValue((int)redUpDown.Value, (int)greenUpDown.Value, (int)blueUpDown.Value); + hexBox.Text = hexText; + + ColorBgra rgbColor = ColorBgra.FromBgra((byte)blueUpDown.Value, (byte)greenUpDown.Value, (byte)redUpDown.Value, (byte)alphaUpDown.Value); + + SetColorGradientMinMaxColorsRgb(rgbColor.R, rgbColor.G, rgbColor.B); + SetColorGradientMinMaxColorsAlpha(rgbColor.A); + SetColorGradientValuesRgb(rgbColor.R, rgbColor.G, rgbColor.B); + SetColorGradientMinMaxColorsAlpha(rgbColor.A); + + SyncHsvFromRgb(rgbColor); + OnUserColorChanged(rgbColor); + } + else if (sender == hexBox) + { + int hexInt = 0; + + if (hexBox.Text.Length > 0) + { + try + { + hexInt = int.Parse(hexBox.Text,System.Globalization.NumberStyles.HexNumber); + } + + // Needs to be changed so it reads what the RGB values were last + catch (FormatException) + { + hexInt = 0; + hexBox.Text = ""; + } + + catch (OverflowException) + { + hexInt = 16777215; + hexBox.Text = "FFFFFF"; + } + + if (!((hexInt <= 16777215) && (hexInt >= 0))) + { + hexInt = 16777215; + hexBox.Text = "FFFFFF"; + } + } + + int newRed = ((hexInt & 0xff0000) >> 16); + int newGreen = ((hexInt & 0x00ff00) >> 8); + int newBlue = (hexInt & 0x0000ff); + + Utility.SetNumericUpDownValue(redUpDown, newRed); + Utility.SetNumericUpDownValue(greenUpDown, newGreen); + Utility.SetNumericUpDownValue(blueUpDown, newBlue); + + SetColorGradientMinMaxColorsRgb(newRed, newGreen, newBlue); + SetColorGradientValuesRgb(newRed, newGreen, newBlue); + SetColorGradientMinMaxColorsAlpha((int)alphaUpDown.Value); + + ColorBgra rgbColor = ColorBgra.FromBgra((byte)newBlue, (byte)newGreen, (byte)newRed, (byte)alphaUpDown.Value); + SyncHsvFromRgb(rgbColor); + OnUserColorChanged(rgbColor); + } + else if (sender == hueUpDown || sender == saturationUpDown || sender == valueUpDown) + { + HsvColor oldHsvColor = colorWheel.HsvColor; + HsvColor newHsvColor = new HsvColor((int)hueUpDown.Value, (int)saturationUpDown.Value, (int)valueUpDown.Value); + + if (oldHsvColor != newHsvColor) + { + colorWheel.HsvColor = newHsvColor; + + SetColorGradientValuesHsv(newHsvColor.Hue, newHsvColor.Saturation, newHsvColor.Value); + SetColorGradientMinMaxColorsHsv(newHsvColor.Hue, newHsvColor.Saturation, newHsvColor.Value); + + SyncRgbFromHsv(newHsvColor); + RgbColor rgbColor = newHsvColor.ToRgb(); + OnUserColorChanged(ColorBgra.FromBgra((byte)rgbColor.Blue, (byte)rgbColor.Green, (byte)rgbColor.Red, (byte)alphaUpDown.Value)); + } + } + + PopIgnoreChangedEvents(); + } + } + + private void PushIgnoreChangedEvents() + { + ++this.ignoreChangedEvents; + } + + private void PopIgnoreChangedEvents() + { + --this.ignoreChangedEvents; + } + + private void MoreLessButton_Click(object sender, System.EventArgs e) + { + OnRelinquishFocus(); + + this.SuspendLayout(); + + if (this.inMoreState) + { + this.inMoreState = false; + Size newSize = lessSize; + this.moreLessButton.Text = this.moreText; + + int heightDelta = (moreModeHeaderSentinel.Height - lessModeHeaderSentinel.Height); + + newSize.Height -= heightDelta; + newSize.Height -= SystemLayer.UI.ScaleHeight(18); + + this.ClientSize = newSize; + } + else + { + this.inMoreState = true; + this.moreLessButton.Text = this.lessText; + + this.ClientSize = moreSize; + } + + this.swatchControl.Height = this.ClientSize.Height - SystemLayer.UI.ScaleHeight(4) - this.swatchControl.Top; + + this.ResumeLayout(false); + } + + private void ColorAddButton_Click(object sender, EventArgs e) + { + if (this.colorAddButton.Checked) + { + this.colorAddButton.Checked = false; + this.swatchControl.BlinkHighlight = false; + } + else + { + this.colorAddButton.Checked = true; + this.swatchControl.BlinkHighlight = true; + } + } + + private ColorBgra GetColorFromUpDowns() + { + int r = (int)this.redUpDown.Value; + int g = (int)this.greenUpDown.Value; + int b = (int)this.blueUpDown.Value; + int a = (int)this.alphaUpDown.Value; + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a); + } + + private void SwatchControl_ColorClicked(object sender, EventArgs> e) + { + List colors = new List(this.swatchControl.Colors); + + if (this.colorAddButton.Checked) + { + colors[e.Data.First] = GetColorFromUpDowns(); + this.swatchControl.Colors = colors.ToArray(); + this.colorAddButton.Checked = false; + this.swatchControl.BlinkHighlight = false; + } + else + { + ColorBgra color = colors[e.Data.First]; + + if (e.Data.Second == MouseButtons.Right) + { + SetUserColors(UserPrimaryColor, color); + } + else + { + switch (this.WhichUserColor) + { + case WhichUserColor.Primary: + this.UserPrimaryColor = color; + break; + + case WhichUserColor.Secondary: + this.UserSecondaryColor = color; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + } + + OnRelinquishFocus(); + } + + private void ColorPalettesButton_Click(object sender, EventArgs e) + { + this.colorPalettesButton.ShowDropDown(); + } + + private void ColorPalettesButton_DropDownOpening(object sender, EventArgs e) + { + this.colorPalettesButton.DropDownItems.Clear(); + + if (this.paletteCollection != null) + { + using (new WaitCursorChanger(this)) + { + this.paletteCollection.Load(); + } + + string[] paletteNames = this.paletteCollection.PaletteNames; + + foreach (string paletteName in paletteNames) + { + this.colorPalettesButton.DropDownItems.Add( + paletteName, + PdnResources.GetImageResource("Icons.SwatchIcon.png").Reference, + OnPaletteClickedHandler); + } + + if (paletteNames.Length > 0) + { + this.colorPalettesButton.DropDownItems.Add(new ToolStripSeparator()); + } + } + + this.colorPalettesButton.DropDownItems.Add( + PdnResources.GetString("ColorsForm.ColorPalettesButton.SaveCurrentPaletteAs.Text"), + PdnResources.GetImageResource("Icons.SavePaletteIcon.png").Reference, + OnSavePaletteAsHandler); + + this.colorPalettesButton.DropDownItems.Add( + PdnResources.GetString("ColorsForm.ColorPalettesButton.OpenPalettesFolder.Text"), + PdnResources.GetImageResource("Icons.ColorPalettes.png").Reference, + OnOpenPalettesFolderClickedHandler); + + this.colorPalettesButton.DropDownItems.Add(new ToolStripSeparator()); + + this.colorPalettesButton.DropDownItems.Add( + PdnResources.GetString("ColorsForm.ColorPalettesButton.ResetToDefaultPalette.Text"), + null, + OnResetPaletteHandler); + } + + private void OnSavePaletteAsHandler(object sender, EventArgs e) + { + using (SavePaletteDialog spd = new SavePaletteDialog()) + { + spd.PaletteNames = this.paletteCollection.PaletteNames; + spd.ShowDialog(this); + + if (spd.DialogResult == DialogResult.OK) + { + this.paletteCollection.AddOrUpdate(spd.PaletteName, this.swatchControl.Colors); + + using (new WaitCursorChanger(this)) + { + this.paletteCollection.Save(); + } + } + } + } + + private void OnResetPaletteHandler(object sender, EventArgs e) + { + this.swatchControl.Colors = PaletteCollection.DefaultPalette; + } + + private void OnPaletteClickedHandler(object sender, EventArgs e) + { + ToolStripItem tsi = sender as ToolStripItem; + + if (tsi != null) + { + ColorBgra[] palette = this.paletteCollection.Get(tsi.Text); + + if (palette != null) + { + this.swatchControl.Colors = palette; + } + } + } + + private void SwatchControl_ColorsChanged(object sender, EventArgs e) + { + string paletteString = PaletteCollection.GetPaletteSaveString(this.swatchControl.Colors); + Settings.CurrentUser.SetString(SettingNames.CurrentPalette, paletteString); + } + + private void OnOpenPalettesFolderClickedHandler(object sender, EventArgs e) + { + PaletteCollection.EnsurePalettesPathExists(); + + try + { + using (new WaitCursorChanger(this)) + { + Shell.BrowseFolder(this, PaletteCollection.PalettesPath); + } + } + + catch (Exception ex) + { + Tracing.Ping("Exception when launching PalettesPath (" + PaletteCollection.PalettesPath + "):" + ex.ToString()); + } + } + } +} diff --git a/src/ColorsForm.resx b/src/ColorsForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/CommonAction.cs b/src/CommonAction.cs new file mode 100644 index 0000000..8565384 --- /dev/null +++ b/src/CommonAction.cs @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Represents common UI actions. + /// + internal enum CommonAction + { + New, + Open, + Save, + Print, + Cut, + Copy, + Paste, + CropToSelection, + Deselect, + Undo, + Redo + } +} diff --git a/src/CommonActionsStrip.cs b/src/CommonActionsStrip.cs new file mode 100644 index 0000000..f5cc2ac --- /dev/null +++ b/src/CommonActionsStrip.cs @@ -0,0 +1,218 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class CommonActionsStrip + : ToolStripEx + { + private ToolStripSeparator separator0; + private ToolStripButton newButton; + private ToolStripButton openButton; + private ToolStripButton saveButton; + private ToolStripButton printButton; + private ToolStripSeparator separator1; + private ToolStripButton cutButton; + private ToolStripButton copyButton; + private ToolStripButton pasteButton; + private ToolStripButton cropButton; + private ToolStripButton deselectButton; + private ToolStripSeparator separator2; + private ToolStripButton undoButton; + private ToolStripButton redoButton; + + public CommonActionsStrip() + { + InitializeComponent(); + + this.newButton.Image = PdnResources.GetImageResource("Icons.MenuFileNewIcon.png").Reference; + this.openButton.Image = PdnResources.GetImageResource("Icons.MenuFileOpenIcon.png").Reference; + this.saveButton.Image = PdnResources.GetImageResource("Icons.MenuFileSaveIcon.png").Reference; + this.printButton.Image = PdnResources.GetImageResource("Icons.MenuFilePrintIcon.png").Reference; + this.cutButton.Image = PdnResources.GetImageResource("Icons.MenuEditCutIcon.png").Reference; + this.copyButton.Image = PdnResources.GetImageResource("Icons.MenuEditCopyIcon.png").Reference; + this.pasteButton.Image = PdnResources.GetImageResource("Icons.MenuEditPasteIcon.png").Reference; + this.cropButton.Image = PdnResources.GetImageResource("Icons.MenuImageCropIcon.png").Reference; + this.deselectButton.Image = PdnResources.GetImageResource("Icons.MenuEditDeselectIcon.png").Reference; + this.undoButton.Image = PdnResources.GetImageResource("Icons.MenuEditUndoIcon.png").Reference; + this.redoButton.Image = PdnResources.GetImageResource("Icons.MenuEditRedoIcon.png").Reference; + + this.newButton.ToolTipText = PdnResources.GetString("CommonAction.New"); + this.openButton.ToolTipText = PdnResources.GetString("CommonAction.Open"); + this.saveButton.ToolTipText = PdnResources.GetString("CommonAction.Save"); + this.printButton.ToolTipText = PdnResources.GetString("CommonAction.Print"); + this.cutButton.ToolTipText = PdnResources.GetString("CommonAction.Cut"); + this.copyButton.ToolTipText = PdnResources.GetString("CommonAction.Copy"); + this.pasteButton.ToolTipText = PdnResources.GetString("CommonAction.Paste"); + this.cropButton.ToolTipText = PdnResources.GetString("CommonAction.CropToSelection"); + this.deselectButton.ToolTipText = PdnResources.GetString("CommonAction.Deselect"); + this.undoButton.ToolTipText = PdnResources.GetString("CommonAction.Undo"); + this.redoButton.ToolTipText = PdnResources.GetString("CommonAction.Redo"); + + this.newButton.Tag = CommonAction.New; + this.openButton.Tag = CommonAction.Open; + this.saveButton.Tag = CommonAction.Save; + this.printButton.Tag = CommonAction.Print; + this.cutButton.Tag = CommonAction.Cut; + this.copyButton.Tag = CommonAction.Copy; + this.pasteButton.Tag = CommonAction.Paste; + this.cropButton.Tag = CommonAction.CropToSelection; + this.deselectButton.Tag = CommonAction.Deselect; + this.undoButton.Tag = CommonAction.Undo; + this.redoButton.Tag = CommonAction.Redo; + } + + private void InitializeComponent() + { + this.separator0 = new ToolStripSeparator(); + this.newButton = new ToolStripButton(); + this.openButton = new ToolStripButton(); + this.saveButton = new ToolStripButton(); + this.printButton = new ToolStripButton(); + this.separator1 = new ToolStripSeparator(); + this.cutButton = new ToolStripButton(); + this.copyButton = new ToolStripButton(); + this.pasteButton = new ToolStripButton(); + this.cropButton = new ToolStripButton(); + this.deselectButton = new ToolStripButton(); + this.separator2 = new ToolStripSeparator(); + this.undoButton = new ToolStripButton(); + this.redoButton = new ToolStripButton(); + + this.SuspendLayout(); + + this.Items.Add(this.separator0); + this.Items.Add(this.newButton); + this.Items.Add(this.openButton); + this.Items.Add(this.saveButton); + this.Items.Add(this.printButton); + this.Items.Add(this.separator1); + this.Items.Add(this.cutButton); + this.Items.Add(this.copyButton); + this.Items.Add(this.pasteButton); + this.Items.Add(this.cropButton); + this.Items.Add(this.deselectButton); + this.Items.Add(this.separator2); + this.Items.Add(this.undoButton); + this.Items.Add(this.redoButton); + + this.ResumeLayout(false); + } + + public event EventHandler> ButtonClick; + protected void OnButtonClick(CommonAction action) + { + if (ButtonClick != null) + { + ButtonClick(this, new EventArgs(action)); + } + } + + protected override void OnItemClicked(ToolStripItemClickedEventArgs e) + { + if (e.ClickedItem is ToolStripButton) + { + CommonAction action = (CommonAction)e.ClickedItem.Tag; + Tracing.LogFeature("CommonActionsStrip(" + action.ToString() + ")"); + OnButtonClick(action); + } + + base.OnItemClicked(e); + } + + public void SetButtonEnabled(CommonAction action, bool enabled) + { + ToolStripButton button = FindButton(action); + button.Enabled = enabled; + } + + public void SetButtonVisible(CommonAction action, bool visible) + { + ToolStripButton button = FindButton(action); + button.Visible = visible; + } + + public bool GetButtonEnabled(CommonAction action) + { + ToolStripButton button = FindButton(action); + return button.Enabled; + } + + public bool GetButtonVisible(CommonAction action) + { + ToolStripButton button = FindButton(action); + return button.Visible; + } + + private ToolStripButton FindButton(CommonAction action) + { + ToolStripButton button; + + switch (action) + { + case CommonAction.New: + button = this.newButton; + break; + + case CommonAction.Open: + button = this.openButton; + break; + + case CommonAction.Save: + button = this.saveButton; + break; + + case CommonAction.Print: + button = this.printButton; + break; + + case CommonAction.Cut: + button = this.cutButton; + break; + + case CommonAction.Copy: + button = this.copyButton; + break; + + case CommonAction.Paste: + button = this.pasteButton; + break; + + case CommonAction.CropToSelection: + button = this.cropButton; + break; + + case CommonAction.Deselect: + button = this.deselectButton; + break; + + case CommonAction.Undo: + button = this.undoButton; + break; + + case CommonAction.Redo: + button = this.redoButton; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return button; + } + } +} diff --git a/src/Core/AngleChooserControl.cs b/src/Core/AngleChooserControl.cs new file mode 100644 index 0000000..1a65425 --- /dev/null +++ b/src/Core/AngleChooserControl.cs @@ -0,0 +1,267 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public class AngleChooserControl + : UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + private bool tracking = false; + private bool hover = false; + private Point lastMouseXY; + + public event EventHandler ValueChanged; + protected virtual void OnValueChanged() + { + if (ValueChanged != null) + { + ValueChanged(this, EventArgs.Empty); + } + } + + public double angleValue; + public int Value + { + get + { + return (int)angleValue; + } + + set + { + double v = value % 360; + if (angleValue != v) + { + angleValue = v; + OnValueChanged(); + Invalidate(); + } + } + } + /// + /// ValueDouble exposes the double-precision angle + /// + public double ValueDouble + { + get + { + return angleValue; + } + + set + { + double v = Math.IEEERemainder(value, 360.0); + if (angleValue != v) + { + angleValue = v; + OnValueChanged(); + Invalidate(); + } + } + } + + private void DrawToGraphics(Graphics g) + { + g.Clear(this.BackColor); + + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.AntiAlias; + + RectangleF ourRect = RectangleF.Inflate(ClientRectangle, -1, -1); + double diameter = Math.Min(ourRect.Width, ourRect.Height); + + double radius = (diameter / 2.0); + + PointF center = new PointF( + (float)(ourRect.X + radius), + (float)(ourRect.Y + radius)); + + double theta = (this.angleValue * 2.0 * Math.PI) / 360.0; + + RectangleF ellipseRect = new RectangleF(ourRect.Location, new SizeF((float)diameter, (float)diameter)); + g.FillEllipse(SystemBrushes.ControlLightLight, RectangleF.Inflate(ellipseRect, -2, -2)); + + RectangleF ellipseOutlineRect = this.hover ? RectangleF.Inflate(ellipseRect, -1.0f, -1.0f) : ellipseRect; + + using (Pen ellipseOutlinePen = new Pen(SystemColors.ControlDark)) + { + ellipseOutlinePen.Width = this.hover ? 2.0f : 1.0f; + g.DrawEllipse(ellipseOutlinePen, ellipseOutlineRect); + } + + double endPointRadius = radius - 2; + PointF endPoint = new PointF( + (float)(center.X + (endPointRadius * Math.Cos(theta))), + (float)(center.Y - (endPointRadius * Math.Sin(theta)))); + + float gripSize = 2.5f; + RectangleF gripEllipseRect = new RectangleF(center.X - gripSize, center.Y - gripSize, gripSize * 2, gripSize * 2); + g.FillEllipse(SystemBrushes.ControlDark, gripEllipseRect); + + using (Pen anglePen = (Pen)SystemPens.ControlDark.Clone()) + { + anglePen.Width = 2.0f; + anglePen.Alignment = PenAlignment.Center; + g.DrawLine(anglePen, center, endPoint); + } + + g.SmoothingMode = oldSM; + } + + protected override void OnPaint(PaintEventArgs e) + { + DrawToGraphics(e.Graphics); + } + + protected override void OnMouseEnter(EventArgs e) + { + this.hover = true; + Invalidate(true); + base.OnMouseEnter(e); + } + + protected override void OnMouseLeave(EventArgs e) + { + this.hover = false; + Invalidate(true); + base.OnMouseLeave(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + tracking = true; + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + tracking = false; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove (e); + + lastMouseXY = new Point(e.X, e.Y); + + if (tracking) + { + Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2); + int diameter = Math.Min(ourRect.Width, ourRect.Height); + Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2)); + + int dx = e.X - center.X; + int dy = e.Y - center.Y; + double theta = Math.Atan2(-dy, dx); + + double newAngle = (theta * 360) / (2 * Math.PI); + + if ((ModifierKeys & Keys.Shift) != 0) + { + const double constraintAngle = 15.0; + + double multiple = newAngle / constraintAngle; + double top = Math.Floor(multiple); + double topDelta = Math.Abs(top - multiple); + double bottom = Math.Ceiling(multiple); + double bottomDelta = Math.Abs(bottom - multiple); + + double bestMultiple; + if (bottomDelta < topDelta) + { + bestMultiple = bottom; + } + else + { + bestMultiple = top; + } + + newAngle = bestMultiple * constraintAngle; + } + + this.ValueDouble = newAngle; + + Update(); + } + } + + protected override void OnClick(EventArgs e) + { + base.OnClick (e); + tracking = true; + OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, lastMouseXY.X, lastMouseXY.Y, 0)); + tracking = false; + } + + protected override void OnDoubleClick(EventArgs e) + { + base.OnDoubleClick (e); + tracking = true; + OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, lastMouseXY.X, lastMouseXY.Y, 0)); + tracking = false; + } + + public AngleChooserControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + SetStyle(ControlStyles.Selectable, false); + SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw, true); + + DoubleBuffered = true; + + TabStop = false; + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // AngleChooserControl + // + this.Name = "AngleChooserControl"; + this.Size = new System.Drawing.Size(168, 144); + + } + #endregion + } +} diff --git a/src/Core/AngleChooserControl.resx b/src/Core/AngleChooserControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/ArrowButton.cs b/src/Core/ArrowButton.cs new file mode 100644 index 0000000..0d403b5 --- /dev/null +++ b/src/Core/ArrowButton.cs @@ -0,0 +1,433 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + public sealed class ArrowButton + : ButtonBase + { + private ArrowDirection arrowDirection = ArrowDirection.Right; + private bool drawWithGradient = false; + private bool reverseArrowColors = false; + private float arrowOutlineWidth = 1.0f; + private bool forcedPushed = false; + private Surface backBufferSurface = null; + private RenderArgs backBuffer = null; + + public bool ForcedPushedAppearance + { + get + { + return this.forcedPushed; + } + + set + { + if (this.forcedPushed != value) + { + this.forcedPushed = value; + Invalidate(); + } + } + } + + public bool ReverseArrowColors + { + get + { + return this.reverseArrowColors; + } + + set + { + if (this.reverseArrowColors != value) + { + this.reverseArrowColors = value; + Invalidate(); + } + } + } + + public float ArrowOutlineWidth + { + get + { + return this.arrowOutlineWidth; + } + + set + { + if (this.arrowOutlineWidth != value) + { + this.arrowOutlineWidth = value; + Invalidate(); + } + } + } + + public ArrowDirection ArrowDirection + { + get + { + return this.arrowDirection; + } + + set + { + if (this.arrowDirection != value) + { + this.arrowDirection = value; + Invalidate(); + } + } + } + + public bool DrawWithGradient + { + get + { + return this.drawWithGradient; + } + + set + { + if (this.drawWithGradient != value) + { + this.drawWithGradient = value; + Invalidate(); + } + } + } + + public ArrowButton() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.BackColor = Color.Transparent; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.backBuffer != null) + { + this.backBuffer.Dispose(); + this.backBuffer = null; + } + + if (this.backBufferSurface != null) + { + this.backBufferSurface.Dispose(); + this.backBufferSurface = null; + } + } + + base.Dispose(disposing); + } + + protected override void OnPaintButton(Graphics g, PushButtonState state, bool drawFocusCues, bool drawKeyboardCues) + { + PushButtonState newState; + + if (this.forcedPushed) + { + newState = PushButtonState.Pressed; + } + else + { + newState = state; + } + + OnPaintButtonImpl(g, newState, drawFocusCues, drawKeyboardCues); + } + + private void OnPaintButtonImpl(Graphics g, PushButtonState state, bool drawFocusCues, bool drawKeyboardCues) + { + Color backColor; + Color outlineColor; + Color arrowFillColor; + Color arrowOutlineColor; + + switch (state) + { + case PushButtonState.Disabled: + backColor = Color.Transparent; + outlineColor = BackColor; + arrowFillColor = Color.Gray; + arrowOutlineColor = Color.Black; + break; + + case PushButtonState.Hot: + backColor = Color.FromArgb(64, SystemColors.HotTrack); + outlineColor = backColor; + arrowFillColor = Color.Blue; + arrowOutlineColor = Color.White; + break; + + case PushButtonState.Default: + case PushButtonState.Normal: + backColor = Color.Transparent; + outlineColor = Color.Transparent; + arrowFillColor = Color.Black; + arrowOutlineColor = Color.White; + break; + + case PushButtonState.Pressed: + backColor = Color.FromArgb(192, SystemColors.Highlight); + outlineColor = Color.FromArgb(192, SystemColors.Highlight); + arrowFillColor = Color.Blue; + arrowOutlineColor = Color.White; + break; + + default: + throw new InvalidEnumArgumentException("buttonState"); + } + + // Draw parent background + IPaintBackground asIpb = Parent as IPaintBackground; + + if (!this.drawWithGradient || asIpb == null) + { + if (asIpb != null) + { + Rectangle screenRect = RectangleToScreen(ClientRectangle); + Rectangle parentRect = Parent.RectangleToClient(screenRect); + + g.TranslateTransform(-Left, -Top, MatrixOrder.Append); + asIpb.PaintBackground(g, parentRect); + g.TranslateTransform(+Left, +Top, MatrixOrder.Append); + } + else + { + using (SolidBrush backBrush = new SolidBrush(BackColor)) + { + g.FillRectangle(backBrush, ClientRectangle); + } + } + } + else + { + if (this.backBufferSurface != null && + (this.backBufferSurface.Width != ClientSize.Width || this.backBufferSurface.Height != ClientSize.Height)) + { + this.backBuffer.Dispose(); + this.backBuffer = null; + + this.backBufferSurface.Dispose(); + this.backBufferSurface = null; + } + + if (this.backBufferSurface == null) + { + this.backBufferSurface = new Surface(ClientSize.Width, ClientSize.Height); + this.backBuffer = new RenderArgs(this.backBufferSurface); + } + + Rectangle screenRect = RectangleToScreen(ClientRectangle); + Rectangle parentRect = Parent.RectangleToClient(screenRect); + + using (Graphics bg = Graphics.FromImage(this.backBuffer.Bitmap)) + { + bg.TranslateTransform(-Left, -Top, MatrixOrder.Append); + asIpb.PaintBackground(bg, parentRect); + } + + BitmapData bitmapData = this.backBuffer.Bitmap.LockBits( + new Rectangle(0, 0, this.backBuffer.Bitmap.Width, this.backBuffer.Bitmap.Height), + ImageLockMode.ReadWrite, + PixelFormat.Format32bppArgb); + + int startAlpha; + int finishAlpha; + + if (this.arrowDirection == ArrowDirection.Left || this.arrowDirection == ArrowDirection.Up) + { + startAlpha = 255; + finishAlpha = 0; + } + else if (this.arrowDirection == ArrowDirection.Right || this.ArrowDirection == ArrowDirection.Down) + { + startAlpha = 0; + finishAlpha = 255; + } + else + { + throw new InvalidEnumArgumentException("this.arrowDirection"); + } + + unsafe + { + if (this.arrowDirection == ArrowDirection.Left || this.arrowDirection == ArrowDirection.Right) + { + for (int x = 0; x < this.backBuffer.Bitmap.Width; ++x) + { + float lerp = (float)x / (float)(this.backBuffer.Bitmap.Width - 1); + + if (this.arrowDirection == ArrowDirection.Left) + { + lerp = 1.0f - (float)Math.Cos(lerp * (Math.PI / 2.0)); + } + else + { + lerp = (float)Math.Sin(lerp * (Math.PI / 2.0)); + } + + byte alpha = (byte)(startAlpha + ((int)(lerp * (finishAlpha - startAlpha)))); + byte* pb = (byte*)bitmapData.Scan0.ToPointer() + (x * 4) + 3; // *4 because 4-bytes per pixel, +3 to get to alpha channel + + for (int y = 0; y < this.backBuffer.Bitmap.Height; ++y) + { + *pb = alpha; + pb += bitmapData.Stride; + } + } + } + else if (this.arrowDirection == ArrowDirection.Up || this.arrowDirection == ArrowDirection.Down) + { + for (int y = 0; y < this.backBuffer.Bitmap.Height; ++y) + { + float lerp = (float)y / (float)(this.backBuffer.Bitmap.Height - 1); + lerp = 1.0f - (float)Math.Cos(lerp * (Math.PI / 2.0)); + + byte alpha = (byte)(startAlpha + ((int)(lerp * (finishAlpha - startAlpha)))); + byte* pb = (byte*)bitmapData.Scan0.ToPointer() + (y * bitmapData.Stride) + 3; // *Stride for access to start of row, +3 to get to alpha channel + + for (int x = 0; x < this.backBuffer.Bitmap.Width; ++x) + { + *pb = alpha; + pb += 4; // 4 for byte size of pixel + } + } + } + } + + this.backBuffer.Bitmap.UnlockBits(bitmapData); + bitmapData = null; + + g.DrawImage(this.backBuffer.Bitmap, new Point(0, 0)); + } + + using (SolidBrush fillBrush = new SolidBrush(backColor)) + { + g.FillRectangle(fillBrush, ClientRectangle); + } + + // Draw outline + using (Pen outlinePen = new Pen(outlineColor)) + { + g.DrawRectangle(outlinePen, new Rectangle(0, 0, ClientSize.Width - 1, ClientSize.Height - 1)); + } + + // Draw button + g.SmoothingMode = SmoothingMode.AntiAlias; + + const int arrowInset = 3; + int arrowSize = Math.Min(ClientSize.Width - arrowInset * 2, ClientSize.Height - arrowInset * 2) - 1; + + PointF a; + PointF b; + PointF c; + + switch (this.arrowDirection) + { + case ArrowDirection.Left: + a = new PointF(arrowInset, ClientSize.Height / 2); + b = new PointF(ClientSize.Width - arrowInset, (ClientSize.Height - arrowSize) / 2); + c = new PointF(ClientSize.Width - arrowInset, (ClientSize.Height + arrowSize) / 2); + break; + + case ArrowDirection.Right: + a = new PointF(ClientSize.Width - arrowInset, ClientSize.Height / 2); + b = new PointF(arrowInset, (ClientSize.Height - arrowSize) / 2); + c = new PointF(arrowInset, (ClientSize.Height + arrowSize) / 2); + break; + + case ArrowDirection.Up: + a = new PointF(ClientSize.Width / 2, (ClientSize.Height - arrowSize) / 2); + b = new PointF((ClientSize.Width - arrowSize) / 2, (ClientSize.Height + arrowSize) / 2); + c = new PointF((ClientSize.Width + arrowSize) / 2, (ClientSize.Height + arrowSize) / 2); + break; + + case ArrowDirection.Down: + a = new PointF(ClientSize.Width / 2, (ClientSize.Height + arrowSize) / 2); + b = new PointF((ClientSize.Width - arrowSize) / 2, (ClientSize.Height - arrowSize) / 2); + c = new PointF((ClientSize.Width + arrowSize) / 2, (ClientSize.Height - arrowSize) / 2); + break; + + default: + throw new InvalidEnumArgumentException("this.arrowDirection"); + } + + // SPIKE in order to get this rendering correctly right away + if (this.arrowDirection == ArrowDirection.Down) + { + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.None; + + float top = b.Y - 2; + float left = b.X; + float right = c.X; + int squareCount = (int)((right - left) / 3); + + Brush outlineBrush = new SolidBrush(arrowOutlineColor); + Brush interiorBrush = new SolidBrush(arrowFillColor); + + g.FillRectangle(interiorBrush, left, top, right - left + 1, 3); + + ++left; + while (left < right) + { + RectangleF rect = new RectangleF(left, top + 1, 1, 1); + g.FillRectangle(outlineBrush, rect); + left += 2; + } + + outlineBrush.Dispose(); + outlineBrush = null; + + interiorBrush.Dispose(); + interiorBrush = null; + + a.Y += 2; + b.Y += 2; + c.Y += 2; + + g.SmoothingMode = oldSM; + } + + if (this.reverseArrowColors) + { + Utility.Swap(ref arrowFillColor, ref arrowOutlineColor); + } + + using (Brush buttonBrush = new SolidBrush(arrowFillColor)) + { + g.FillPolygon(buttonBrush, new PointF[] { a, b, c }); + } + + using (Pen buttonPen = new Pen(arrowOutlineColor, this.arrowOutlineWidth)) + { + g.DrawPolygon(buttonPen, new PointF[] { a, b, c }); + } + } + } +} diff --git a/src/Core/AssemblyInfo.cs b/src/Core/AssemblyInfo.cs new file mode 100644 index 0000000..d119109 --- /dev/null +++ b/src/Core/AssemblyInfo.cs @@ -0,0 +1,30 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Paint.NET Library")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Always)] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: ComVisibleAttribute(false)] diff --git a/src/Core/AutoSizeStrategy.cs b/src/Core/AutoSizeStrategy.cs new file mode 100644 index 0000000..8a8dc37 --- /dev/null +++ b/src/Core/AutoSizeStrategy.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum AutoSizeStrategy + { + None = 0, + AutoHeightAndExpandWidthToContent = 1, + ExpandHeightToContentAndKeepWidth = 2 + } +} diff --git a/src/Core/BinaryPixelOp.cs b/src/Core/BinaryPixelOp.cs new file mode 100644 index 0000000..169c421 --- /dev/null +++ b/src/Core/BinaryPixelOp.cs @@ -0,0 +1,169 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Defines a way to operate on a pixel, or a region of pixels, in a binary fashion. + /// That is, it is a simple function F that takes two parameters and returns a + /// result of the form: c = F(a, b) + /// + [Serializable] + public unsafe abstract class BinaryPixelOp + : PixelOp + { + public abstract ColorBgra Apply(ColorBgra lhs, ColorBgra rhs); + + public unsafe virtual void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) + { + unsafe + { + while (length > 0) + { + *dst = Apply(*lhs, *rhs); + ++dst; + ++lhs; + ++rhs; + --length; + } + } + } + + /// + /// Provides a default implementation for performing dst = F(lhs, rhs) over some rectangle of interest. + /// + /// The Surface to write pixels to. + /// The pixel offset that defines the upper-left of the rectangle-of-interest for the dst Surface. + /// The Surface to read pixels from for the lhs parameter given to the method ColorBgra Apply(ColorBgra, ColorBgra)b>. + /// The pixel offset that defines the upper-left of the rectangle-of-interest for the lhs Surface. + /// The Surface to read pixels from for the rhs parameter given to the method ColorBgra Apply(ColorBgra, ColorBgra) + /// The pixel offset that defines the upper-left of the rectangle-of-interest for the rhs Surface. + /// The size of the rectangles-of-interest for all Surfaces. + public void Apply(Surface dst, Point dstOffset, + Surface lhs, Point lhsOffset, + Surface rhs, Point rhsOffset, + Size roiSize) + { + // Bounds checking only enabled in Debug builds. +#if DEBUG + // Create bounding rectangles for each Surface + Rectangle dstRect = new Rectangle(dstOffset, roiSize); + Rectangle lhsRect = new Rectangle(lhsOffset, roiSize); + Rectangle rhsRect = new Rectangle(rhsOffset, roiSize); + + // Clip those rectangles to those Surface's bounding rectangles + Rectangle dstClip = Rectangle.Intersect(dstRect, dst.Bounds); + Rectangle lhsClip = Rectangle.Intersect(lhsRect, lhs.Bounds); + Rectangle rhsClip = Rectangle.Intersect(rhsRect, rhs.Bounds); + + // If any of those Rectangles actually got clipped, then throw an exception + if (dstRect != dstClip) + { + throw new ArgumentOutOfRangeException("roiSize", "Destination roi out of bounds"); + } + + if (lhsRect != lhsClip) + { + throw new ArgumentOutOfRangeException("roiSize", "lhs roi out of bounds"); + } + + if (rhsRect != rhsClip) + { + throw new ArgumentOutOfRangeException("roiSize", "rhs roi out of bounds"); + } +#endif + + // Cache the width and height properties + int width = roiSize.Width; + int height = roiSize.Height; + + // Do the work. + unsafe + { + for (int row = 0; row < height; ++row) + { + ColorBgra *dstPtr = dst.GetPointAddress(dstOffset.X, dstOffset.Y + row); + ColorBgra *lhsPtr = lhs.GetPointAddress(lhsOffset.X, lhsOffset.Y + row); + ColorBgra *rhsPtr = rhs.GetPointAddress(rhsOffset.X, rhsOffset.Y + row); + + Apply(dstPtr, lhsPtr, rhsPtr, width); + } + } + } + + public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) + { + unsafe + { + while (length > 0) + { + *dst = Apply(*dst, *src); + ++dst; + ++src; + --length; + } + } + } + + public override void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int roiLength) + { + Apply(dst.GetPointAddress(dstOffset), src.GetPointAddress(srcOffset), roiLength); + } + + public void Apply(Surface dst, Surface src) + { + if (dst.Size != src.Size) + { + throw new ArgumentException("dst.Size != src.Size"); + } + + unsafe + { + for (int y = 0; y < dst.Height; ++y) + { + ColorBgra *dstPtr = dst.GetRowAddressUnchecked(y); + ColorBgra *srcPtr = src.GetRowAddressUnchecked(y); + Apply(dstPtr, srcPtr, dst.Width); + } + } + } + + public void Apply(Surface dst, Surface lhs, Surface rhs) + { + if (dst.Size != lhs.Size) + { + throw new ArgumentException("dst.Size != lhs.Size"); + } + + if (lhs.Size != rhs.Size) + { + throw new ArgumentException("lhs.Size != rhs.Size"); + } + + unsafe + { + for (int y = 0; y < dst.Height; ++y) + { + ColorBgra *dstPtr = dst.GetRowAddressUnchecked(y); + ColorBgra *lhsPtr = lhs.GetRowAddressUnchecked(y); + ColorBgra *rhsPtr = rhs.GetRowAddressUnchecked(y); + + Apply(dstPtr, lhsPtr, rhsPtr, dst.Width); + } + } + } + + protected BinaryPixelOp() + { + } + } +} diff --git a/src/Core/BinaryPixelOps.cs b/src/Core/BinaryPixelOps.cs new file mode 100644 index 0000000..545a97d --- /dev/null +++ b/src/Core/BinaryPixelOps.cs @@ -0,0 +1,124 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Provides a set of standard BinaryPixelOps. + /// + public sealed class BinaryPixelOps + { + private BinaryPixelOps() + { + } + + // This is provided solely for data file format compatibility + [Obsolete("User UserBlendOps.NormalBlendOp instead", true)] + [Serializable] + public class AlphaBlend + : BinaryPixelOp + { + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + return lhs; + } + } + + /// + /// F(lhs, rhs) = rhs.A + lhs.R,g,b + /// + public class SetAlphaChannel + : BinaryPixelOp + { + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + lhs.A = rhs.A; + return lhs; + } + } + + /// + /// F(lhs, rhs) = lhs.R,g,b + rhs.A + /// + public class SetColorChannels + : BinaryPixelOp + { + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + rhs.A = lhs.A; + return rhs; + } + } + + /// + /// result(lhs,rhs) = rhs + /// + [Serializable] + public class AssignFromRhs + : BinaryPixelOp + { + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + return rhs; + } + + public unsafe override void Apply(ColorBgra* dst, ColorBgra* lhs, ColorBgra* rhs, int length) + { + Memory.Copy(dst, rhs, (ulong)length * (ulong)ColorBgra.SizeOf); + } + + public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + Memory.Copy(dst, src, (ulong)length * (ulong)ColorBgra.SizeOf); + } + + public AssignFromRhs() + { + } + } + + /// + /// result(lhs,rhs) = lhs + /// + [Serializable] + public class AssignFromLhs + : BinaryPixelOp + { + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + return lhs; + } + + public AssignFromLhs() + { + } + } + + [Serializable] + public class Swap + : BinaryPixelOp + { + BinaryPixelOp swapMyArgs; + + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) + { + return swapMyArgs.Apply(rhs, lhs); + } + + public Swap(BinaryPixelOp swapMyArgs) + { + this.swapMyArgs = swapMyArgs; + } + } + } +} diff --git a/src/Core/BitVector2D.cs b/src/Core/BitVector2D.cs new file mode 100644 index 0000000..72074ce --- /dev/null +++ b/src/Core/BitVector2D.cs @@ -0,0 +1,253 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class BitVector2D + : IBitVector2D, + ICloneable + { + private BitArray bitArray; + private int width; + private int height; + + public int Width + { + get + { + return width; + } + } + + public int Height + { + get + { + return height; + } + } + + public bool IsEmpty + { + get + { + return (width == 0) || (height == 0); + } + } + + public bool this[int x, int y] + { + get + { + CheckBounds(x, y); + return bitArray[x + (y * width)]; + } + + set + { + CheckBounds(x, y); + bitArray[x + (y * width)] = value; + } + } + + public bool this[System.Drawing.Point pt] + { + get + { + CheckBounds(pt.X, pt.Y); + return bitArray[pt.X + (pt.Y * width)]; + } + + set + { + CheckBounds(pt.X, pt.Y); + bitArray[pt.X + (pt.Y * width)] = value; + } + } + + public BitVector2D(int width, int height) + { + this.width = width; + this.height = height; + this.bitArray = new BitArray(width * height, false); + } + + public BitVector2D(BitVector2D copyMe) + { + this.width = copyMe.width; + this.height = copyMe.height; + this.bitArray = (BitArray)copyMe.bitArray.Clone(); + } + + private void CheckBounds(int x, int y) + { + if (x >= width || y >= height || x < 0 || y < 0) + { + throw new ArgumentOutOfRangeException(); + } + } + + public void Clear(bool newValue) + { + bitArray.SetAll(newValue); + } + + public bool Get(int x, int y) + { + return this[x, y]; + } + + public bool GetUnchecked(int x, int y) + { + return bitArray[x + (y * width)]; + } + + public void Set(int x, int y, bool newValue) + { + this[x, y] = newValue; + } + + public void Set(Point pt, bool newValue) + { + Set(pt.X, pt.Y, newValue); + } + + public void Set(Rectangle rect, bool newValue) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + Set(x, y, newValue); + } + } + } + + public void SetUnchecked(Rectangle rect, bool newValue) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + SetUnchecked(x, y, newValue); + } + } + } + + public void Set(Scanline scan, bool newValue) + { + int x = scan.X; + while (x < scan.X + scan.Length) + { + Set(x, scan.Y, newValue); + ++x; + } + } + + public void SetUnchecked(Scanline scan, bool newValue) + { + int x = scan.X; + while (x < scan.X + scan.Length) + { + SetUnchecked(x, scan.Y, newValue); + ++x; + } + } + + public void Set(PdnRegion region, bool newValue) + { + foreach (Rectangle rect in region.GetRegionScansReadOnlyInt()) + { + Set(rect, newValue); + } + } + + public void SetUnchecked(int x, int y, bool newValue) + { + bitArray[x + (y * width)] = newValue; + } + + public void Invert(int x, int y) + { + Set(x, y, !Get(x, y)); + } + + public unsafe void InvertUnchecked(int x, int y) + { + SetUnchecked(x, y, !GetUnchecked(x, y)); + } + + public void Invert(Point pt) + { + Invert(pt.X, pt.Y); + } + + public void Invert(Rectangle rect) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + Invert(x, y); + } + } + } + + public void InvertUnchecked(Rectangle rect) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + InvertUnchecked(x, y); + } + } + } + + public void Invert(Scanline scan) + { + int x = scan.X; + + while (x < scan.X + scan.Length) + { + Invert(x, scan.Y); + ++x; + } + } + + public void InvertUnchecked(Scanline scan) + { + int x = scan.X; + + while (x < scan.X + scan.Length) + { + InvertUnchecked(x, scan.Y); + ++x; + } + } + + public void Invert(PdnRegion region) + { + foreach (Rectangle rect in region.GetRegionScansReadOnlyInt()) + { + Invert(rect); + } + } + + public object Clone() + { + return new BitVector2D(this); + } + } +} diff --git a/src/Core/BitVector2DSurfaceAdapter.cs b/src/Core/BitVector2DSurfaceAdapter.cs new file mode 100644 index 0000000..380fc2f --- /dev/null +++ b/src/Core/BitVector2DSurfaceAdapter.cs @@ -0,0 +1,254 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Adapts a Surface class so it can be used as a two dimensional boolean array. + /// Elements are stored compactly, such that each pixel stores 32 boolean values. + /// However, the usable width is the same as that of the adapted surface. + /// (in other words, a surface that is 100 pixels wide can still only store 100 + /// booleans per row) + /// + public sealed class BitVector2DSurfaceAdapter + : IBitVector2D + { + private Surface surface; + + public BitVector2DSurfaceAdapter(Surface surface) + { + if (surface == null) + { + throw new ArgumentNullException("surface"); + } + + this.surface = surface; + } + + public int Width + { + get + { + return surface.Width; + } + } + + public int Height + { + get + { + return surface.Height; + } + } + + public bool IsEmpty + { + get + { + return (Width == 0) || (Height == 0); + } + } + + public void Clear(bool newValue) + { + unsafe + { + uint val = newValue ? 0xffffffff : 0; + + for (int y = 0; y < Height; ++y) + { + ColorBgra *row = surface.GetRowAddress(y); + + int w = (this.Width + 31) / 32; + + while (w > 0) + { + row->Bgra = val; + ++row; + --w; + } + } + } + } + + public bool Get(int x, int y) + { + if (x < 0 || x >= this.Width) + { + throw new ArgumentOutOfRangeException("x"); + } + + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException("y"); + } + + return GetUnchecked(x, y); + } + + public unsafe bool GetUnchecked(int x, int y) + { + int cx = x / 32; + int sx = x % 32; + uint mask = surface.GetPointAddressUnchecked(cx, y)->Bgra; + return 0 != (mask & (1 << sx)); + } + + public void Set(int x, int y, bool newValue) + { + if (x < 0 || x >= this.Width) + { + throw new ArgumentOutOfRangeException("x"); + } + + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException("y"); + } + + SetUnchecked(x, y, newValue); + } + + public void Set(Point pt, bool newValue) + { + Set(pt.X, pt.Y, newValue); + } + + public void Set(Rectangle rect, bool newValue) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + Set(x, y, newValue); + } + } + } + + public void Set(Scanline scan, bool newValue) + { + int x = scan.X; + + while (x < scan.X + scan.Length) + { + Set(x, scan.Y, newValue); + ++x; + } + } + + public void Set(PdnRegion region, bool newValue) + { + foreach (Rectangle rect in region.GetRegionScansReadOnlyInt()) + { + Set(rect, newValue); + } + } + + public unsafe void SetUnchecked(int x, int y, bool newValue) + { + int cx = x / 32; + int sx = x % 32; + ColorBgra *ptr = surface.GetPointAddressUnchecked(cx, y); + uint mask = ptr->Bgra; + uint slice = ((uint)1 << sx); + uint newMask; + + if (newValue) + { + newMask = mask | slice; + } + else + { + newMask = mask & ~slice; + } + + ptr->Bgra = newMask; + } + + public void Invert(int x, int y) + { + Set(x, y, !Get(x, y)); + } + + public void Invert(Point pt) + { + Invert(pt.X, pt.Y); + } + + public void Invert(Rectangle rect) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + Invert(x, y); + } + } + } + + public void Invert(Scanline scan) + { + int x = scan.X; + + while (x < scan.X + scan.Length) + { + Invert(x, scan.Y); + ++x; + } + } + + public void Invert(PdnRegion region) + { + foreach (Rectangle rect in region.GetRegionScansReadOnlyInt()) + { + Invert(rect); + } + } + + public bool this[System.Drawing.Point pt] + { + get + { + return this[pt.X, pt.Y]; + } + + set + { + this[pt.X, pt.Y] = value; + } + } + + public bool this[int x, int y] + { + get + { + return Get(x, y); + } + + set + { + Set(x, y, value); + } + } + + public BitVector2DSurfaceAdapter Clone() + { + Surface clonedSurface = this.surface.Clone(); + return new BitVector2DSurfaceAdapter(clonedSurface); + } + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/Core/BlockedPluginException.cs b/src/Core/BlockedPluginException.cs new file mode 100644 index 0000000..fd15864 --- /dev/null +++ b/src/Core/BlockedPluginException.cs @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + public class BlockedPluginException + : PdnException + { + public BlockedPluginException() + { + } + + public BlockedPluginException(string message) + : base(message) + { + } + + public BlockedPluginException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected BlockedPluginException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Core/ButtonBase.cs b/src/Core/ButtonBase.cs new file mode 100644 index 0000000..f35fc58 --- /dev/null +++ b/src/Core/ButtonBase.cs @@ -0,0 +1,249 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + /// + /// This class provides the logic for handling input and managing rendering + /// states for a typical button type control. Just provide the rendering code! + /// + public abstract class ButtonBase + : Control, + IButtonControl + { + private bool isDefault = false; + private bool drawPressed = false; + private bool drawHover = false; + private DialogResult dialogResult = DialogResult.None; + + public event EventHandler DialogResultChanged; + protected virtual void OnDialogResultChanged() + { + if (DialogResultChanged != null) + { + DialogResultChanged(this, EventArgs.Empty); + } + } + + public DialogResult DialogResult + { + get + { + return this.dialogResult; + } + + set + { + if (this.dialogResult != value) + { + this.dialogResult = value; + OnDialogResultChanged(); + } + } + } + + public event EventHandler IsDefaultChanged; + protected virtual void OnIsDefaultChanged() + { + if (IsDefaultChanged != null) + { + IsDefaultChanged(this, EventArgs.Empty); + } + } + + public bool IsDefault + { + get + { + return this.isDefault; + } + } + + protected internal ButtonBase() + { + UI.InitScaling(this); + + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.Selectable, true); + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.StandardDoubleClick, false); + + InitializeComponent(); + } + + private void InitializeComponent() + { + this.AccessibleRole = AccessibleRole.PushButton; + this.Name = "ButtonBase"; + this.DoubleBuffered = true; + this.TabStop = true; + } + + public void NotifyDefault(bool value) + { + if (this.isDefault != value) + { + this.isDefault = value; + OnIsDefaultChanged(); + Invalidate(true); + } + } + + public void PerformClick() + { + OnClick(EventArgs.Empty); + } + + private bool ContainsMouseCursor + { + get + { + Point mousePt = Control.MousePosition; + Rectangle screenRect = this.RectangleToScreen(ClientRectangle); + + return screenRect.Contains(mousePt); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + PushButtonState state; + + if (!Enabled) + { + state = PushButtonState.Disabled; + } + else if (this.drawPressed && ContainsMouseCursor) + { + state = PushButtonState.Pressed; + } + else if (this.drawHover) + { + state = PushButtonState.Hot; + } + else if (IsDefault) + { + state = PushButtonState.Default; + } + else + { + state = PushButtonState.Normal; + } + + bool drawFocusCues = ShowFocusCues && Focused; + bool drawKeyboardCues = ShowKeyboardCues; + + OnPaintButton(e.Graphics, state, drawFocusCues, drawKeyboardCues); + base.OnPaint(e); + } + + protected abstract void OnPaintButton( + Graphics g, + PushButtonState buttonState, + bool drawFocusCues, + bool drawKeyboardCues); + + protected override void OnEnabledChanged(EventArgs e) + { + Invalidate(true); + base.OnEnabledChanged(e); + } + + protected override void OnMouseEnter(EventArgs e) + { + this.drawHover = true; + Invalidate(true); + Update(); + base.OnMouseEnter(e); + } + + protected override void OnMouseDown(MouseEventArgs mevent) + { + this.drawPressed = true; + Invalidate(true); + base.OnMouseDown(mevent); + } + + protected override void OnMouseMove(MouseEventArgs mevent) + { + Invalidate(true); + base.OnMouseMove(mevent); + } + + protected override void OnMouseUp(MouseEventArgs mevent) + { + this.drawPressed = false; + Invalidate(true); + base.OnMouseUp(mevent); + } + + protected override void OnMouseLeave(EventArgs e) + { + this.drawHover = false; + Invalidate(true); + Update(); + base.OnMouseLeave(e); + } + + protected override void OnGotFocus(EventArgs e) + { + Invalidate(true); + base.OnGotFocus(e); + } + + protected override void OnLostFocus(EventArgs e) + { + this.drawPressed = false; + Invalidate(true); + base.OnLostFocus(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.KeyCode == Keys.Space) + { + this.drawPressed = true; + Refresh(); + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + if (e.KeyCode == Keys.Space) + { + this.drawPressed = false; + Refresh(); + PerformClick(); + } + + base.OnKeyUp(e); + } + + protected override bool ProcessMnemonic(char charCode) + { + if (CanSelect && IsMnemonic(charCode, this.Text)) + { + OnClick(EventArgs.Empty); + } + + return base.ProcessMnemonic(charCode); + } + } +} diff --git a/src/Core/ColorBgra.cs b/src/Core/ColorBgra.cs new file mode 100644 index 0000000..90949be --- /dev/null +++ b/src/Core/ColorBgra.cs @@ -0,0 +1,1851 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace PaintDotNet +{ + /// + /// This is our pixel format that we will work with. It is always 32-bits / 4-bytes and is + /// always laid out in BGRA order. + /// Generally used with the Surface class. + /// + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct ColorBgra + { + [FieldOffset(0)] + public byte B; + + [FieldOffset(1)] + public byte G; + + [FieldOffset(2)] + public byte R; + + [FieldOffset(3)] + public byte A; + + /// + /// Lets you change B, G, R, and A at the same time. + /// + [NonSerialized] + [FieldOffset(0)] + public uint Bgra; + + public const int BlueChannel = 0; + public const int GreenChannel = 1; + public const int RedChannel = 2; + public const int AlphaChannel = 3; + + public const int SizeOf = 4; + + public static ColorBgra ParseHexString(string hexString) + { + uint value = Convert.ToUInt32(hexString, 16); + return ColorBgra.FromUInt32(value); + } + + public string ToHexString() + { + int rgbNumber = (this.R << 16) | (this.G << 8) | this.B; + string colorString = Convert.ToString(rgbNumber, 16); + + while (colorString.Length < 6) + { + colorString = "0" + colorString; + } + + string alphaString = System.Convert.ToString(this.A, 16); + + while (alphaString.Length < 2) + { + alphaString = "0" + alphaString; + } + + colorString = alphaString + colorString; + + return colorString.ToUpper(); + } + + /// + /// Gets or sets the byte value of the specified color channel. + /// + public unsafe byte this[int channel] + { + get + { + if (channel < 0 || channel > 3) + { + throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]"); + } + + fixed (byte *p = &B) + { + return p[channel]; + } + } + + set + { + if (channel < 0 || channel > 3) + { + throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]"); + } + + fixed (byte *p = &B) + { + p[channel] = value; + } + } + } + + /// + /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored. + /// + /// A value in the range 0 to 1 inclusive. + public double GetIntensity() + { + return ((0.114 * (double)B) + (0.587 * (double)G) + (0.299 * (double)R)) / 255.0; + } + + /// + /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored. + /// + /// A value in the range 0 to 255 inclusive. + public byte GetIntensityByte() + { + return (byte)((7471 * B + 38470 * G + 19595 * R) >> 16); + } + + /// + /// Returns the maximum value out of the B, G, and R values. Alpha is ignored. + /// + /// + public byte GetMaxColorChannelValue() + { + return Math.Max(this.B, Math.Max(this.G, this.R)); + } + + /// + /// Returns the average of the B, G, and R values. Alpha is ignored. + /// + /// + public byte GetAverageColorChannelValue() + { + return (byte)((this.B + this.G + this.R) / 3); + } + + /// + /// Compares two ColorBgra instance to determine if they are equal. + /// + public static bool operator == (ColorBgra lhs, ColorBgra rhs) + { + return lhs.Bgra == rhs.Bgra; + } + + /// + /// Compares two ColorBgra instance to determine if they are not equal. + /// + public static bool operator != (ColorBgra lhs, ColorBgra rhs) + { + return lhs.Bgra != rhs.Bgra; + } + + /// + /// Compares two ColorBgra instance to determine if they are equal. + /// + public override bool Equals(object obj) + { + + if (obj != null && obj is ColorBgra && ((ColorBgra)obj).Bgra == this.Bgra) + { + return true; + } + else + { + return false; + } + } + + /// + /// Returns a hash code for this color value. + /// + /// + public override int GetHashCode() + { + unchecked + { + return (int)Bgra; + } + } + + /// + /// Gets the equivalent GDI+ PixelFormat. + /// + /// + /// This property always returns PixelFormat.Format32bppArgb. + /// + public static PixelFormat PixelFormat + { + get + { + return PixelFormat.Format32bppArgb; + } + } + + /// + /// Returns a new ColorBgra with the same color values but with a new alpha component value. + /// + public ColorBgra NewAlpha(byte newA) + { + return ColorBgra.FromBgra(B, G, R, newA); + } + + /// + /// Creates a new ColorBgra instance with the given color and alpha values. + /// + [Obsolete ("Use FromBgra() instead (make sure to swap the order of your b and r parameters)")] + public static ColorBgra FromRgba(byte r, byte g, byte b, byte a) + { + return FromBgra(b, g, r, a); + } + + /// + /// Creates a new ColorBgra instance with the given color values, and 255 for alpha. + /// + [Obsolete ("Use FromBgr() instead (make sure to swap the order of your b and r parameters)")] + public static ColorBgra FromRgb(byte r, byte g, byte b) + { + return FromBgr(b, g, r); + } + + /// + /// Creates a new ColorBgra instance with the given color and alpha values. + /// + public static ColorBgra FromBgra(byte b, byte g, byte r, byte a) + { + ColorBgra color = new ColorBgra(); + color.Bgra = BgraToUInt32(b, g, r, a); + return color; + } + + /// + /// Creates a new ColorBgra instance with the given color and alpha values. + /// + public static ColorBgra FromBgraClamped(int b, int g, int r, int a) + { + return FromBgra( + Utility.ClampToByte(b), + Utility.ClampToByte(g), + Utility.ClampToByte(r), + Utility.ClampToByte(a)); + } + + /// + /// Creates a new ColorBgra instance with the given color and alpha values. + /// + public static ColorBgra FromBgraClamped(float b, float g, float r, float a) + { + return FromBgra( + Utility.ClampToByte(b), + Utility.ClampToByte(g), + Utility.ClampToByte(r), + Utility.ClampToByte(a)); + } + + /// + /// Packs color and alpha values into a 32-bit integer. + /// + public static UInt32 BgraToUInt32(byte b, byte g, byte r, byte a) + { + return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24); + } + + /// + /// Packs color and alpha values into a 32-bit integer. + /// + public static UInt32 BgraToUInt32(int b, int g, int r, int a) + { + return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24); + } + + /// + /// Creates a new ColorBgra instance with the given color values, and 255 for alpha. + /// + public static ColorBgra FromBgr(byte b, byte g, byte r) + { + return FromBgra(b, g, r, 255); + } + + /// + /// Constructs a new ColorBgra instance with the given 32-bit value. + /// + public static ColorBgra FromUInt32(UInt32 bgra) + { + ColorBgra color = new ColorBgra(); + color.Bgra = bgra; + return color; + } + + /// + /// Constructs a new ColorBgra instance given a 32-bit signed integer that represents an R,G,B triple. + /// Alpha will be initialized to 255. + /// + public static ColorBgra FromOpaqueInt32(Int32 bgr) + { + if (bgr < 0 || bgr > 0xffffff) + { + throw new ArgumentOutOfRangeException("bgr", "must be in the range [0, 0xffffff]"); + } + + ColorBgra color = new ColorBgra(); + color.Bgra = (uint)bgr; + color.A = 255; + + return color; + } + + public static int ToOpaqueInt32(ColorBgra color) + { + if (color.A != 255) + { + throw new InvalidOperationException("Alpha value must be 255 for this to work"); + } + + return (int)(color.Bgra & 0xffffff); + } + + /// + /// Constructs a new ColorBgra instance from the values in the given Color instance. + /// + public static ColorBgra FromColor(Color c) + { + return FromBgra(c.B, c.G, c.R, c.A); + } + + /// + /// Converts this ColorBgra instance to a Color instance. + /// + public Color ToColor() + { + return Color.FromArgb(A, R, G, B); + } + + /// + /// Smoothly blends between two colors. + /// + public static ColorBgra Blend(ColorBgra ca, ColorBgra cb, byte cbAlpha) + { + uint caA = (uint)Utility.FastScaleByteByByte((byte)(255 - cbAlpha), ca.A); + uint cbA = (uint)Utility.FastScaleByteByByte(cbAlpha, cb.A); + uint cbAT = caA + cbA; + + uint r; + uint g; + uint b; + + if (cbAT == 0) + { + r = 0; + g = 0; + b = 0; + } + else + { + r = ((ca.R * caA) + (cb.R * cbA)) / cbAT; + g = ((ca.G * caA) + (cb.G * cbA)) / cbAT; + b = ((ca.B * caA) + (cb.B * cbA)) / cbAT; + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)cbAT); + } + + /// + /// Linearly interpolates between two color values. + /// + /// The color value that represents 0 on the lerp number line. + /// The color value that represents 1 on the lerp number line. + /// A value in the range [0, 1]. + /// + /// This method does a simple lerp on each color value and on the alpha channel. It does + /// not properly take into account the alpha channel's effect on color blending. + /// + public static ColorBgra Lerp(ColorBgra from, ColorBgra to, float frac) + { + ColorBgra ret = new ColorBgra(); + + ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac)); + ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac)); + ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac)); + ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac)); + + return ret; + } + + /// + /// Linearly interpolates between two color values. + /// + /// The color value that represents 0 on the lerp number line. + /// The color value that represents 1 on the lerp number line. + /// A value in the range [0, 1]. + /// + /// This method does a simple lerp on each color value and on the alpha channel. It does + /// not properly take into account the alpha channel's effect on color blending. + /// + public static ColorBgra Lerp(ColorBgra from, ColorBgra to, double frac) + { + ColorBgra ret = new ColorBgra(); + + ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac)); + ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac)); + ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac)); + ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac)); + + return ret; + } + + /// + /// Blends four colors together based on the given weight values. + /// + /// The blended color. + /// + /// The weights should be 16-bit fixed point numbers that add up to 65536 ("1.0"). + /// 4W16IP means "4 colors, weights, 16-bit integer precision" + /// + public static ColorBgra BlendColors4W16IP(ColorBgra c1, uint w1, ColorBgra c2, uint w2, ColorBgra c3, uint w3, ColorBgra c4, uint w4) + { +#if DEBUG + /* + if ((w1 + w2 + w3 + w4) != 65536) + { + throw new ArgumentOutOfRangeException("w1 + w2 + w3 + w4 must equal 65536!"); + } + * */ +#endif + + const uint ww = 32768; + uint af = (c1.A * w1) + (c2.A * w2) + (c3.A * w3) + (c4.A * w4); + uint a = (af + ww) >> 16; + + uint b; + uint g; + uint r; + + if (a == 0) + { + b = 0; + g = 0; + r = 0; + } + else + { + b = (uint)((((long)c1.A * c1.B * w1) + ((long)c2.A * c2.B * w2) + ((long)c3.A * c3.B * w3) + ((long)c4.A * c4.B * w4)) / af); + g = (uint)((((long)c1.A * c1.G * w1) + ((long)c2.A * c2.G * w2) + ((long)c3.A * c3.G * w3) + ((long)c4.A * c4.G * w4)) / af); + r = (uint)((((long)c1.A * c1.R * w1) + ((long)c2.A * c2.R * w2) + ((long)c3.A * c3.R * w3) + ((long)c4.A * c4.R * w4)) / af); + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a); + } + + /// + /// Blends the colors based on the given weight values. + /// + /// The array of color values. + /// The array of weight values. + /// + /// The weights should be fixed point numbers. + /// The total summation of the weight values will be treated as "1.0". + /// Each color will be blended in proportionally to its weight value respective to + /// the total summation of the weight values. + /// + /// + /// "WAIP" stands for "weights, arbitrary integer precision" + public static ColorBgra BlendColorsWAIP(ColorBgra[] c, uint[] w) + { + if (c.Length != w.Length) + { + throw new ArgumentException("c.Length != w.Length"); + } + + if (c.Length == 0) + { + return ColorBgra.FromUInt32(0); + } + + long wsum = 0; + long asum = 0; + + for (int i = 0; i < w.Length; ++i) + { + wsum += w[i]; + asum += c[i].A * w[i]; + } + + uint a = (uint)((asum + (wsum >> 1)) / wsum); + + long b; + long g; + long r; + + if (a == 0) + { + b = 0; + g = 0; + r = 0; + } + else + { + b = 0; + g = 0; + r = 0; + + for (int i = 0; i < c.Length; ++i) + { + b += (long)c[i].A * c[i].B * w[i]; + g += (long)c[i].A * c[i].G * w[i]; + r += (long)c[i].A * c[i].R * w[i]; + } + + b /= asum; + g /= asum; + r /= asum; + } + + return ColorBgra.FromUInt32((uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24)); + } + + /// + /// Blends the colors based on the given weight values. + /// + /// The array of color values. + /// The array of weight values. + /// + /// Each color will be blended in proportionally to its weight value respective to + /// the total summation of the weight values. + /// + /// + /// "WAIP" stands for "weights, floating-point" + public static ColorBgra BlendColorsWFP(ColorBgra[] c, double[] w) + { + if (c.Length != w.Length) + { + throw new ArgumentException("c.Length != w.Length"); + } + + if (c.Length == 0) + { + return ColorBgra.Transparent; + } + + double wsum = 0; + double asum = 0; + + for (int i = 0; i < w.Length; ++i) + { + wsum += w[i]; + asum += (double)c[i].A * w[i]; + } + + double a = asum / wsum; + double aMultWsum = a * wsum; + + double b; + double g; + double r; + + if (asum == 0) + { + b = 0; + g = 0; + r = 0; + } + else + { + b = 0; + g = 0; + r = 0; + + for (int i = 0; i < c.Length; ++i) + { + b += (double)c[i].A * c[i].B * w[i]; + g += (double)c[i].A * c[i].G * w[i]; + r += (double)c[i].A * c[i].R * w[i]; + } + + b /= aMultWsum; + g /= aMultWsum; + r /= aMultWsum; + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a); + } + + public static ColorBgra Blend(ColorBgra[] colors) + { + unsafe + { + fixed (ColorBgra* pColors = colors) + { + return Blend(pColors, colors.Length); + } + } + } + + /// + /// Smoothly blends the given colors together, assuming equal weighting for each one. + /// + /// + /// + /// + public unsafe static ColorBgra Blend(ColorBgra* colors, int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException("count must be 0 or greater"); + } + + if (count == 0) + { + return ColorBgra.Transparent; + } + + ulong aSum = 0; + + for (int i = 0; i < count; ++i) + { + aSum += (ulong)colors[i].A; + } + + byte b = 0; + byte g = 0; + byte r = 0; + byte a = (byte)(aSum / (ulong)count); + + if (aSum != 0) + { + ulong bSum = 0; + ulong gSum = 0; + ulong rSum = 0; + + for (int i = 0; i < count; ++i) + { + bSum += (ulong)(colors[i].A * colors[i].B); + gSum += (ulong)(colors[i].A * colors[i].G); + rSum += (ulong)(colors[i].A * colors[i].R); + } + + b = (byte)(bSum / aSum); + g = (byte)(gSum / aSum); + r = (byte)(rSum / aSum); + } + + return ColorBgra.FromBgra(b, g, r, a); + } + + public override string ToString() + { + return "B: " + B + ", G: " + G + ", R: " + R + ", A: " + A; + } + + /// + /// Casts a ColorBgra to a UInt32. + /// + public static explicit operator UInt32(ColorBgra color) + { + return color.Bgra; + } + + /// + /// Casts a UInt32 to a ColorBgra. + /// + public static explicit operator ColorBgra(UInt32 uint32) + { + return ColorBgra.FromUInt32(uint32); + } + + // Colors: copied from System.Drawing.Color's list (don't worry I didn't type it in + // manually, I used a code generator w/ reflection ...) + + public static ColorBgra Transparent + { + get + { + return ColorBgra.FromBgra(255, 255, 255, 0); + } + } + + public static ColorBgra AliceBlue + { + get + { + return ColorBgra.FromBgra(255, 248, 240, 255); + } + } + + public static ColorBgra AntiqueWhite + { + get + { + return ColorBgra.FromBgra(215, 235, 250, 255); + } + } + + public static ColorBgra Aqua + { + get + { + return ColorBgra.FromBgra(255, 255, 0, 255); + } + } + + public static ColorBgra Aquamarine + { + get + { + return ColorBgra.FromBgra(212, 255, 127, 255); + } + } + + public static ColorBgra Azure + { + get + { + return ColorBgra.FromBgra(255, 255, 240, 255); + } + } + + public static ColorBgra Beige + { + get + { + return ColorBgra.FromBgra(220, 245, 245, 255); + } + } + + public static ColorBgra Bisque + { + get + { + return ColorBgra.FromBgra(196, 228, 255, 255); + } + } + + public static ColorBgra Black + { + get + { + return ColorBgra.FromBgra(0, 0, 0, 255); + } + } + + public static ColorBgra BlanchedAlmond + { + get + { + return ColorBgra.FromBgra(205, 235, 255, 255); + } + } + + public static ColorBgra Blue + { + get + { + return ColorBgra.FromBgra(255, 0, 0, 255); + } + } + + public static ColorBgra BlueViolet + { + get + { + return ColorBgra.FromBgra(226, 43, 138, 255); + } + } + + public static ColorBgra Brown + { + get + { + return ColorBgra.FromBgra(42, 42, 165, 255); + } + } + + public static ColorBgra BurlyWood + { + get + { + return ColorBgra.FromBgra(135, 184, 222, 255); + } + } + + public static ColorBgra CadetBlue + { + get + { + return ColorBgra.FromBgra(160, 158, 95, 255); + } + } + + public static ColorBgra Chartreuse + { + get + { + return ColorBgra.FromBgra(0, 255, 127, 255); + } + } + + public static ColorBgra Chocolate + { + get + { + return ColorBgra.FromBgra(30, 105, 210, 255); + } + } + + public static ColorBgra Coral + { + get + { + return ColorBgra.FromBgra(80, 127, 255, 255); + } + } + + public static ColorBgra CornflowerBlue + { + get + { + return ColorBgra.FromBgra(237, 149, 100, 255); + } + } + + public static ColorBgra Cornsilk + { + get + { + return ColorBgra.FromBgra(220, 248, 255, 255); + } + } + + public static ColorBgra Crimson + { + get + { + return ColorBgra.FromBgra(60, 20, 220, 255); + } + } + + public static ColorBgra Cyan + { + get + { + return ColorBgra.FromBgra(255, 255, 0, 255); + } + } + + public static ColorBgra DarkBlue + { + get + { + return ColorBgra.FromBgra(139, 0, 0, 255); + } + } + + public static ColorBgra DarkCyan + { + get + { + return ColorBgra.FromBgra(139, 139, 0, 255); + } + } + + public static ColorBgra DarkGoldenrod + { + get + { + return ColorBgra.FromBgra(11, 134, 184, 255); + } + } + + public static ColorBgra DarkGray + { + get + { + return ColorBgra.FromBgra(169, 169, 169, 255); + } + } + + public static ColorBgra DarkGreen + { + get + { + return ColorBgra.FromBgra(0, 100, 0, 255); + } + } + + public static ColorBgra DarkKhaki + { + get + { + return ColorBgra.FromBgra(107, 183, 189, 255); + } + } + + public static ColorBgra DarkMagenta + { + get + { + return ColorBgra.FromBgra(139, 0, 139, 255); + } + } + + public static ColorBgra DarkOliveGreen + { + get + { + return ColorBgra.FromBgra(47, 107, 85, 255); + } + } + + public static ColorBgra DarkOrange + { + get + { + return ColorBgra.FromBgra(0, 140, 255, 255); + } + } + + public static ColorBgra DarkOrchid + { + get + { + return ColorBgra.FromBgra(204, 50, 153, 255); + } + } + + public static ColorBgra DarkRed + { + get + { + return ColorBgra.FromBgra(0, 0, 139, 255); + } + } + + public static ColorBgra DarkSalmon + { + get + { + return ColorBgra.FromBgra(122, 150, 233, 255); + } + } + + public static ColorBgra DarkSeaGreen + { + get + { + return ColorBgra.FromBgra(139, 188, 143, 255); + } + } + + public static ColorBgra DarkSlateBlue + { + get + { + return ColorBgra.FromBgra(139, 61, 72, 255); + } + } + + public static ColorBgra DarkSlateGray + { + get + { + return ColorBgra.FromBgra(79, 79, 47, 255); + } + } + + public static ColorBgra DarkTurquoise + { + get + { + return ColorBgra.FromBgra(209, 206, 0, 255); + } + } + + public static ColorBgra DarkViolet + { + get + { + return ColorBgra.FromBgra(211, 0, 148, 255); + } + } + + public static ColorBgra DeepPink + { + get + { + return ColorBgra.FromBgra(147, 20, 255, 255); + } + } + + public static ColorBgra DeepSkyBlue + { + get + { + return ColorBgra.FromBgra(255, 191, 0, 255); + } + } + + public static ColorBgra DimGray + { + get + { + return ColorBgra.FromBgra(105, 105, 105, 255); + } + } + + public static ColorBgra DodgerBlue + { + get + { + return ColorBgra.FromBgra(255, 144, 30, 255); + } + } + + public static ColorBgra Firebrick + { + get + { + return ColorBgra.FromBgra(34, 34, 178, 255); + } + } + + public static ColorBgra FloralWhite + { + get + { + return ColorBgra.FromBgra(240, 250, 255, 255); + } + } + + public static ColorBgra ForestGreen + { + get + { + return ColorBgra.FromBgra(34, 139, 34, 255); + } + } + + public static ColorBgra Fuchsia + { + get + { + return ColorBgra.FromBgra(255, 0, 255, 255); + } + } + + public static ColorBgra Gainsboro + { + get + { + return ColorBgra.FromBgra(220, 220, 220, 255); + } + } + + public static ColorBgra GhostWhite + { + get + { + return ColorBgra.FromBgra(255, 248, 248, 255); + } + } + + public static ColorBgra Gold + { + get + { + return ColorBgra.FromBgra(0, 215, 255, 255); + } + } + + public static ColorBgra Goldenrod + { + get + { + return ColorBgra.FromBgra(32, 165, 218, 255); + } + } + + public static ColorBgra Gray + { + get + { + return ColorBgra.FromBgra(128, 128, 128, 255); + } + } + + public static ColorBgra Green + { + get + { + return ColorBgra.FromBgra(0, 128, 0, 255); + } + } + + public static ColorBgra GreenYellow + { + get + { + return ColorBgra.FromBgra(47, 255, 173, 255); + } + } + + public static ColorBgra Honeydew + { + get + { + return ColorBgra.FromBgra(240, 255, 240, 255); + } + } + + public static ColorBgra HotPink + { + get + { + return ColorBgra.FromBgra(180, 105, 255, 255); + } + } + + public static ColorBgra IndianRed + { + get + { + return ColorBgra.FromBgra(92, 92, 205, 255); + } + } + + public static ColorBgra Indigo + { + get + { + return ColorBgra.FromBgra(130, 0, 75, 255); + } + } + + public static ColorBgra Ivory + { + get + { + return ColorBgra.FromBgra(240, 255, 255, 255); + } + } + + public static ColorBgra Khaki + { + get + { + return ColorBgra.FromBgra(140, 230, 240, 255); + } + } + + public static ColorBgra Lavender + { + get + { + return ColorBgra.FromBgra(250, 230, 230, 255); + } + } + + public static ColorBgra LavenderBlush + { + get + { + return ColorBgra.FromBgra(245, 240, 255, 255); + } + } + + public static ColorBgra LawnGreen + { + get + { + return ColorBgra.FromBgra(0, 252, 124, 255); + } + } + + public static ColorBgra LemonChiffon + { + get + { + return ColorBgra.FromBgra(205, 250, 255, 255); + } + } + + public static ColorBgra LightBlue + { + get + { + return ColorBgra.FromBgra(230, 216, 173, 255); + } + } + + public static ColorBgra LightCoral + { + get + { + return ColorBgra.FromBgra(128, 128, 240, 255); + } + } + + public static ColorBgra LightCyan + { + get + { + return ColorBgra.FromBgra(255, 255, 224, 255); + } + } + + public static ColorBgra LightGoldenrodYellow + { + get + { + return ColorBgra.FromBgra(210, 250, 250, 255); + } + } + + public static ColorBgra LightGreen + { + get + { + return ColorBgra.FromBgra(144, 238, 144, 255); + } + } + + public static ColorBgra LightGray + { + get + { + return ColorBgra.FromBgra(211, 211, 211, 255); + } + } + + public static ColorBgra LightPink + { + get + { + return ColorBgra.FromBgra(193, 182, 255, 255); + } + } + + public static ColorBgra LightSalmon + { + get + { + return ColorBgra.FromBgra(122, 160, 255, 255); + } + } + + public static ColorBgra LightSeaGreen + { + get + { + return ColorBgra.FromBgra(170, 178, 32, 255); + } + } + + public static ColorBgra LightSkyBlue + { + get + { + return ColorBgra.FromBgra(250, 206, 135, 255); + } + } + + public static ColorBgra LightSlateGray + { + get + { + return ColorBgra.FromBgra(153, 136, 119, 255); + } + } + + public static ColorBgra LightSteelBlue + { + get + { + return ColorBgra.FromBgra(222, 196, 176, 255); + } + } + + public static ColorBgra LightYellow + { + get + { + return ColorBgra.FromBgra(224, 255, 255, 255); + } + } + + public static ColorBgra Lime + { + get + { + return ColorBgra.FromBgra(0, 255, 0, 255); + } + } + + public static ColorBgra LimeGreen + { + get + { + return ColorBgra.FromBgra(50, 205, 50, 255); + } + } + + public static ColorBgra Linen + { + get + { + return ColorBgra.FromBgra(230, 240, 250, 255); + } + } + + public static ColorBgra Magenta + { + get + { + return ColorBgra.FromBgra(255, 0, 255, 255); + } + } + + public static ColorBgra Maroon + { + get + { + return ColorBgra.FromBgra(0, 0, 128, 255); + } + } + + public static ColorBgra MediumAquamarine + { + get + { + return ColorBgra.FromBgra(170, 205, 102, 255); + } + } + + public static ColorBgra MediumBlue + { + get + { + return ColorBgra.FromBgra(205, 0, 0, 255); + } + } + + public static ColorBgra MediumOrchid + { + get + { + return ColorBgra.FromBgra(211, 85, 186, 255); + } + } + + public static ColorBgra MediumPurple + { + get + { + return ColorBgra.FromBgra(219, 112, 147, 255); + } + } + + public static ColorBgra MediumSeaGreen + { + get + { + return ColorBgra.FromBgra(113, 179, 60, 255); + } + } + + public static ColorBgra MediumSlateBlue + { + get + { + return ColorBgra.FromBgra(238, 104, 123, 255); + } + } + + public static ColorBgra MediumSpringGreen + { + get + { + return ColorBgra.FromBgra(154, 250, 0, 255); + } + } + + public static ColorBgra MediumTurquoise + { + get + { + return ColorBgra.FromBgra(204, 209, 72, 255); + } + } + + public static ColorBgra MediumVioletRed + { + get + { + return ColorBgra.FromBgra(133, 21, 199, 255); + } + } + + public static ColorBgra MidnightBlue + { + get + { + return ColorBgra.FromBgra(112, 25, 25, 255); + } + } + + public static ColorBgra MintCream + { + get + { + return ColorBgra.FromBgra(250, 255, 245, 255); + } + } + + public static ColorBgra MistyRose + { + get + { + return ColorBgra.FromBgra(225, 228, 255, 255); + } + } + + public static ColorBgra Moccasin + { + get + { + return ColorBgra.FromBgra(181, 228, 255, 255); + } + } + + public static ColorBgra NavajoWhite + { + get + { + return ColorBgra.FromBgra(173, 222, 255, 255); + } + } + + public static ColorBgra Navy + { + get + { + return ColorBgra.FromBgra(128, 0, 0, 255); + } + } + + public static ColorBgra OldLace + { + get + { + return ColorBgra.FromBgra(230, 245, 253, 255); + } + } + + public static ColorBgra Olive + { + get + { + return ColorBgra.FromBgra(0, 128, 128, 255); + } + } + + public static ColorBgra OliveDrab + { + get + { + return ColorBgra.FromBgra(35, 142, 107, 255); + } + } + + public static ColorBgra Orange + { + get + { + return ColorBgra.FromBgra(0, 165, 255, 255); + } + } + + public static ColorBgra OrangeRed + { + get + { + return ColorBgra.FromBgra(0, 69, 255, 255); + } + } + + public static ColorBgra Orchid + { + get + { + return ColorBgra.FromBgra(214, 112, 218, 255); + } + } + + public static ColorBgra PaleGoldenrod + { + get + { + return ColorBgra.FromBgra(170, 232, 238, 255); + } + } + + public static ColorBgra PaleGreen + { + get + { + return ColorBgra.FromBgra(152, 251, 152, 255); + } + } + + public static ColorBgra PaleTurquoise + { + get + { + return ColorBgra.FromBgra(238, 238, 175, 255); + } + } + + public static ColorBgra PaleVioletRed + { + get + { + return ColorBgra.FromBgra(147, 112, 219, 255); + } + } + + public static ColorBgra PapayaWhip + { + get + { + return ColorBgra.FromBgra(213, 239, 255, 255); + } + } + + public static ColorBgra PeachPuff + { + get + { + return ColorBgra.FromBgra(185, 218, 255, 255); + } + } + + public static ColorBgra Peru + { + get + { + return ColorBgra.FromBgra(63, 133, 205, 255); + } + } + + public static ColorBgra Pink + { + get + { + return ColorBgra.FromBgra(203, 192, 255, 255); + } + } + + public static ColorBgra Plum + { + get + { + return ColorBgra.FromBgra(221, 160, 221, 255); + } + } + + public static ColorBgra PowderBlue + { + get + { + return ColorBgra.FromBgra(230, 224, 176, 255); + } + } + + public static ColorBgra Purple + { + get + { + return ColorBgra.FromBgra(128, 0, 128, 255); + } + } + + public static ColorBgra Red + { + get + { + return ColorBgra.FromBgra(0, 0, 255, 255); + } + } + + public static ColorBgra RosyBrown + { + get + { + return ColorBgra.FromBgra(143, 143, 188, 255); + } + } + + public static ColorBgra RoyalBlue + { + get + { + return ColorBgra.FromBgra(225, 105, 65, 255); + } + } + + public static ColorBgra SaddleBrown + { + get + { + return ColorBgra.FromBgra(19, 69, 139, 255); + } + } + + public static ColorBgra Salmon + { + get + { + return ColorBgra.FromBgra(114, 128, 250, 255); + } + } + + public static ColorBgra SandyBrown + { + get + { + return ColorBgra.FromBgra(96, 164, 244, 255); + } + } + + public static ColorBgra SeaGreen + { + get + { + return ColorBgra.FromBgra(87, 139, 46, 255); + } + } + + public static ColorBgra SeaShell + { + get + { + return ColorBgra.FromBgra(238, 245, 255, 255); + } + } + + public static ColorBgra Sienna + { + get + { + return ColorBgra.FromBgra(45, 82, 160, 255); + } + } + + public static ColorBgra Silver + { + get + { + return ColorBgra.FromBgra(192, 192, 192, 255); + } + } + + public static ColorBgra SkyBlue + { + get + { + return ColorBgra.FromBgra(235, 206, 135, 255); + } + } + + public static ColorBgra SlateBlue + { + get + { + return ColorBgra.FromBgra(205, 90, 106, 255); + } + } + + public static ColorBgra SlateGray + { + get + { + return ColorBgra.FromBgra(144, 128, 112, 255); + } + } + + public static ColorBgra Snow + { + get + { + return ColorBgra.FromBgra(250, 250, 255, 255); + } + } + + public static ColorBgra SpringGreen + { + get + { + return ColorBgra.FromBgra(127, 255, 0, 255); + } + } + + public static ColorBgra SteelBlue + { + get + { + return ColorBgra.FromBgra(180, 130, 70, 255); + } + } + + public static ColorBgra Tan + { + get + { + return ColorBgra.FromBgra(140, 180, 210, 255); + } + } + + public static ColorBgra Teal + { + get + { + return ColorBgra.FromBgra(128, 128, 0, 255); + } + } + + public static ColorBgra Thistle + { + get + { + return ColorBgra.FromBgra(216, 191, 216, 255); + } + } + + public static ColorBgra Tomato + { + get + { + return ColorBgra.FromBgra(71, 99, 255, 255); + } + } + + public static ColorBgra Turquoise + { + get + { + return ColorBgra.FromBgra(208, 224, 64, 255); + } + } + + public static ColorBgra Violet + { + get + { + return ColorBgra.FromBgra(238, 130, 238, 255); + } + } + + public static ColorBgra Wheat + { + get + { + return ColorBgra.FromBgra(179, 222, 245, 255); + } + } + + public static ColorBgra White + { + get + { + return ColorBgra.FromBgra(255, 255, 255, 255); + } + } + + public static ColorBgra WhiteSmoke + { + get + { + return ColorBgra.FromBgra(245, 245, 245, 255); + } + } + + public static ColorBgra Yellow + { + get + { + return ColorBgra.FromBgra(0, 255, 255, 255); + } + } + + public static ColorBgra YellowGreen + { + get + { + return ColorBgra.FromBgra(50, 205, 154, 255); + } + } + + public static ColorBgra Zero + { + get + { + return (ColorBgra)0; + } + } + + private static Dictionary predefinedColors; + + /// + /// Gets a hashtable that contains a list of all the predefined colors. + /// These are the same color values that are defined as public static properties + /// in System.Drawing.Color. The hashtable uses strings for the keys, and + /// ColorBgras for the values. + /// + public static Dictionary PredefinedColors + { + get + { + if (predefinedColors != null) + { + Type colorBgraType = typeof(ColorBgra); + PropertyInfo[] propInfos = colorBgraType.GetProperties(BindingFlags.Static | BindingFlags.Public); + Hashtable colors = new Hashtable(); + + foreach (PropertyInfo pi in propInfos) + { + if (pi.PropertyType == colorBgraType) + { + colors.Add(pi.Name, (ColorBgra)pi.GetValue(null, null)); + } + } + } + + return new Dictionary(predefinedColors); + } + } + } +} diff --git a/src/Core/ColorGradientControl.cs b/src/Core/ColorGradientControl.cs new file mode 100644 index 0000000..2e7abcd --- /dev/null +++ b/src/Core/ColorGradientControl.cs @@ -0,0 +1,733 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class ColorGradientControl + : UserControl + { + private Point lastTrackingMouseXY = new Point(-1, -1); + + private int tracking = -1; + private int highlight = -1; + + private const int triangleSize = 7; + private const int triangleHalfLength = (triangleSize - 1) / 2; + + private Orientation orientation = Orientation.Vertical; + + private Color[] customGradient = null; + + private bool drawNearNub = true; + public bool DrawNearNub + { + get + { + return this.drawNearNub; + } + + set + { + this.drawNearNub = value; + Invalidate(); + } + } + + private bool drawFarNub = true; + public bool DrawFarNub + { + get + { + return this.drawFarNub; + } + + set + { + this.drawFarNub = value; + Invalidate(); + } + } + + private int[] vals; + + // value from [0,255] that specifies the hsv "value" component + // where we should draw little triangles that show the value + public int Value + { + get + { + return GetValue(0); + } + + set + { + SetValue(0, value); + } + } + + public Color[] CustomGradient + { + get + { + if (this.customGradient == null) + { + return null; + } + else + { + return (Color[])this.customGradient.Clone(); + } + } + + set + { + if (value != this.customGradient) + { + if (value == null) + { + this.customGradient = null; + } + else + { + this.customGradient = (Color[])value.Clone(); + } + + Invalidate(); + } + } + } + + public Orientation Orientation + { + get + { + return this.orientation; + } + + set + { + if (value != this.orientation) + { + this.orientation = value; + Invalidate(); + } + } + } + + public int Count + { + get + { + return vals.Length; + } + + set + { + if (value < 0 || value > 16) + { + throw new ArgumentOutOfRangeException("value", value, "Count must be between 0 and 16"); + } + + vals = new int[value]; + + if (value > 1) + { + for (int i = 0; i < value; i++) + { + vals[i] = i * 255 / (value - 1); + } + } + else if (value == 1) + { + vals[0] = 128; + } + + OnValueChanged(0); + Invalidate(); + } + } + + public int GetValue(int index) + { + if (index < 0 || index >= vals.Length) + { + throw new ArgumentOutOfRangeException("index", index, "Index must be within the bounds of the array"); + } + + int val = vals[index]; + return val; + } + + public void SetValue(int index, int val) + { + int min = -1; + int max = 256; + + if (index < 0 || index >= vals.Length) + { + throw new ArgumentOutOfRangeException("index", index, "Index must be within the bounds of the array"); + } + + if (index - 1 >= 0) + { + min = vals[index - 1]; + } + + if (index + 1 < vals.Length) + { + max = vals[index + 1]; + } + + if (vals[index] != val) + { + int newVal = Utility.Clamp(val, min + 1, max - 1); + vals[index] = newVal; + OnValueChanged(index); + Invalidate(); + } + + Update(); + } + + public event IndexEventHandler ValueChanged; + private void OnValueChanged(int index) + { + if (ValueChanged != null) + { + ValueChanged(this, new IndexEventArgs(index)); + } + } + + [Obsolete("Use MinColor property instead", true)] + public Color BottomColor + { + get + { + return MinColor; + } + + set + { + MinColor = value; + } + } + + [Obsolete("Use MaxColor property instead", true)] + public Color TopColor + { + get + { + return MaxColor; + } + + set + { + MaxColor = value; + } + } + + private Color maxColor; + public Color MaxColor + { + get + { + return maxColor; + } + + set + { + if (maxColor != value) + { + maxColor = value; + Invalidate(); + } + } + } + + + private Color minColor; + public Color MinColor + { + get + { + return minColor; + } + + set + { + if (minColor != value) + { + minColor = value; + Invalidate(); + } + } + } + + public ColorGradientControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + this.DoubleBuffered = true; + this.ResizeRedraw = true; + this.Count = 1; + } + + private void DrawGradient(Graphics g) + { + g.PixelOffsetMode = PixelOffsetMode.Half; + Rectangle gradientRect; + + float gradientAngle; + + switch (this.orientation) + { + case Orientation.Horizontal: + gradientAngle = 180.0f; + break; + + case Orientation.Vertical: + gradientAngle = 90.0f; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + // draw gradient + gradientRect = ClientRectangle; + + switch (this.orientation) + { + case Orientation.Horizontal: + gradientRect.Inflate(-triangleHalfLength, -triangleSize + 3); + break; + + case Orientation.Vertical: + gradientRect.Inflate(-triangleSize + 3, -triangleHalfLength); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + if (this.customGradient != null && gradientRect.Width > 1 && gradientRect.Height > 1) + { + Surface gradientSurface = new Surface(gradientRect.Size); + + using (RenderArgs ra = new RenderArgs(gradientSurface)) + { + Utility.DrawColorRectangle(ra.Graphics, ra.Bounds, Color.Transparent, false); + + if (Orientation == Orientation.Horizontal) + { + for (int x = 0; x < gradientSurface.Width; ++x) + { + // TODO: refactor, double buffer, save this computation in a bitmap somewhere + double index = (double)(x * (this.customGradient.Length - 1)) / (double)(gradientSurface.Width - 1); + int indexL = (int)Math.Floor(index); + double t = 1.0 - (index - indexL); + int indexR = (int)Math.Min(this.customGradient.Length - 1, Math.Ceiling(index)); + Color colorL = this.customGradient[indexL]; + Color colorR = this.customGradient[indexR]; + + double a1 = colorL.A / 255.0; + double r1 = colorL.R / 255.0; + double g1 = colorL.G / 255.0; + double b1 = colorL.B / 255.0; + + double a2 = colorR.A / 255.0; + double r2 = colorR.R / 255.0; + double g2 = colorR.G / 255.0; + double b2 = colorR.B / 255.0; + + double at = (t * a1) + ((1.0 - t) * a2); + + double rt; + double gt; + double bt; + if (at == 0) + { + rt = 0; + gt = 0; + bt = 0; + } + else + { + rt = ((t * a1 * r1) + ((1.0 - t) * a2 * r2)) / at; + gt = ((t * a1 * g1) + ((1.0 - t) * a2 * g2)) / at; + bt = ((t * a1 * b1) + ((1.0 - t) * a2 * b2)) / at; + } + + int ap = Utility.Clamp((int)Math.Round(at * 255.0), 0, 255); + int rp = Utility.Clamp((int)Math.Round(rt * 255.0), 0, 255); + int gp = Utility.Clamp((int)Math.Round(gt * 255.0), 0, 255); + int bp = Utility.Clamp((int)Math.Round(bt * 255.0), 0, 255); + + for (int y = 0; y < gradientSurface.Height; ++y) + { + ColorBgra src = gradientSurface[x, y]; + + // we are assuming that src.A = 255 + + int rd = ((rp * ap) + (src.R * (255 - ap))) / 255; + int gd = ((gp * ap) + (src.G * (255 - ap))) / 255; + int bd = ((bp * ap) + (src.B * (255 - ap))) / 255; + + // TODO: proper alpha blending! + gradientSurface[x, y] = ColorBgra.FromBgra((byte)bd, (byte)gd, (byte)rd, 255); + } + } + + g.DrawImage(ra.Bitmap, gradientRect, ra.Bounds, GraphicsUnit.Pixel); + } + else if (Orientation == Orientation.Vertical) + { + // TODO + } + else + { + throw new InvalidEnumArgumentException(); + } + } + + gradientSurface.Dispose(); + } + else + { + using (LinearGradientBrush lgb = new LinearGradientBrush(this.ClientRectangle, + maxColor, minColor, gradientAngle, false)) + { + g.FillRectangle(lgb, gradientRect); + } + } + + // fill background + using (PdnRegion nonGradientRegion = new PdnRegion()) + { + nonGradientRegion.MakeInfinite(); + nonGradientRegion.Exclude(gradientRect); + + using (SolidBrush sb = new SolidBrush(this.BackColor)) + { + g.FillRegion(sb, nonGradientRegion.GetRegionReadOnly()); + } + } + + // draw value triangles + for (int i = 0; i < this.vals.Length; i++) + { + int pos = ValueToPosition(vals[i]); + Brush brush; + Pen pen; + + if (i == highlight) + { + brush = Brushes.Blue; + pen = (Pen)Pens.White.Clone(); + } + else + { + brush = Brushes.Black; + pen = (Pen)Pens.Gray.Clone(); + } + + g.SmoothingMode = SmoothingMode.AntiAlias; + + Point a1; + Point b1; + Point c1; + + Point a2; + Point b2; + Point c2; + + switch (this.orientation) + { + case Orientation.Horizontal: + a1 = new Point(pos - triangleHalfLength, 0); + b1 = new Point(pos, triangleSize - 1); + c1 = new Point(pos + triangleHalfLength, 0); + + a2 = new Point(a1.X, Height - 1 - a1.Y); + b2 = new Point(b1.X, Height - 1 - b1.Y); + c2 = new Point(c1.X, Height - 1 - c1.Y); + break; + + case Orientation.Vertical: + a1 = new Point(0, pos - triangleHalfLength); + b1 = new Point(triangleSize - 1, pos); + c1 = new Point(0, pos + triangleHalfLength); + + a2 = new Point(Width - 1 - a1.X, a1.Y); + b2 = new Point(Width - 1 - b1.X, b1.Y); + c2 = new Point(Width - 1 - c1.X, c1.Y); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + if (this.drawNearNub) + { + g.FillPolygon(brush, new Point[] { a1, b1, c1, a1 }); + } + + if (this.drawFarNub) + { + g.FillPolygon(brush, new Point[] { a2, b2, c2, a2 }); + } + + if (pen != null) + { + if (this.drawNearNub) + { + g.DrawPolygon(pen, new Point[] { a1, b1, c1, a1 }); + } + + if (this.drawFarNub) + { + g.DrawPolygon(pen, new Point[] { a2, b2, c2, a2 }); + } + + pen.Dispose(); + } + } + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint (e); + DrawGradient(e.Graphics); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + DrawGradient(pevent.Graphics); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + + base.Dispose(disposing); + } + + private int PositionToValue(int pos) + { + int max; + + switch (this.orientation) + { + case Orientation.Horizontal: + max = Width; + break; + + case Orientation.Vertical: + max = Height; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + int val = (((max - triangleSize) - (pos - triangleHalfLength)) * 255) / (max - triangleSize); + + if (this.orientation == Orientation.Horizontal) + { + val = 255 - val; + } + + return val; + } + + private int ValueToPosition(int val) + { + int max; + + if (this.orientation == Orientation.Horizontal) + { + val = 255 - val; + } + + switch (this.orientation) + { + case Orientation.Horizontal: + max = Width; + break; + + case Orientation.Vertical: + max = Height; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + int pos = triangleHalfLength + ((max - triangleSize) - (((val * (max - triangleSize)) / 255))); + return pos; + } + + private int WhichTriangle(int val) + { + int bestIndex = -1; + int bestDistance = int.MaxValue; + int v = PositionToValue(val); + + for (int i = 0; i < this.vals.Length; i++) + { + int distance = Math.Abs(this.vals[i] - v); + + if (distance < bestDistance) + { + bestDistance = distance; + bestIndex = i; + } + } + + return bestIndex; + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (e.Button == MouseButtons.Left) + { + int val = GetOrientedValue(e); + tracking = WhichTriangle(val); + Invalidate(); + OnMouseMove(e); + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (e.Button == MouseButtons.Left) + { + OnMouseMove(e); + tracking = -1; + Invalidate(); + } + } + + private int GetOrientedValue(MouseEventArgs me) + { + return GetOrientedValue(new Point(me.X, me.Y)); + } + + private int GetOrientedValue(Point pt) + { + int pos; + + switch (this.orientation) + { + case Orientation.Horizontal: + pos = pt.X; + break; + + case Orientation.Vertical: + pos = pt.Y; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return pos; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + int pos = GetOrientedValue(e); + + Point newMouseXY = new Point(e.X, e.Y); + + if (tracking >= 0 && newMouseXY != this.lastTrackingMouseXY) + { + int val = PositionToValue(pos); + this.SetValue(tracking, val); + this.lastTrackingMouseXY = newMouseXY; + } + else + { + int oldHighlight = highlight; + highlight = WhichTriangle(pos); + + if (highlight != oldHighlight) + { + this.InvalidateTriangle(oldHighlight); + this.InvalidateTriangle(highlight); + } + } + + } + + protected override void OnMouseLeave(EventArgs e) + { + int oldhighlight = highlight; + highlight = -1; + this.InvalidateTriangle(oldhighlight); + } + + private void InvalidateTriangle(int index) + { + if (index < 0 || index >= this.vals.Length) + { + return; + } + + int value = ValueToPosition(this.vals[index]); + Rectangle rect; + + switch (this.orientation) + { + case Orientation.Horizontal: + rect = new Rectangle(value - triangleHalfLength, 0, triangleSize, this.Height); + break; + + case Orientation.Vertical: + rect = new Rectangle(0, value - triangleHalfLength, this.Width, triangleSize); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + this.Invalidate(rect, true); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + } + #endregion + } +} diff --git a/src/Core/ColorGradientControl.resx b/src/Core/ColorGradientControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/ColorTransferMode.cs b/src/Core/ColorTransferMode.cs new file mode 100644 index 0000000..c6e0b0c --- /dev/null +++ b/src/Core/ColorTransferMode.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum ColorTransferMode + { + Rgb, + Luminosity + } +} diff --git a/src/Core/CommandButton.cs b/src/Core/CommandButton.cs new file mode 100644 index 0000000..7bf8b1f --- /dev/null +++ b/src/Core/CommandButton.cs @@ -0,0 +1,300 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Drawing.Text; +using System.Text; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + public sealed class CommandButton + : ButtonBase + { + private Font actionTextFont; + private string actionText; + private Font explanationTextFont; + private string explanationText; + private Image actionImage; + private Image actionImageDisabled; + + [Browsable(true)] + [EditorBrowsable(EditorBrowsableState.Always)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public override bool AutoSize + { + get + { + return base.AutoSize; + } + + set + { + base.AutoSize = value; + PerformLayout(); + Invalidate(true); + } + } + + public string ActionText + { + get + { + return this.actionText; + } + + set + { + if (this.actionText != value) + { + this.actionText = value; + this.Text = value; // ensure that mnemonics get processed correctly + PerformLayout(); + Invalidate(true); + } + } + } + + public string ExplanationText + { + get + { + return this.explanationText; + } + + set + { + if (this.explanationText != value) + { + this.explanationText = value; + PerformLayout(); + Invalidate(true); + } + } + } + + public Image ActionImage + { + get + { + return this.actionImage; + } + + set + { + if (this.actionImage != null) + { + this.actionImageDisabled.Dispose(); + this.actionImageDisabled = null; + this.actionImage.Dispose(); + this.actionImage = null; + } + + if (value != null) + { + this.actionImage = value; + this.actionImageDisabled = ToolStripRenderer.CreateDisabledImage(this.actionImage); + } + + PerformLayout(); + Invalidate(true); + } + } + + public CommandButton() + { + InitializeComponent(); + this.actionTextFont = new Font(this.Font.FontFamily, this.Font.Size * 1.25f, this.Font.Style); + this.explanationTextFont = this.Font; + } + + protected override void OnPaintButton(Graphics g, PushButtonState state, bool drawFocusCues, bool drawKeyboardCues) + { + MeasureAndDraw(g, true, state, drawFocusCues, drawKeyboardCues); + } + + private Size MeasureAndDraw(Graphics g, bool enableDrawing, PushButtonState state, bool drawFocusCues, bool drawKeyboardCues) + { + if (enableDrawing) + { + g.PixelOffsetMode = PixelOffsetMode.Half; + g.CompositingMode = CompositingMode.SourceOver; + g.InterpolationMode = InterpolationMode.Bilinear; + } + + int marginX = UI.ScaleWidth(9); + int marginYTop = UI.ScaleHeight(8); + int marginYBottom = UI.ScaleHeight(9); + int paddingX = UI.ScaleWidth(8); + int paddingY = UI.ScaleHeight(3); + int offsetX = 0; + int offsetY = 0; + + bool drawAsDefault = (state == PushButtonState.Default); + + if (enableDrawing) + { + using (Brush backBrush = new SolidBrush(this.BackColor)) + { + CompositingMode oldCM = g.CompositingMode; + g.CompositingMode = CompositingMode.SourceCopy; + g.FillRectangle(backBrush, ClientRectangle); + g.CompositingMode = oldCM; + } + + Rectangle ourRect = new Rectangle(0, 0, ClientSize.Width, ClientSize.Height); + + if (state == PushButtonState.Pressed) + { + offsetX = 1; + offsetY = 1; + } + + UI.DrawCommandButton(g, state, ourRect, BackColor, this); + } + + Rectangle actionImageRect; + + Brush textBrush = new SolidBrush(SystemColors.WindowText); + + if (this.actionImage == null) + { + actionImageRect = new Rectangle(offsetX, offsetY + marginYTop, 0, 0); + } + else + { + actionImageRect = new Rectangle(offsetX + marginX, offsetY + marginYTop, + UI.ScaleWidth(this.actionImage.Width), UI.ScaleHeight(this.actionImage.Height)); + + Rectangle srcRect = new Rectangle(0, 0, this.actionImage.Width, this.actionImage.Height); + + if (enableDrawing) + { + Image drawMe = Enabled ? this.actionImage : this.actionImageDisabled; + + if (Enabled) + { + actionImageRect.Y += 3; + actionImageRect.X += 1; + g.DrawImage(this.actionImageDisabled, actionImageRect, srcRect, GraphicsUnit.Pixel); + actionImageRect.X -= 1; + actionImageRect.Y -= 3; + } + + actionImageRect.Y += 2; + g.DrawImage(drawMe, actionImageRect, srcRect, GraphicsUnit.Pixel); + actionImageRect.Y -= 2; + } + } + + int actionTextX = actionImageRect.Right + paddingX; + int actionTextY = actionImageRect.Top; + int actionTextWidth = ClientSize.Width - actionTextX - marginX + offsetX; + + StringFormat stringFormat = (StringFormat)StringFormat.GenericTypographic.Clone(); + stringFormat.HotkeyPrefix = drawKeyboardCues ? HotkeyPrefix.Show : HotkeyPrefix.Hide; + + SizeF actionTextSize = g.MeasureString(this.actionText, this.actionTextFont, actionTextWidth, stringFormat); + + Rectangle actionTextRect = new Rectangle(actionTextX, actionTextY, + actionTextWidth, (int)Math.Ceiling(actionTextSize.Height)); + + if (enableDrawing) + { + if (state == PushButtonState.Disabled) + { + ControlPaint.DrawStringDisabled(g, this.actionText, this.actionTextFont, this.BackColor, actionTextRect, stringFormat); + } + else + { + g.DrawString(this.actionText, this.actionTextFont, textBrush, actionTextRect, stringFormat); + } + } + + int descriptionTextX = actionTextX; + int descriptionTextY = actionTextRect.Bottom + paddingY; + int descriptionTextWidth = actionTextWidth; + + SizeF descriptionTextSize = g.MeasureString(this.explanationText, this.explanationTextFont, + descriptionTextWidth, stringFormat); + + Rectangle descriptionTextRect = new Rectangle(descriptionTextX, descriptionTextY, + descriptionTextWidth, (int)Math.Ceiling(descriptionTextSize.Height)); + + if (enableDrawing) + { + if (state == PushButtonState.Disabled) + { + ControlPaint.DrawStringDisabled(g, this.explanationText, this.explanationTextFont, this.BackColor, descriptionTextRect, stringFormat); + } + else + { + g.DrawString(this.explanationText, this.explanationTextFont, textBrush, descriptionTextRect, stringFormat); + } + } + + if (enableDrawing) + { + if (drawFocusCues) + { + ControlPaint.DrawFocusRectangle(g, new Rectangle(3, 3, ClientSize.Width - 5, ClientSize.Height - 5)); + } + } + + if (textBrush != null) + { + textBrush.Dispose(); + textBrush = null; + } + + stringFormat.Dispose(); + stringFormat = null; + + Size layoutSize = new Size(ClientSize.Width, descriptionTextRect.Bottom + marginYBottom); + return layoutSize; + } + + protected override void OnLayout(LayoutEventArgs levent) + { + if (AutoSize) + { + Size layoutSize; + + using (Graphics g = CreateGraphics()) + { + layoutSize = MeasureAndDraw(g, false, PushButtonState.Normal, false, false); + } + + this.ClientSize = layoutSize; + } + + base.OnLayout(levent); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.AccessibleRole = AccessibleRole.PushButton; + this.TabStop = true; + this.DoubleBuffered = true; + this.Name = "CommandButton"; + PerformLayout(); + } + } +} diff --git a/src/Core/CommandButton.resx b/src/Core/CommandButton.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/ControlShadow.cs b/src/Core/ControlShadow.cs new file mode 100644 index 0000000..45dea9a --- /dev/null +++ b/src/Core/ControlShadow.cs @@ -0,0 +1,283 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class ControlShadow + : Control + { + private Image roundedEdgeUL = null; + private Image roundedEdgeUR = null; + private Image roundedEdgeLL = null; + private Image roundedEdgeLR = null; + + private System.ComponentModel.Container components = null; + + private Control occludingControl; + + private static bool betaTagDone = false; + private string betaTagString = null; + private int betaTagOpacity = 255; + private Timer betaTagTimer; + private DateTime betaTagStart; + + [Browsable(false)] + public Control OccludingControl + { + get + { + return this.occludingControl; + } + + set + { + this.occludingControl = value; + Invalidate(); + } + } + + public ControlShadow() + { + this.SetStyle(ControlStyles.Opaque, true); + this.SetStyle(ControlStyles.UserPaint, true); + this.SetStyle(ControlStyles.AllPaintingInWmPaint, true); + + this.Dock = DockStyle.Fill; + this.DoubleBuffered = true; + this.ResizeRedraw = true; + + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + BackColor = Color.FromArgb(0xc0, 0xc0, 0xc0); + + this.roundedEdgeUL = PdnResources.GetImageResource("Images.RoundedEdgeUL.png").Reference; + this.roundedEdgeUR = PdnResources.GetImageResource("Images.RoundedEdgeUR.png").Reference; + this.roundedEdgeLL = PdnResources.GetImageResource("Images.RoundedEdgeLL.png").Reference; + this.roundedEdgeLR = PdnResources.GetImageResource("Images.RoundedEdgeLR.png").Reference; + + if (!PdnInfo.IsFinalBuild && !betaTagDone) + { + betaTagDone = true; + + string betaTagStringFormat = PdnResources.GetString("ControlShadow.BetaTag.Text.Format"); + string appName = PdnInfo.GetFullAppName(); + string expiredDateString = PdnInfo.ExpirationDate.ToShortDateString(); + this.betaTagString = string.Format(betaTagStringFormat, appName, expiredDateString); + + this.betaTagStart = DateTime.Now; + this.betaTagTimer = new Timer(); + this.betaTagTimer.Interval = 100; + this.betaTagTimer.Tick += new EventHandler(BetaTagTimer_Tick); + this.betaTagTimer.Enabled = true; + } + } + + private void BetaTagTimer_Tick(object sender, EventArgs e) + { + int newOpacity; + TimeSpan uptime = DateTime.Now - this.betaTagStart; + + if (uptime.TotalMilliseconds < 10000) + { + newOpacity = 255; + } + else + { + newOpacity = (int)(255 - (((uptime.TotalMilliseconds - 10000) * 128) / 1000)); + newOpacity = Math.Max(0, newOpacity); + } + + if (this.betaTagOpacity != newOpacity) + { + this.betaTagOpacity = newOpacity; + Invalidate(); + } + + if (this.betaTagOpacity == 0) + { + this.betaTagTimer.Enabled = false; + this.betaTagTimer.Tick -= BetaTagTimer_Tick; + this.betaTagTimer.Dispose(); + this.betaTagTimer = null; + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + } + } + + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + #endregion + + protected override void OnPaint(PaintEventArgs pe) + { + DrawShadow(pe.Graphics, pe.ClipRectangle); + base.OnPaint(pe); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + base.OnPaintBackground(pevent); + } + + private void DrawShadow(Graphics g, Rectangle clipRect) + { + if (this.occludingControl != null) + { + // Draw the outline rectangle + Rectangle outlineRect = new Rectangle(new Point(0, 0), this.occludingControl.Size); + + outlineRect = occludingControl.RectangleToScreen(outlineRect); + outlineRect = RectangleToClient(outlineRect); + outlineRect.X -= 1; + outlineRect.Y -= 1; + outlineRect.Width += 2; + outlineRect.Height += 2; + + g.DrawLines( + Pens.Black, + new Point[] + { + new Point(outlineRect.Left, outlineRect.Top), + new Point(outlineRect.Right, outlineRect.Top), + new Point(outlineRect.Right, outlineRect.Bottom), + new Point(outlineRect.Left, outlineRect.Bottom), + new Point(outlineRect.Left, outlineRect.Top) + }); + + using (PdnRegion backRegion = new PdnRegion(clipRect)) + { + Rectangle occludingRect = new Rectangle(0, 0, this.occludingControl.Width, this.occludingControl.Height); + occludingRect = this.occludingControl.RectangleToScreen(occludingRect); + occludingRect = RectangleToClient(occludingRect); + backRegion.Exclude(occludingRect); + backRegion.Exclude(outlineRect); + + using (Brush backBrush = new SolidBrush(this.BackColor)) + { + g.FillRegion(backBrush, backRegion.GetRegionReadOnly()); + } + } + + Rectangle edgeRect = new Rectangle(0, 0, this.roundedEdgeUL.Width, this.roundedEdgeUR.Height); + + Rectangle ulEdgeRect = new Rectangle(outlineRect.Left - 3, outlineRect.Top - 3, this.roundedEdgeUL.Width, this.roundedEdgeUL.Height); + Rectangle urEdgeRect = new Rectangle(outlineRect.Right - 3, outlineRect.Top - 3, this.roundedEdgeUR.Width, this.roundedEdgeUR.Height); + Rectangle llEdgeRect = new Rectangle(outlineRect.Left - 3, outlineRect.Bottom - 3, this.roundedEdgeLL.Width, this.roundedEdgeLL.Height); + Rectangle lrEdgeRect = new Rectangle(outlineRect.Right - 3, outlineRect.Bottom - 3, this.roundedEdgeLR.Width, this.roundedEdgeLR.Height); + + g.DrawImage(this.roundedEdgeUL, ulEdgeRect, edgeRect, GraphicsUnit.Pixel); + g.DrawImage(this.roundedEdgeUR, urEdgeRect, edgeRect, GraphicsUnit.Pixel); + g.DrawImage(this.roundedEdgeLL, llEdgeRect, edgeRect, GraphicsUnit.Pixel); + g.DrawImage(this.roundedEdgeLR, lrEdgeRect, edgeRect, GraphicsUnit.Pixel); + + Color c1 = Color.FromArgb(95, Color.Black); + Color c2 = Color.FromArgb(47, Color.Black); + Color c3 = Color.FromArgb(15, Color.Black); + + Pen p1 = new Pen(c1); + Pen p2 = new Pen(c2); + Pen p3 = new Pen(c3); + + // Draw top soft edge + g.DrawLine(p1, ulEdgeRect.Right, outlineRect.Top - 1, urEdgeRect.Left - 1, outlineRect.Top - 1); + g.DrawLine(p2, ulEdgeRect.Right, outlineRect.Top - 2, urEdgeRect.Left - 1, outlineRect.Top - 2); + g.DrawLine(p3, ulEdgeRect.Right, outlineRect.Top - 3, urEdgeRect.Left - 1, outlineRect.Top - 3); + + // Draw bottom soft edge + g.DrawLine(p1, llEdgeRect.Right, outlineRect.Bottom + 0, lrEdgeRect.Left - 1, outlineRect.Bottom + 0); + g.DrawLine(p2, llEdgeRect.Right, outlineRect.Bottom + 1, lrEdgeRect.Left - 1, outlineRect.Bottom + 1); + g.DrawLine(p3, llEdgeRect.Right, outlineRect.Bottom + 2, lrEdgeRect.Left - 1, outlineRect.Bottom + 2); + + // Draw left soft edge + g.DrawLine(p1, outlineRect.Left - 1, ulEdgeRect.Bottom, outlineRect.Left - 1, llEdgeRect.Top - 1); + g.DrawLine(p2, outlineRect.Left - 2, ulEdgeRect.Bottom, outlineRect.Left - 2, llEdgeRect.Top - 1); + g.DrawLine(p3, outlineRect.Left - 3, ulEdgeRect.Bottom, outlineRect.Left - 3, llEdgeRect.Top - 1); + + // Draw right soft edge + g.DrawLine(p1, outlineRect.Right + 0, urEdgeRect.Bottom, outlineRect.Right + 0, lrEdgeRect.Top - 1); + g.DrawLine(p2, outlineRect.Right + 1, urEdgeRect.Bottom, outlineRect.Right + 1, lrEdgeRect.Top - 1); + g.DrawLine(p3, outlineRect.Right + 2, urEdgeRect.Bottom, outlineRect.Right + 2, lrEdgeRect.Top - 1); + + p1.Dispose(); + p1 = null; + + p2.Dispose(); + p2 = null; + + p3.Dispose(); + p3 = null; + } + + if (this.betaTagString != null) + { + Color betaTagColor = Color.FromArgb(this.betaTagOpacity, SystemColors.WindowText); + Brush betaTagBrush = new SolidBrush(betaTagColor); + StringFormat sf = (StringFormat)StringFormat.GenericTypographic.Clone(); + + sf.Alignment = StringAlignment.Center; + sf.LineAlignment = StringAlignment.Near; + + g.DrawString( + this.betaTagString, + this.Font, + betaTagBrush, + ClientRectangle.Width / 2, + 1, + sf); + + sf.Dispose(); + sf = null; + + betaTagBrush.Dispose(); + betaTagBrush = null; + } + } + + protected override void WndProc(ref Message m) + { + // Ignore focus + if (m.Msg == 7 /* WM_SETFOCUS */) + { + return; + } + + base.WndProc (ref m); + } + } +} diff --git a/src/Core/ControlShadow.resx b/src/Core/ControlShadow.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj new file mode 100644 index 0000000..061a6e1 --- /dev/null +++ b/src/Core/Core.csproj @@ -0,0 +1,479 @@ + + + Local + 9.0.21022 + 2.0 + {1EADE568-A866-4DD4-9898-0A151E3F0E26} + Debug + AnyCPU + + + + + PaintDotNet.Core + + + JScript + Grid + IE50 + false + Library + PaintDotNet + OnBuildSuccess + + + + + + + 2.0 + + + bin\Debug\ + true + 270532608 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + bin\Release\ + true + 270532608 + false + + + TRACE + + + true + 512 + false + + + true + false + false + true + 4 + pdbonly + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + + System + + + + System.Drawing + + + System.Windows.Forms + + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + {0B173113-1F9B-4939-A62F-A176336F13AC} + Resources + + + SystemLayer + {80572820-93A5-4278-A513-D902BEA2639C} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + UserControl + + + Component + + + Code + + + + + + + Component + + + UserControl + + + UserControl + + + + UserControl + + + UserControl + + + + + + + + + Code + + + Code + + + Code + + + Code + + + Component + + + Code + + + UserControl + + + Component + + + Component + + + + Code + + + UserControl + + + UserControl + + + UserControl + + + + Code + + + + + + + Component + + + Code + + + UserControl + + + Code + + + Code + + + Code + + + Code + + + + + Component + + + Code + + + Code + + + + + + + + Component + + + Component + + + UserControl + + + + + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + UserControl + + + + + UserControl + + + + + + + + + Component + + + + + Code + + + + Component + + + + + + Code + + + Code + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Component + + + Form + + + Form + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + UserControl + + + Code + + + Code + + + Code + + + Code + + + Code + + + Component + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + UserControl + + + + Component + + + Code + + + UserControl + + + Code + + + Code + + + Code + + + + + Designer + CommandButton.cs + + + + + + + + + + @rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/Core/CursorChanger.cs b/src/Core/CursorChanger.cs new file mode 100644 index 0000000..572bbb3 --- /dev/null +++ b/src/Core/CursorChanger.cs @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// This class will set the cursor of a control to the requested one, + /// and then when this class is Disposed it will reset the cursor + /// to the original cursor. + /// + public sealed class CursorChanger + : IDisposable + { + private Control control; + private Cursor oldCursor; + + private Control FindTopParent(Control childControl) + { + Control parent = childControl.Parent; + + if (parent == null) + { + return childControl; + } + else + { + return FindTopParent(parent); + } + } + + public CursorChanger(Control control, Cursor newCursor) + { + this.control = control; + this.oldCursor = this.control.Cursor; + FindTopParent(control).Cursor = newCursor; + } + + ~CursorChanger() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool disposed = false; + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + FindTopParent(control).Cursor = oldCursor; + } + + disposed = true; + } + } + } +} diff --git a/src/Core/CurveControl.cs b/src/Core/CurveControl.cs new file mode 100644 index 0000000..654476a --- /dev/null +++ b/src/Core/CurveControl.cs @@ -0,0 +1,568 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// This class is for manipulation of transfer functions. + /// It is intended for curve adjustment + /// + public abstract class CurveControl + : UserControl + { + private System.ComponentModel.Container components = null; + private int[] curvesInvalidRange = new int[] { int.MaxValue, int.MinValue }; + private Point lastMouseXY = new Point(int.MinValue, int.MinValue); + private int lastKey = -1; + private int lastValue = -1; + private bool tracking = false; + private Point[] ptSave; + private int[] pointsNearMousePerChannel; + private bool[] effectChannel; + + public abstract ColorTransferMode ColorTransferMode + { + get; + } + + + protected SortedList[] controlPoints; + public SortedList[] ControlPoints + { + get + { + return this.controlPoints; + } + + set + { + if (value.Length != controlPoints.Length) + { + throw new ArgumentException("value must have a matching channel count", "value"); + } + + this.controlPoints = value; + Invalidate(); + } + } + + protected int channels; + public int Channels + { + get + { + return this.channels; + } + } + + protected int entries; + public int Entries + { + get + { + return entries; + } + } + + protected ColorBgra[] visualColors; + public ColorBgra GetVisualColor(int channel) + { + return visualColors[channel]; + } + + protected string[] channelNames; + public string GetChannelName(int channel) + { + return channelNames[channel]; + } + + protected bool[] mask; + public void SetSelected(int channel, bool val) + { + mask[channel] = val; + Invalidate(); + } + + public bool GetSelected(int channel) + { + return mask[channel]; + } + + protected internal CurveControl(int channels, int entries) + { + this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); + + this.channels = channels; + this.entries = entries; + + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + pointsNearMousePerChannel = new int[channels]; + for (int i = 0; i < channels; ++i) + { + pointsNearMousePerChannel[i] = -1; + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.TabStop = false; + } + + #endregion + + public event EventHandler ValueChanged; + protected virtual void OnValueChanged() + { + if (ValueChanged != null) + { + ValueChanged(this, EventArgs.Empty); + } + } + + public event EventHandler> CoordinatesChanged; + protected virtual void OnCoordinatesChanged() + { + if (CoordinatesChanged != null) + { + CoordinatesChanged(this, new EventArgs(new Point(lastKey, lastValue))); + } + } + + public void ResetControlPoints() + { + controlPoints = new SortedList[Channels]; + + for (int i = 0; i < Channels; ++i) + { + SortedList newList = new SortedList(); + + newList.Add(0, 0); + newList.Add(Entries - 1, Entries - 1); + controlPoints[i] = newList; + } + + Invalidate(); + OnValueChanged(); + } + + private void DrawToGraphics(Graphics g) + { + ColorBgra colorSolid = ColorBgra.FromColor(this.ForeColor); + ColorBgra colorGuide = ColorBgra.FromColor(this.ForeColor); + ColorBgra colorGrid = ColorBgra.FromColor(this.ForeColor); + + colorGrid.A = 128; + colorGuide.A = 96; + + Pen penSolid = new Pen(colorSolid.ToColor(), 1); + Pen penGrid = new Pen(colorGrid.ToColor(), 1); + Pen penGuide = new Pen(colorGuide.ToColor(), 1); + + penGrid.DashStyle = DashStyle.Dash; + + g.Clear(this.BackColor); + g.SmoothingMode = SmoothingMode.AntiAlias; + + Rectangle ourRect = ClientRectangle; + + ourRect.Inflate(-1, -1); + + if (lastMouseXY.Y >= 0) + { + g.DrawLine(penGuide, 0, lastMouseXY.Y, Width, lastMouseXY.Y); + } + + if (lastMouseXY.X >= 0) + { + g.DrawLine(penGuide, lastMouseXY.X, 0, lastMouseXY.X, Height); + } + + for (float f = 0.25f; f <= 0.75f; f += 0.25f) + { + float x = Utility.Lerp(ourRect.Left, ourRect.Right, f); + float y = Utility.Lerp(ourRect.Top, ourRect.Bottom, f); + + g.DrawLine(penGrid, + Point.Round(new PointF(x, ourRect.Top)), + Point.Round(new PointF(x, ourRect.Bottom))); + + g.DrawLine(penGrid, + Point.Round(new PointF(ourRect.Left, y)), + Point.Round(new PointF(ourRect.Right, y))); + } + + g.DrawLine(penGrid, ourRect.Left, ourRect.Bottom, ourRect.Right, ourRect.Top); + + float width = this.ClientRectangle.Width; + float height = this.ClientRectangle.Height; + + for (int c = 0; c < channels; ++c) + { + SortedList channelControlPoints = controlPoints[c]; + int points = channelControlPoints.Count; + + ColorBgra color = GetVisualColor(c); + ColorBgra colorSelected = ColorBgra.Blend(color, ColorBgra.White, 128); + + const float penWidthNonSelected = 1; + const float penWidthSelected = 2; + float penWidth = mask[c] ? penWidthSelected : penWidthNonSelected; + Pen penSelected = new Pen(color.ToColor(), penWidth); + + color.A = 128; + + Pen pen = new Pen(color.ToColor(), penWidth); + Brush brush = new SolidBrush(color.ToColor()); + SolidBrush brushSelected = new SolidBrush(Color.White); + + SplineInterpolator interpolator = new SplineInterpolator(); + IList xa = channelControlPoints.Keys; + IList ya = channelControlPoints.Values; + PointF[] line = new PointF[Entries]; + + for (int i = 0; i < points; ++i) + { + interpolator.Add(xa[i], ya[i]); + } + + for (int i = 0; i < line.Length; ++i) + { + line[i].X = (float)i * (width - 1) / (entries - 1); + line[i].Y = (float)(Utility.Clamp(entries - 1 - interpolator.Interpolate(i), 0, entries - 1)) * + (height - 1) / (entries - 1); + } + + pen.LineJoin = LineJoin.Round; + g.DrawLines(pen, line); + + for (int i = 0; i < points; ++i) + { + int k = channelControlPoints.Keys[i]; + float x = k * (width - 1) / (entries - 1); + float y = (entries - 1 - channelControlPoints.Values[i]) * (height - 1) / (entries - 1); + + const float radiusSelected = 4; + const float radiusNotSelected = 3; + const float radiusUnMasked = 2; + + bool selected = (mask[c] && pointsNearMousePerChannel[c] == i); + float size = selected ? radiusSelected : (mask[c] ? radiusNotSelected : radiusUnMasked); + RectangleF rect = Utility.RectangleFromCenter(new PointF(x, y), size); + + g.FillEllipse(selected ? brushSelected : brush, rect.X, rect.Y, rect.Width, rect.Height); + g.DrawEllipse(selected ? penSelected : pen, rect.X, rect.Y, rect.Width, rect.Height); + } + + pen.Dispose(); + } + + penSolid.Dispose(); + penGrid.Dispose(); + penGuide.Dispose(); + } + + protected override void OnPaint(PaintEventArgs e) + { + DrawToGraphics(e.Graphics); + base.OnPaint(e); + } + + /* This is not used now, but may be used later + /// + /// Reduces the number of control points by at least given factor. + /// + /// + public void Simplify(float factor) + { + for (int c = 0; c < channels; ++c) + { + SortedList channelControlPoints = controlPoints[c]; + int targetPoints = (int)Math.Ceiling(channelControlPoints.Count / factor); + + float minPointWorth = float.MaxValue; + + //remove points until the target point count is reached, but always remove unnecessary + while (channelControlPoints.Count > 2) + { + minPointWorth = float.MaxValue; + int minPointWorthIndex = -1; + + for (int i = 1; i < channelControlPoints.Count - 1; ++i) + { + Point left = new Point( + channelControlPoints.Keys[i - 1], + channelControlPoints.Values[i - 1]); + Point right = new Point( + channelControlPoints.Keys[i + 1], + channelControlPoints.Values[i + 1]); + Point actual = new Point( + channelControlPoints.Keys[i], + channelControlPoints.Values[i]); + + float targetY = left.Y + (actual.X - left.X) * (right.Y - left.Y) / (float)(right.X - left.X); + float error = targetY - actual.Y; + float pointWorth = error * error * (right.X - left.X); + + if (pointWorth < minPointWorth) + { + minPointWorth = pointWorth; + minPointWorthIndex = i; + } + } + + + if (channelControlPoints.Count > targetPoints || minPointWorth == 0) + { + //if we found a point and it's not the first point + if (minPointWorthIndex > 0) + { + channelControlPoints.RemoveAt(minPointWorthIndex); + } + } + else + { + break; + } + } + } + + Invalidate(); + OnValueChanged(); + } + */ + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + float width = this.ClientRectangle.Width; + float height = this.ClientRectangle.Height; + int mx = (int)Utility.Clamp(0.5f + e.X * (entries - 1) / (width - 1), 0, Entries - 1); + int my = (int)Utility.Clamp(0.5f + Entries - 1 - e.Y * (entries - 1) / (height - 1), 0, Entries - 1); + + ptSave = new Point[channels]; + for (int i = 0; i < channels; ++i) + { + ptSave[i].X = -1; + } + + if (0 != e.Button) + { + tracking = (e.Button == MouseButtons.Left); + lastKey = mx; + + bool anyNearMouse = false; + + effectChannel = new bool[channels]; + for (int c = 0; c < channels; ++c) + { + SortedList channelControlPoints = controlPoints[c]; + int index = pointsNearMousePerChannel[c]; + bool hasPoint = (index >= 0); + int key = hasPoint ? channelControlPoints.Keys[index] : index; + + anyNearMouse = (anyNearMouse || hasPoint); + + effectChannel[c] = hasPoint; + + if (mask[c] && hasPoint && + key > 0 && key < entries - 1) + { + channelControlPoints.RemoveAt(index); + OnValueChanged(); + } + } + + if (!anyNearMouse) + { + for (int c = 0; c < channels; ++c) + { + effectChannel[c] = true; + } + } + } + + OnMouseMove(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (0 != (e.Button & MouseButtons.Left) && tracking) + { + tracking = false; + lastKey = -1; + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + lastMouseXY = new Point(e.X, e.Y); + float width = this.ClientRectangle.Width; + float height = this.ClientRectangle.Height; + int mx = (int)Utility.Clamp(0.5f + e.X * (entries - 1) / (width - 1), 0, Entries - 1); + int my = (int)Utility.Clamp(0.5f + Entries - 1 - e.Y * (entries - 1) / (height - 1), 0, Entries - 1); + + Invalidate(); + + if (tracking && e.Button == MouseButtons.None) + { + tracking = false; + } + + if (tracking) + { + bool changed = false; + for (int c = 0; c < channels; ++c) + { + SortedList channelControlPoints = controlPoints[c]; + + pointsNearMousePerChannel[c] = -1; + if (mask[c] && effectChannel[c]) + { + int lastIndex = channelControlPoints.IndexOfKey(lastKey); + + if (ptSave[c].X >= 0 && ptSave[c].X != mx) + { + channelControlPoints[ptSave[c].X] = ptSave[c].Y; + ptSave[c].X = -1; + + changed = true; + } + else if (lastKey > 0 && lastKey < Entries - 1 && lastIndex >= 0 && mx != lastKey) + { + channelControlPoints.RemoveAt(lastIndex); + } + + if (mx >= 0 && mx < Entries) + { + int newValue = Utility.Clamp(my, 0, Entries - 1); + int oldIndex = channelControlPoints.IndexOfKey(mx); + int oldValue = (oldIndex >= 0) ? channelControlPoints.Values[oldIndex] : -1; + + if (oldIndex >= 0 && mx != lastKey) + { + // if we drag onto an existing point, delete it, but save it in case we drag away + ptSave[c].X = mx; + ptSave[c].Y = channelControlPoints.Values[oldIndex]; + } + + if (oldIndex < 0 || + channelControlPoints[mx] != newValue) + { + channelControlPoints[mx] = newValue; + changed = true; + } + + pointsNearMousePerChannel[c] = channelControlPoints.IndexOfKey(mx); + } + } + } + + if (changed) + { + Update(); + OnValueChanged(); + } + } + else + { + pointsNearMousePerChannel = new int[channels]; + + for (int c = 0; c < channels; ++c) + { + SortedList channelControlPoints = controlPoints[c]; + int minRadiusSq = 30; + int bestIndex = -1; + + if (mask[c]) + { + for (int i = 0; i < channelControlPoints.Count; ++i) + { + int sumsq = 0; + int diff = 0; + + diff = channelControlPoints.Keys[i] - mx; + sumsq += diff * diff; + + diff = channelControlPoints.Values[i] - my; + sumsq += diff * diff; + + if (sumsq < minRadiusSq) + { + minRadiusSq = sumsq; + bestIndex = i; + } + } + } + + pointsNearMousePerChannel[c] = bestIndex; + } + + Update(); + } + + lastKey = mx; + lastValue = my; + OnCoordinatesChanged(); + } + + protected override void OnMouseLeave(EventArgs e) + { + lastKey = -1; + lastValue = -1; + lastMouseXY = new Point(int.MinValue, int.MinValue); + Invalidate(); + OnCoordinatesChanged(); + base.OnMouseLeave(e); + } + + public virtual void InitFromPixelOp(UnaryPixelOp op) + { + OnValueChanged(); + Invalidate(); + } + } +} diff --git a/src/Core/CurveControlLuminosity.cs b/src/Core/CurveControlLuminosity.cs new file mode 100644 index 0000000..122de69 --- /dev/null +++ b/src/Core/CurveControlLuminosity.cs @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Curve control specialized for luminosity + /// + public sealed class CurveControlLuminosity + : CurveControl + { + public CurveControlLuminosity() + : base(1, 256) + { + this.mask = new bool[1]{true}; + visualColors = new ColorBgra[]{ + ColorBgra.Black + }; + channelNames = new string[]{ + PdnResources.GetString("CurveControlLuminosity.Luminosity") + }; + ResetControlPoints(); + } + + public override ColorTransferMode ColorTransferMode + { + get + { + return ColorTransferMode.Luminosity; + } + } + } +} diff --git a/src/Core/CurveControlRgb.cs b/src/Core/CurveControlRgb.cs new file mode 100644 index 0000000..baa57c1 --- /dev/null +++ b/src/Core/CurveControlRgb.cs @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Curve control specialization for RGB curves + /// + public sealed class CurveControlRgb + : CurveControl + { + public CurveControlRgb() + : base(3, 256) + { + this.mask = new bool[3] { true, true, true }; + visualColors = new ColorBgra[] { + ColorBgra.Red, + ColorBgra.Green, + ColorBgra.Blue + }; + channelNames = new string[]{ + PdnResources.GetString("CurveControlRgb.Red"), + PdnResources.GetString("CurveControlRgb.Green"), + PdnResources.GetString("CurveControlRgb.Blue") + }; + ResetControlPoints(); + } + + public override ColorTransferMode ColorTransferMode + { + get + { + return ColorTransferMode.Rgb; + } + } + } +} diff --git a/src/Core/EdgeSnapOptions.cs b/src/Core/EdgeSnapOptions.cs new file mode 100644 index 0000000..c3d3c36 --- /dev/null +++ b/src/Core/EdgeSnapOptions.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + [Flags] + public enum EdgeSnapOptions + { + None = 0, + SnapLeftEdgeToContainerLeftEdge = 1, + SnapRightEdgeToContainerRightEdge = 2 + } +} diff --git a/src/Core/EnumLocalizer.cs b/src/Core/EnumLocalizer.cs new file mode 100644 index 0000000..7317990 --- /dev/null +++ b/src/Core/EnumLocalizer.cs @@ -0,0 +1,151 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; + +namespace PaintDotNet +{ + /// + /// Provides convenience methods for converting between enumeration values + /// and their localized value names. This class only works with strings + /// loaded via the PdnResources class. + /// + public sealed class EnumLocalizer + { + private Type enumType; + private Hashtable valueToName; + private Hashtable nameToValue; + private static Hashtable typeToWrapper; + + public Type EnumType + { + get + { + return this.enumType; + } + } + + public object[] GetEnumValues() + { + ICollection valueNames = this.valueToName.Keys; + object[] values = new object[valueNames.Count]; + + int index = 0; + foreach (string valueName in valueNames) + { + values[index] = Enum.Parse(this.enumType, valueName); + ++index; + } + + return values; + } + + public string[] GetLocalizedNames() + { + ICollection keysCollection = this.nameToValue.Keys; + string[] keysArray = new string[keysCollection.Count]; + + int index = 0; + foreach (string key in keysCollection) + { + keysArray[index] = key; + ++index; + } + + return keysArray; + } + + public static string EnumValueToLocalizedName(Type enumType, object enumValue) + { + EnumLocalizer wrapper = EnumLocalizer.Create(enumType); + return wrapper.EnumValueToLocalizedName(enumValue); + } + + public string EnumValueToLocalizedName(object enumValue) + { + object retValue = this.valueToName[enumValue.ToString()]; + + if (retValue == null) + { + this.valueToName.Remove(enumValue); + return null; + } + else + { + return (string)retValue; + } + } + + public object LocalizedNameToEnumValue(string locName) + { + object enumValueName = this.nameToValue[locName]; + + if (enumValueName == null) + { + this.nameToValue.Remove(locName); + return null; + } + else + { + object enumValue = Enum.Parse(this.enumType, (string)enumValueName); + return enumValue; + } + } + + public string LocalizedNameToEnumValueName(string locName) + { + object enumValueName = this.nameToValue[locName]; + + if (enumValueName == null) + { + this.nameToValue.Remove(locName); + return null; + } + else + { + return (string)enumValueName; + } + } + + public static EnumLocalizer Create(Type enumType) + { + if (typeToWrapper == null) + { + typeToWrapper = new Hashtable(); + } + + object wrapper = typeToWrapper[enumType]; + + if (wrapper == null) + { + wrapper = new EnumLocalizer(enumType); + typeToWrapper[enumType] = wrapper; + } + + return (EnumLocalizer)wrapper; + } + + private EnumLocalizer(Type enumType) + { + this.enumType = enumType; + + this.valueToName = new Hashtable(); + this.nameToValue = new Hashtable(); + + foreach (string enumValueName in Enum.GetNames(this.enumType)) + { + string resourceName = this.enumType.Name + "." + enumValueName; + string localizedName = PdnResources.GetString(resourceName); + this.valueToName.Add(enumValueName, localizedName); + this.nameToValue.Add(localizedName, enumValueName); + } + } + } +} diff --git a/src/Core/EtchedLine.cs b/src/Core/EtchedLine.cs new file mode 100644 index 0000000..d1c333b --- /dev/null +++ b/src/Core/EtchedLine.cs @@ -0,0 +1,107 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + public sealed class EtchedLine + : Control + { + private bool selfDrawn = false; + private Label label; + + public void InitForCurrentVisualStyle() + { + // If we are Vista Aero, draw using a GroupBox + // Else, use the "etched line via a label w/ a border style" trick + // We would use the "GroupBox" style w/ Luna, except that it wasn't + // working correctly for some reason. + + switch (UI.VisualStyleClass) + { + case VisualStyleClass.Aero: + this.selfDrawn = true; + break; + + case VisualStyleClass.Luna: + case VisualStyleClass.Classic: + case VisualStyleClass.Other: + this.selfDrawn = false; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + if (this.selfDrawn && (this.label != null && Controls.Contains(this.label))) + { + SuspendLayout(); + Controls.Remove(this.label); + ResumeLayout(false); + PerformLayout(); + Invalidate(true); + } + else if (!this.selfDrawn && (this.label != null || !Controls.Contains(this.label))) + { + if (this.label == null) + { + this.label = new Label(); + this.label.BorderStyle = BorderStyle.Fixed3D; + } + + SuspendLayout(); + Controls.Add(this.label); + ResumeLayout(false); + PerformLayout(); + Invalidate(true); + } + } + + public EtchedLine() + { + InitForCurrentVisualStyle(); + DoubleBuffered = true; + ResizeRedraw = true; + TabStop = false; + SetStyle(ControlStyles.Selectable, false); + } + + public override Size GetPreferredSize(Size proposedSize) + { + return new Size(proposedSize.Width, 2); + } + + protected override void OnPaint(PaintEventArgs e) + { + if (this.selfDrawn) + { + GroupBoxRenderer.DrawGroupBox(e.Graphics, new Rectangle(0, 0, Width, 1), GroupBoxState.Normal); + } + + base.OnPaint(e); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + if (!this.selfDrawn) + { + this.label.Bounds = new Rectangle(0, 0, Width, Height); + } + + base.OnLayout(levent); + } + } +} diff --git a/src/Core/GradientRenderer.cs b/src/Core/GradientRenderer.cs new file mode 100644 index 0000000..1956af7 --- /dev/null +++ b/src/Core/GradientRenderer.cs @@ -0,0 +1,284 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public abstract class GradientRenderer + { + private BinaryPixelOp normalBlendOp; + private ColorBgra startColor; + private ColorBgra endColor; + private PointF startPoint; + private PointF endPoint; + private bool alphaBlending; + private bool alphaOnly; + + private bool lerpCacheIsValid = false; + private byte[] lerpAlphas; + private ColorBgra[] lerpColors; + + public ColorBgra StartColor + { + get + { + return this.startColor; + } + + set + { + if (this.startColor != value) + { + this.startColor = value; + this.lerpCacheIsValid = false; + } + } + } + + public ColorBgra EndColor + { + get + { + return this.endColor; + } + + set + { + if (this.endColor != value) + { + this.endColor = value; + this.lerpCacheIsValid = false; + } + } + } + + public PointF StartPoint + { + get + { + return this.startPoint; + } + + set + { + this.startPoint = value; + } + } + + public PointF EndPoint + { + get + { + return this.endPoint; + } + + set + { + this.endPoint = value; + } + } + + public bool AlphaBlending + { + get + { + return this.alphaBlending; + } + + set + { + this.alphaBlending = value; + } + } + + public bool AlphaOnly + { + get + { + return this.alphaOnly; + } + + set + { + this.alphaOnly = value; + } + } + + public virtual void BeforeRender() + { + if (!this.lerpCacheIsValid) + { + byte startAlpha; + byte endAlpha; + + if (this.alphaOnly) + { + ComputeAlphaOnlyValuesFromColors(this.startColor, this.endColor, out startAlpha, out endAlpha); + } + else + { + startAlpha = this.startColor.A; + endAlpha = this.endColor.A; + } + + this.lerpAlphas = new byte[256]; + this.lerpColors = new ColorBgra[256]; + + for (int i = 0; i < 256; ++i) + { + byte a = (byte)i; + this.lerpColors[a] = ColorBgra.Blend(this.startColor, this.endColor, a); + this.lerpAlphas[a] = (byte)(startAlpha + ((endAlpha - startAlpha) * a) / 255); + } + + this.lerpCacheIsValid = true; + } + } + + public abstract float ComputeUnboundedLerp(int x, int y); + public abstract float BoundLerp(float t); + + public virtual void AfterRender() + { + } + + private static void ComputeAlphaOnlyValuesFromColors(ColorBgra startColor, ColorBgra endColor, out byte startAlpha, out byte endAlpha) + { + startAlpha = startColor.A; + endAlpha = (byte)(255 - endColor.A); + } + + public unsafe void Render(Surface surface, Rectangle[] rois, int startIndex, int length) + { + byte startAlpha; + byte endAlpha; + + if (this.alphaOnly) + { + ComputeAlphaOnlyValuesFromColors(this.startColor, this.endColor, out startAlpha, out endAlpha); + } + else + { + startAlpha = this.startColor.A; + endAlpha = this.endColor.A; + } + + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle rect = rois[ri]; + + if (this.startPoint == this.endPoint) + { + // Start and End point are the same ... fill with solid color. + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* pixelPtr = surface.GetPointAddress(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + ColorBgra result; + + if (this.alphaOnly && this.alphaBlending) + { + byte resultAlpha = (byte)Utility.FastDivideShortByByte((ushort)(pixelPtr->A * endAlpha), 255); + result = *pixelPtr; + result.A = resultAlpha; + } + else if (this.alphaOnly && !this.alphaBlending) + { + result = *pixelPtr; + result.A = endAlpha; + } + else if (!this.alphaOnly && this.alphaBlending) + { + result = this.normalBlendOp.Apply(*pixelPtr, this.endColor); + } + else //if (!this.alphaOnly && !this.alphaBlending) + { + result = this.endColor; + } + + *pixelPtr = result; + ++pixelPtr; + } + } + } + else + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* pixelPtr = surface.GetPointAddress(rect.Left, y); + + if (this.alphaOnly && this.alphaBlending) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + float lerpUnbounded = ComputeUnboundedLerp(x, y); + float lerpBounded = BoundLerp(lerpUnbounded); + byte lerpByte = (byte)(lerpBounded * 255.0f); + byte lerpAlpha = this.lerpAlphas[lerpByte]; + byte resultAlpha = Utility.FastScaleByteByByte(pixelPtr->A, lerpAlpha); + pixelPtr->A = resultAlpha; + ++pixelPtr; + } + } + else if (this.alphaOnly && !this.alphaBlending) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + float lerpUnbounded = ComputeUnboundedLerp(x, y); + float lerpBounded = BoundLerp(lerpUnbounded); + byte lerpByte = (byte)(lerpBounded * 255.0f); + byte lerpAlpha = this.lerpAlphas[lerpByte]; + pixelPtr->A = lerpAlpha; + ++pixelPtr; + } + } + else if (!this.alphaOnly && (this.alphaBlending && (startAlpha != 255 || endAlpha != 255))) + { + // If we're doing all color channels, and we're doing alpha blending, and if alpha blending is necessary + for (int x = rect.Left; x < rect.Right; ++x) + { + float lerpUnbounded = ComputeUnboundedLerp(x, y); + float lerpBounded = BoundLerp(lerpUnbounded); + byte lerpByte = (byte)(lerpBounded * 255.0f); + ColorBgra lerpColor = this.lerpColors[lerpByte]; + ColorBgra result = this.normalBlendOp.Apply(*pixelPtr, lerpColor); + *pixelPtr = result; + ++pixelPtr; + } + } + else //if (!this.alphaOnly && !this.alphaBlending) // or sC.A == 255 && eC.A == 255 + { + for (int x = rect.Left; x < rect.Right; ++x) + { + float lerpUnbounded = ComputeUnboundedLerp(x, y); + float lerpBounded = BoundLerp(lerpUnbounded); + byte lerpByte = (byte)(lerpBounded * 255.0f); + ColorBgra lerpColor = this.lerpColors[lerpByte]; + *pixelPtr = lerpColor; + ++pixelPtr; + } + } + } + } + } + + AfterRender(); + } + + protected internal GradientRenderer(bool alphaOnly, BinaryPixelOp normalBlendOp) + { + this.normalBlendOp = normalBlendOp; + this.alphaOnly = alphaOnly; + } + } +} diff --git a/src/Core/GradientRenderers.cs b/src/Core/GradientRenderers.cs new file mode 100644 index 0000000..9eb0a3b --- /dev/null +++ b/src/Core/GradientRenderers.cs @@ -0,0 +1,216 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public static class GradientRenderers + { + public abstract class LinearBase + : GradientRenderer + { + protected float dtdx; + protected float dtdy; + + public override void BeforeRender() + { + PointF vec = new PointF(EndPoint.X - StartPoint.X, EndPoint.Y - StartPoint.Y); + float mag = Utility.Magnitude(vec); + + if (EndPoint.X == StartPoint.X) + { + this.dtdx = 0; + } + else + { + this.dtdx = vec.X / (mag * mag); + } + + if (EndPoint.Y == StartPoint.Y) + { + this.dtdy = 0; + } + else + { + this.dtdy = vec.Y / (mag * mag); + } + + base.BeforeRender(); + } + + protected internal LinearBase(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + + public abstract class LinearStraight + : LinearBase + { + public override float ComputeUnboundedLerp(int x, int y) + { + float dx = x - StartPoint.X; + float dy = y - StartPoint.Y; + + float lerp = (dx * this.dtdx) + (dy * this.dtdy); + + return lerp; + } + + protected internal LinearStraight(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + + public sealed class LinearReflected + : LinearStraight + { + public override float BoundLerp(float t) + { + return Utility.Clamp(Math.Abs(t), 0, 1); + } + + public LinearReflected(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + + public sealed class LinearClamped + : LinearStraight + { + public override float BoundLerp(float t) + { + return Utility.Clamp(t, 0, 1); + } + + public LinearClamped(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + + public sealed class LinearDiamond + : LinearStraight + { + public override float ComputeUnboundedLerp(int x, int y) + { + float dx = x - StartPoint.X; + float dy = y - StartPoint.Y; + + float lerp1 = (dx * this.dtdx) + (dy * this.dtdy); + float lerp2 = (dx * this.dtdy) - (dy * this.dtdx); + + float absLerp1 = Math.Abs(lerp1); + float absLerp2 = Math.Abs(lerp2); + + return absLerp1 + absLerp2; + } + + public override float BoundLerp(float t) + { + return Utility.Clamp(t, 0, 1); + } + + public LinearDiamond(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + + public sealed class Radial + : GradientRenderer + { + private float invDistanceScale; + + public override void BeforeRender() + { + float distanceScale = Utility.Distance(this.StartPoint, this.EndPoint); + + if (distanceScale == 0) + { + this.invDistanceScale = 0; + } + else + { + this.invDistanceScale = 1.0f / distanceScale; + } + + base.BeforeRender(); + } + + public override float ComputeUnboundedLerp(int x, int y) + { + float dx = x - StartPoint.X; + float dy = y - StartPoint.Y; + + float distance = (float)Math.Sqrt(dx * dx + dy * dy); + + return distance * this.invDistanceScale; + } + + public override float BoundLerp(float t) + { + return Utility.Clamp(t, 0, 1); + } + + public Radial(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + + public sealed class Conical + : GradientRenderer + { + private float tOffset; + private const float invPi = (float)(1.0 / Math.PI); + + public override void BeforeRender() + { + this.tOffset = -ComputeUnboundedLerp((int)EndPoint.X, (int)EndPoint.Y); + base.BeforeRender(); + } + + public override float ComputeUnboundedLerp(int x, int y) + { + float ax = x - StartPoint.X; + float ay = y - StartPoint.Y; + + float theta = (float)Math.Atan2(ay, ax); + + float t = theta * invPi; + + return t + this.tOffset; + } + + public override float BoundLerp(float t) + { + if (t > 1) + { + t -= 2; + } + else if (t < -1) + { + t += 2; + } + + return Utility.Clamp(Math.Abs(t), 0, 1); + } + + public Conical(bool alphaOnly, BinaryPixelOp normalBlendOp) + : base(alphaOnly, normalBlendOp) + { + } + } + } +} diff --git a/src/Core/HandledEventArgs`1.cs b/src/Core/HandledEventArgs`1.cs new file mode 100644 index 0000000..ee2a879 --- /dev/null +++ b/src/Core/HandledEventArgs`1.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; + +namespace PaintDotNet +{ + public class HandledEventArgs + : HandledEventArgs + { + private T data; + public T Data + { + get + { + return this.data; + } + } + + public HandledEventArgs(bool handled, T data) + : base(handled) + { + this.data = data; + } + } +} diff --git a/src/Core/HandledEventHandler`1.cs b/src/Core/HandledEventHandler`1.cs new file mode 100644 index 0000000..9984dd9 --- /dev/null +++ b/src/Core/HandledEventHandler`1.cs @@ -0,0 +1,17 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + public delegate void HandledEventHandler(object sender, HandledEventArgs e); +} diff --git a/src/Core/HeaderLabel.cs b/src/Core/HeaderLabel.cs new file mode 100644 index 0000000..054be9e --- /dev/null +++ b/src/Core/HeaderLabel.cs @@ -0,0 +1,136 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + public sealed class HeaderLabel + : Control + { + private const TextFormatFlags textFormatFlags = + TextFormatFlags.Default | + TextFormatFlags.EndEllipsis | + TextFormatFlags.HidePrefix | + TextFormatFlags.NoPadding | + TextFormatFlags.NoPrefix | + TextFormatFlags.SingleLine; + + private int leftMargin = 2; + private int rightMargin = 8; + + private EtchedLine etchedLine; + + [DefaultValue(8)] + public int RightMargin + { + get + { + return this.rightMargin; + } + + set + { + this.rightMargin = value; + PerformLayout(); + } + } + + protected override void OnFontChanged(EventArgs e) + { + PerformLayout(); + Refresh(); + base.OnFontChanged(e); + } + + protected override void OnTextChanged(EventArgs e) + { + PerformLayout(); + Refresh(); + base.OnTextChanged(e); + } + + public HeaderLabel() + { + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.Opaque, true); + SetStyle(ControlStyles.ResizeRedraw, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.Selectable, false); + UI.InitScaling(null); + TabStop = false; + ForeColor = SystemColors.Highlight; + DoubleBuffered = true; + ResizeRedraw = true; + + SuspendLayout(); + this.etchedLine = new EtchedLine(); + Controls.Add(this.etchedLine); + Size = new Size(144, 14); + ResumeLayout(false); + } + + private int GetPreferredWidth(Size proposedSize) + { + Size textSize = GetTextSize(); + return this.leftMargin + textSize.Width; + } + + public override Size GetPreferredSize(Size proposedSize) + { + return new Size(Math.Max(proposedSize.Width, GetPreferredWidth(proposedSize)), GetTextSize().Height); + } + + private Size GetTextSize() + { + string textToUse = string.IsNullOrEmpty(Text) ? " " : Text; + + Size size = TextRenderer.MeasureText(textToUse, this.Font, this.ClientSize, textFormatFlags); + + if (string.IsNullOrEmpty(Text)) + { + size.Width = 0; + } + + return size; + } + + protected override void OnLayout(LayoutEventArgs levent) + { + Size textSize = GetTextSize(); + + int lineLeft = (string.IsNullOrEmpty(this.Text) ? 0 : this.leftMargin) + textSize.Width + (string.IsNullOrEmpty(this.Text) ? 0 : 1); + int lineRight = ClientRectangle.Right - this.rightMargin; + + this.etchedLine.Size = this.etchedLine.GetPreferredSize(new Size(lineRight - lineLeft, 1)); + this.etchedLine.Location = new Point(lineLeft, (ClientSize.Height - this.etchedLine.Height) / 2); + + base.OnLayout(levent); + } + + protected override void OnPaint(PaintEventArgs e) + { + using (SolidBrush backBrush = new SolidBrush(BackColor)) + { + e.Graphics.FillRectangle(backBrush, e.ClipRectangle); + } + + Size textSize = GetTextSize(); + TextRenderer.DrawText(e.Graphics, this.Text, this.Font, new Point(this.leftMargin, 0), SystemColors.WindowText, textFormatFlags); + + base.OnPaint(e); + } + } +} diff --git a/src/Core/HeaderLabel.resx b/src/Core/HeaderLabel.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/Histogram.cs b/src/Core/Histogram.cs new file mode 100644 index 0000000..6c129cd --- /dev/null +++ b/src/Core/Histogram.cs @@ -0,0 +1,222 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Histogram is used to calculate a histogram for a surface (in a selection, + /// if desired). This can then be used to retrieve percentile, average, peak, + /// and distribution information. + /// + public abstract class Histogram + { + protected long[][] histogram; + public long[][] HistogramValues + { + get + { + return this.histogram; + } + + set + { + if (value.Length == this.histogram.Length && value[0].Length == this.histogram[0].Length) + { + this.histogram = value; + OnHistogramUpdated(); + } + else + { + throw new ArgumentException("value muse be an array of arrays of matching size", "value"); + } + } + } + + public int Channels + { + get + { + return this.histogram.Length; + } + } + + public int Entries + { + get + { + return this.histogram[0].Length; + } + } + + protected internal Histogram(int channels, int entries) + { + this.histogram = new long[channels][]; + + for (int channel = 0; channel < channels; ++channel) + { + this.histogram[channel] = new long[entries]; + } + } + + public event EventHandler HistogramChanged; + protected void OnHistogramUpdated() + { + if (HistogramChanged != null) + { + HistogramChanged(this, EventArgs.Empty); + } + } + + protected ColorBgra[] visualColors; + public ColorBgra GetVisualColor(int channel) + { + return visualColors[channel]; + } + + public long GetOccurrences(int channel, int val) + { + return histogram[channel][val]; + } + + public long GetMax() + { + long max = -1; + + foreach (long[] channelHistogram in histogram) + { + foreach (long i in channelHistogram) + { + if (i > max) + { + max = i; + } + } + } + + return max; + } + + public long GetMax(int channel) + { + long max = -1; + + foreach (long i in histogram[channel]) + { + if (i > max) + { + max = i; + } + } + + return max; + } + + public float[] GetMean() + { + float[] ret = new float[Channels]; + + for (int channel = 0; channel < Channels; ++channel) + { + long[] channelHistogram = histogram[channel]; + long avg = 0; + long sum = 0; + + for (int j = 0; j < channelHistogram.Length; j++) + { + avg += j * channelHistogram[j]; + sum += channelHistogram[j]; + } + + if (sum != 0) + { + ret[channel] = (float)avg / (float)sum; + } + else + { + ret[channel] = 0; + } + } + + return ret; + } + + public int[] GetPercentile(float fraction) + { + int[] ret = new int[Channels]; + + for (int channel = 0; channel < Channels; ++channel) + { + long[] channelHistogram = histogram[channel]; + long integral = 0; + long sum = 0; + + for (int j = 0; j < channelHistogram.Length; j++) + { + sum += channelHistogram[j]; + } + + for (int j = 0; j < channelHistogram.Length; j++) + { + integral += channelHistogram[j]; + + if (integral > sum * fraction) + { + ret[channel] = j; + break; + } + } + } + + return ret; + } + + public abstract ColorBgra GetMeanColor(); + + public abstract ColorBgra GetPercentileColor(float fraction); + + /// + /// Sets the histogram to be all zeros. + /// + protected void Clear() + { + histogram.Initialize(); + } + + protected abstract void AddSurfaceRectangleToHistogram(Surface surface, Rectangle rect); + + public void UpdateHistogram(Surface surface) + { + Clear(); + AddSurfaceRectangleToHistogram(surface, surface.Bounds); + OnHistogramUpdated(); + } + + public void UpdateHistogram(Surface surface, Rectangle rect) + { + Clear(); + AddSurfaceRectangleToHistogram(surface, rect); + OnHistogramUpdated(); + } + + public void UpdateHistogram(Surface surface, PdnRegion roi) + { + Clear(); + + foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt()) + { + AddSurfaceRectangleToHistogram(surface, rect); + } + + OnHistogramUpdated(); + } + } +} diff --git a/src/Core/HistogramControl.cs b/src/Core/HistogramControl.cs new file mode 100644 index 0000000..3577c5f --- /dev/null +++ b/src/Core/HistogramControl.cs @@ -0,0 +1,213 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class HistogramControl + : UserControl + { + private Histogram histogram; + public Histogram Histogram + { + get + { + return histogram; + } + + set + { + if (histogram != value) + { + if (histogram != null) + { + histogram.HistogramChanged -= histogramChangedDelegate; + } + histogram = value; + histogram.HistogramChanged += histogramChangedDelegate; + + int channels = histogram.Channels; + + if (selected == null || channels != selected.GetLength(0)) + { + selected = new bool[channels]; + } + + Invalidate(); + } + } + } + + public int Channels + { + get + { + return histogram.Channels; + } + } + + public int Entries + { + get + { + return histogram.Entries; + } + } + + private bool[] selected; + + public void SetSelected(int channel, bool val) + { + selected[channel] = val; + Invalidate(); + } + + public bool GetSelected(int channel) + { + return selected[channel]; + } + + private bool flipHorizontal; + public bool FlipHorizontal + { + get + { + return flipHorizontal; + } + set + { + flipHorizontal = value; + } + } + + private bool flipVertical; + public bool FlipVertical + { + get + { + return flipVertical; + } + set + { + flipVertical = value; + } + } + + public EventHandler histogramChangedDelegate; + public HistogramControl() + { + histogramChangedDelegate = new EventHandler(Histogram_HistogramChanged); + + this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | + ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true); + } + + protected override void Dispose(bool disposing) + { + base.Dispose (disposing); + } + + private const int tickSize = 4; + + private void RenderChannel(Graphics g, ColorBgra color, int channel, long max, float mean) + { + Rectangle innerRect = ClientRectangle; + + int l = innerRect.Left; + int t = innerRect.Top; + int b = innerRect.Bottom; + int r = innerRect.Right; + int channels = histogram.Channels; + int entries = histogram.Entries; + long[] hist = Histogram.HistogramValues[channel]; + + ++max; + + if (flipHorizontal) + { + Utility.Swap(ref l, ref r); + } + + if (!flipVertical) + { + Utility.Swap(ref t, ref b); + } + + PointF[] points = new PointF[entries + 2]; + + points[entries] = new PointF(Utility.Lerp(l, r, -1), Utility.Lerp(t, b, 20)); + points[entries + 1] = new PointF(Utility.Lerp(l, r, -1), Utility.Lerp(b, t, 20)); + + for (int i = 0; i < entries; i += entries - 1) + { + points[i] = new PointF( + Utility.Lerp(l, r, (float)hist[i] / (float)max), + Utility.Lerp(t, b, (float)i / (float)entries)); + } + + long sum3 = hist[0] + hist[1]; + + for (int i = 1; i < entries - 1; ++i) + { + sum3 += hist[i + 1]; + + points[i] = new PointF( + Utility.Lerp(l, r, (float)(sum3) / (float)(max * 3.1f)), + Utility.Lerp(t, b, (float)i / (float)entries)); + + sum3 -= hist[i - 1]; + } + + byte intensity = selected[channel] ? (byte)96 : (byte)32; + ColorBgra colorPen = ColorBgra.Blend(ColorBgra.Black, color, intensity); + ColorBgra colorBrush = color; + + colorBrush.A = intensity; + + Pen pen = new Pen(colorPen.ToColor(), 1.3f); + SolidBrush brush = new SolidBrush(colorBrush.ToColor()); + + g.FillPolygon(brush, points, FillMode.Alternate); + g.DrawPolygon(pen, points); + } + + private void RenderHistogram(Graphics g) + { + long max = histogram.GetMax(); + float[] mean = histogram.GetMean(); + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.Clear(BackColor); + int channels = histogram.Channels; + + for (int i = 0; i < channels; ++i) + { + RenderChannel(g, histogram.GetVisualColor(i), i, max, mean[i]); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint (e); + + RenderHistogram(e.Graphics); + } + + private void Histogram_HistogramChanged(object sender, EventArgs e) + { + Invalidate(); + } + } +} diff --git a/src/Core/HistogramControl.resx b/src/Core/HistogramControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/HistogramLuminosity.cs b/src/Core/HistogramLuminosity.cs new file mode 100644 index 0000000..f48383b --- /dev/null +++ b/src/Core/HistogramLuminosity.cs @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Histogram is used to calculate a histogram for a surface (in a selection, + /// if desired). This can then be used to retrieve percentile, average, peak, + /// and distribution information. + /// + public sealed class HistogramLuminosity + : Histogram + { + public HistogramLuminosity() + : base(1, 256) + { + this.visualColors = new ColorBgra[] { ColorBgra.Black }; + } + + public override ColorBgra GetMeanColor() + { + float[] mean = GetMean(); + return ColorBgra.FromBgr((byte)(mean[0] + 0.5f), (byte)(mean[0] + 0.5f), (byte)(mean[0] + 0.5f)); + } + + public override ColorBgra GetPercentileColor(float fraction) + { + int[] perc = GetPercentile(fraction); + return ColorBgra.FromBgr((byte)(perc[0]), (byte)(perc[0]), (byte)(perc[0])); + } + + protected override unsafe void AddSurfaceRectangleToHistogram(Surface surface, Rectangle rect) + { + long[] histogramLuminosity = histogram[0]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + + ColorBgra* ptr = surface.GetPointAddressUnchecked(rect.Left, y); + for (int x = rect.Left; x < rect.Right; ++x) + { + ++histogramLuminosity[ptr->GetIntensityByte()]; + ++ptr; + } + } + } + + public UnaryPixelOps.Level MakeLevelsAuto() + { + ColorBgra lo, md, hi; + + lo = GetPercentileColor(0.005f); + md = GetMeanColor(); + hi = GetPercentileColor(0.995f); + + return UnaryPixelOps.Level.AutoFromLoMdHi(lo, md, hi); + } + } +} diff --git a/src/Core/HistogramRGB.cs b/src/Core/HistogramRGB.cs new file mode 100644 index 0000000..be73bf2 --- /dev/null +++ b/src/Core/HistogramRGB.cs @@ -0,0 +1,136 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Histogram is used to calculate a histogram for a surface (in a selection, + /// if desired). This can then be used to retrieve percentile, average, peak, + /// and distribution information. + /// + public sealed class HistogramRgb + : Histogram + { + public HistogramRgb() + : base(3, 256) + { + visualColors = new ColorBgra[]{ + ColorBgra.Blue, + ColorBgra.Green, + ColorBgra.Red + }; + } + + public override ColorBgra GetMeanColor() + { + float[] mean = GetMean(); + return ColorBgra.FromBgr((byte)(mean[0] + 0.5f), (byte)(mean[1] + 0.5f), (byte)(mean[2] + 0.5f)); + } + + public override ColorBgra GetPercentileColor(float fraction) + { + int[] perc = GetPercentile(fraction); + + return ColorBgra.FromBgr((byte)(perc[0]), (byte)(perc[1]), (byte)(perc[2])); + } + + protected override unsafe void AddSurfaceRectangleToHistogram(Surface surface, Rectangle rect) + { + long[] histogramB = histogram[0]; + long[] histogramG = histogram[1]; + long[] histogramR = histogram[2]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* ptr = surface.GetPointAddressUnchecked(rect.Left, y); + for (int x = rect.Left; x < rect.Right; ++x) + { + ++histogramB[ptr->B]; + ++histogramG[ptr->G]; + ++histogramR[ptr->R]; + ++ptr; + } + } + } + + public void SetFromLeveledHistogram(HistogramRgb inputHistogram, UnaryPixelOps.Level upo) + { + if (inputHistogram == null || upo == null) + { + return; + } + + Clear(); + + float[] before = new float[3]; + float[] slopes = new float[3]; + + for (int c = 0; c < 3; c++) + { + long[] channelHistogramOutput = histogram[c]; + long[] channelHistogramInput = inputHistogram.histogram[c]; + + for (int v = 0; v <= 255; v++) + { + ColorBgra after = ColorBgra.FromBgr((byte)v, (byte)v, (byte)v); + + upo.UnApply(after, before, slopes); + + if (after[c] > upo.ColorOutHigh[c] + || after[c] < upo.ColorOutLow[c] + || (int)Math.Floor(before[c]) < 0 + || (int)Math.Ceiling(before[c]) > 255 + || float.IsNaN(before[c])) + { + channelHistogramOutput[v] = 0; + } + else if (before[c] <= upo.ColorInLow[c]) + { + channelHistogramOutput[v] = 0; + + for (int i = 0; i <= upo.ColorInLow[c]; i++) + { + channelHistogramOutput[v] += channelHistogramInput[i]; + } + } + else if (before[c] >= upo.ColorInHigh[c]) + { + channelHistogramOutput[v] = 0; + + for (int i = upo.ColorInHigh[c]; i < 256; i++) + { + channelHistogramOutput[v] += channelHistogramInput[i]; + } + } + else + { + channelHistogramOutput[v] = (int)(slopes[c] * Utility.Lerp( + channelHistogramInput[(int)Math.Floor(before[c])], + channelHistogramInput[(int)Math.Ceiling(before[c])], + before[c] - Math.Floor(before[c]))); + } + } + } + + OnHistogramUpdated(); + } + + public UnaryPixelOps.Level MakeLevelsAuto() + { + ColorBgra lo = GetPercentileColor(0.005f); + ColorBgra md = GetMeanColor(); + ColorBgra hi = GetPercentileColor(0.995f); + + return UnaryPixelOps.Level.AutoFromLoMdHi(lo, md, hi); + } + } +} diff --git a/src/Core/HorizontalSnapEdge.cs b/src/Core/HorizontalSnapEdge.cs new file mode 100644 index 0000000..5e63e92 --- /dev/null +++ b/src/Core/HorizontalSnapEdge.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum HorizontalSnapEdge + { + Neither, + Top, + Bottom + } +} diff --git a/src/Core/HsvColor.cs b/src/Core/HsvColor.cs new file mode 100644 index 0000000..1204e84 --- /dev/null +++ b/src/Core/HsvColor.cs @@ -0,0 +1,192 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Adapted from: + /// "A Primer on Building a Color Picker User Control with GDI+ in Visual Basic .NET or C#" + /// http://www.msdnaa.net/Resources/display.aspx?ResID=2460 + /// + public struct HsvColor + { + public int Hue; // 0-360 + public int Saturation; // 0-100 + public int Value; // 0-100 + + public static bool operator== (HsvColor lhs, HsvColor rhs) + { + if ((lhs.Hue == rhs.Hue) && + (lhs.Saturation == rhs.Saturation) && + (lhs.Value == rhs.Value)) + { + return true; + } + else + { + return false; + } + } + + public static bool operator!= (HsvColor lhs, HsvColor rhs) + { + return !(lhs == rhs); + } + + public override bool Equals(object obj) + { + return this == (HsvColor)obj; + } + + public override int GetHashCode() + { + return (Hue + (Saturation << 8) + (Value << 16)).GetHashCode();; + } + + public HsvColor(int hue, int saturation, int value) + { + if (hue < 0 || hue > 360) + { + throw new ArgumentOutOfRangeException("hue", "must be in the range [0, 360]"); + } + + if (saturation < 0 || saturation > 100) + { + throw new ArgumentOutOfRangeException("saturation", "must be in the range [0, 100]"); + } + + if (value < 0 || value > 100) + { + throw new ArgumentOutOfRangeException("value", "must be in the range [0, 100]"); + } + + Hue = hue; + Saturation = saturation; + Value = value; + } + + public static HsvColor FromColor(Color color) + { + RgbColor rgb = new RgbColor(color.R, color.G, color.B); + return rgb.ToHsv(); + } + + public Color ToColor() + { + RgbColor rgb = ToRgb(); + return Color.FromArgb(rgb.Red, rgb.Green, rgb.Blue); + } + + public RgbColor ToRgb() + { + // HsvColor contains values scaled as in the color wheel: + + double h; + double s; + double v; + + double r = 0; + double g = 0; + double b = 0; + + // Scale Hue to be between 0 and 360. Saturation + // and value scale to be between 0 and 1. + h = (double) Hue % 360; + s = (double) Saturation / 100; + v = (double) Value / 100; + + if (s == 0) + { + // If s is 0, all colors are the same. + // This is some flavor of gray. + r = v; + g = v; + b = v; + } + else + { + double p; + double q; + double t; + + double fractionalSector; + int sectorNumber; + double sectorPos; + + // The color wheel consists of 6 sectors. + // Figure out which sector you're in. + sectorPos = h / 60; + sectorNumber = (int)(Math.Floor(sectorPos)); + + // get the fractional part of the sector. + // That is, how many degrees into the sector + // are you? + fractionalSector = sectorPos - sectorNumber; + + // Calculate values for the three axes + // of the color. + p = v * (1 - s); + q = v * (1 - (s * fractionalSector)); + t = v * (1 - (s * (1 - fractionalSector))); + + // Assign the fractional colors to r, g, and b + // based on the sector the angle is in. + switch (sectorNumber) + { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + case 5: + r = v; + g = p; + b = q; + break; + } + } + // return an RgbColor structure, with values scaled + // to be between 0 and 255. + return new RgbColor((int)(r * 255), (int)(g * 255), (int)(b * 255)); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2})", Hue, Saturation, Value); + } + } +} diff --git a/src/Core/IBitVector2D.cs b/src/Core/IBitVector2D.cs new file mode 100644 index 0000000..1bfeb11 --- /dev/null +++ b/src/Core/IBitVector2D.cs @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public interface IBitVector2D + : ICloneable + { + int Width + { + get; + } + + int Height + { + get; + } + + bool this[int x, int y] + { + get; + set; + } + + bool this[System.Drawing.Point pt] + { + get; + set; + } + + bool IsEmpty + { + get; + } + + void Clear(bool newValue); + void Set(int x, int y, bool newValue); + void Set(Point pt, bool newValue); + void Set(Rectangle rect, bool newValue); + void Set(Scanline scan, bool newValue); + void Set(PdnRegion region, bool newValue); + void SetUnchecked(int x, int y, bool newValue); + bool Get(int x, int y); + bool GetUnchecked(int x, int y); + void Invert(int x, int y); + void Invert(Point pt); + void Invert(Rectangle rect); + void Invert(Scanline scan); + void Invert(PdnRegion region); + } +} diff --git a/src/Core/IFirstSelection.cs b/src/Core/IFirstSelection.cs new file mode 100644 index 0000000..e346f19 --- /dev/null +++ b/src/Core/IFirstSelection.cs @@ -0,0 +1,18 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface IFirstSelection + { + bool FirstSelect(); + } +} diff --git a/src/Core/IFormAssociate.cs b/src/Core/IFormAssociate.cs new file mode 100644 index 0000000..61decff --- /dev/null +++ b/src/Core/IFormAssociate.cs @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Used by classes to indicate they are associated with a certain Form, even if + /// they are not contained within the Form. To this end, they are an Associate of + /// the Form. + /// + public interface IFormAssociate + { + /// + /// Gets the Form that this object is associated with, or null if there is + /// no association. + /// + Form AssociatedForm + { + get; + } + } +} diff --git a/src/Core/IHotKeyTarget.cs b/src/Core/IHotKeyTarget.cs new file mode 100644 index 0000000..5aaed42 --- /dev/null +++ b/src/Core/IHotKeyTarget.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface IHotKeyTarget + : IDisposedEvent, + IFormAssociate + { + } +} diff --git a/src/Core/IPaintBackground.cs b/src/Core/IPaintBackground.cs new file mode 100644 index 0000000..91e92f0 --- /dev/null +++ b/src/Core/IPaintBackground.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public interface IPaintBackground + { + void PaintBackground(Graphics g, Rectangle clipRect); + } +} diff --git a/src/Core/IPixelOp.cs b/src/Core/IPixelOp.cs new file mode 100644 index 0000000..94b7249 --- /dev/null +++ b/src/Core/IPixelOp.cs @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Provides an interface for the methods that UnaryPixelOp and BinaryPixelOp share. + /// For UnaryPixelOp, this produces the function, "dst = F(src)" + /// For BinaryPixelOp, this produces the function, "dst = F(dst, src)" + /// + public interface IPixelOp + { + /// + /// This version of Apply has the liberty to decompose the rectangle of interest + /// or do whatever types of optimizations it wants to with it. This is generally + /// done to split the Apply operation into multiple threads. + /// + void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, Size roiSize); + + /// + /// This is the version of Apply that will always do exactly what you tell it do, + /// without optimizations or otherwise. + /// + void ApplyBase(Surface dst, Point dstOffset, Surface src, Point srcOffset, Size roiSize); + + /// + /// This version of Apply will perform on a scanline, not just a rectangle. + /// + void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int scanLength); + } +} + diff --git a/src/Core/IPluginSupportInfo.cs b/src/Core/IPluginSupportInfo.cs new file mode 100644 index 0000000..cccdbb9 --- /dev/null +++ b/src/Core/IPluginSupportInfo.cs @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface IPluginSupportInfo + { + string DisplayName + { + get; + } + + string Author + { + get; + } + + string Copyright + { + get; + } + + Version Version + { + get; + } + + Uri WebsiteUri + { + get; + } + } +} diff --git a/src/Core/ISnapManagerHost.cs b/src/Core/ISnapManagerHost.cs new file mode 100644 index 0000000..9bedb02 --- /dev/null +++ b/src/Core/ISnapManagerHost.cs @@ -0,0 +1,23 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + public interface ISnapManagerHost + { + SnapManager SnapManager + { + get; + } + } +} diff --git a/src/Core/ISnapObstacleHost.cs b/src/Core/ISnapObstacleHost.cs new file mode 100644 index 0000000..24da9ea --- /dev/null +++ b/src/Core/ISnapObstacleHost.cs @@ -0,0 +1,23 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + public interface ISnapObstacleHost + { + SnapObstacle SnapObstacle + { + get; + } + } +} diff --git a/src/Core/ISurfaceDraw.cs b/src/Core/ISurfaceDraw.cs new file mode 100644 index 0000000..8c29bb8 --- /dev/null +++ b/src/Core/ISurfaceDraw.cs @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Provides a standard interface for allowing an object to draw itself on to a given Surface. + /// + public interface ISurfaceDraw + { + /// + /// Draws the object on to the given Surface. + /// + /// The Surface to draw to. + void Draw(Surface dst); + + /// + /// Draws the object on to the given Surface after passing each pixel through + /// the given pixel operation as in: dst = pixelOp(dst, src) + /// + /// The Surface to draw to. + /// The pixelOp to use for rendering. + void Draw(Surface dst, IPixelOp pixelOp); + + /// + /// Draws the object on to the given Surface starting at the given (x,y) offset. + /// + /// The Surface to draw to. + /// The value to be added to every X coordinate that is used for drawing. + /// The value to be added to every Y coordinate that is used for drawing. + void Draw(Surface dst, int tX, int tY); + + + /// + /// Draws the object on to the given Surface starting at the given (x,y) offset after + /// passing each pixel through the given pixel operation as in: dst = pixelOp(dst, src) + /// + /// The Surface to draw to. + /// The value to be added to every X coordinate that is used for drawing. + /// The value to be added to every Y coordinate that is used for drawing. + void Draw(Surface dst, int tX, int tY, IPixelOp pixelOp); + } +} diff --git a/src/Core/IThumbnailProvider.cs b/src/Core/IThumbnailProvider.cs new file mode 100644 index 0000000..ab235da --- /dev/null +++ b/src/Core/IThumbnailProvider.cs @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public interface IThumbnailProvider + { + /// + /// Renders a thumbnail for the underlying object. + /// + /// The maximum edge length of the thumbnail. + /// + /// This method must only render the thumbnail without any borders. The Surface returned may have + /// a maximum size of (maxEdgeLength x maxEdgeLength). This method may be called from any thread. + /// The Surface returned is then owned by the calling method. + /// + /// + /// This method may throw exceptions; however, it must guarantee that the underlying object is + /// still valid and coherent in this situation. + /// + Surface RenderThumbnail(int maxEdgeLength); + } +} diff --git a/src/Core/IUnitsComboBox.cs b/src/Core/IUnitsComboBox.cs new file mode 100644 index 0000000..cbc82a5 --- /dev/null +++ b/src/Core/IUnitsComboBox.cs @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + public interface IUnitsComboBox + { + UnitsDisplayType UnitsDisplayType + { + get; + set; + } + + bool LowercaseStrings + { + get; + set; + } + + MeasurementUnit Units + { + get; + set; + } + + string UnitsText + { + get; + } + + bool PixelsAvailable + { + get; + set; + } + + bool InchesAvailable + { + get; + } + + bool CentimetersAvailable + { + get; + } + + void RemoveUnit(MeasurementUnit removeMe); + void AddUnit(MeasurementUnit addMe); + + event EventHandler UnitsChanged; + } +} diff --git a/src/Core/ImageListMenu.cs b/src/Core/ImageListMenu.cs new file mode 100644 index 0000000..622d6d0 --- /dev/null +++ b/src/Core/ImageListMenu.cs @@ -0,0 +1,385 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class ImageListMenu + : Control + { + private ComboBox comboBox; + private StringFormat stringFormat; + private Size itemSize = Size.Empty; + private Size maxImageSize = Size.Empty; + private Bitmap backBuffer = null; + + // layout parameters + private int imageXInset; + private int imageYInset; + private int textLeftMargin; + private int textRightMargin; + private int textVMargin; + + public sealed class Item + { + private Image image; + private string name; + private bool selected; + private object tag; + + public Image Image + { + get + { + return this.image; + } + } + + public string Name + { + get + { + return this.name; + } + } + + public bool Selected + { + get + { + return this.selected; + } + } + + public object Tag + { + get + { + return this.tag; + } + + set + { + this.tag = value; + } + } + + public override string ToString() + { + return this.name; + } + + public Item(Image image, string name, bool selected) + { + this.image = image; + this.name = name; + this.selected = selected; + } + } + + public event EventHandler> ItemClicked; + private void OnItemClicked(Item item) + { + if (ItemClicked != null) + { + ItemClicked(this, new EventArgs(item)); + } + } + + public event EventHandler Closed; + private void OnClosed() + { + if (Closed != null) + { + Closed(this, EventArgs.Empty); + } + } + + public bool IsImageListVisible + { + get + { + return this.comboBox.DroppedDown; + } + } + + private void DetermineMaxItemSize(Graphics g, Item[] items, out Size maxItemSizeResult, out Size maxImageSizeResult) + { + // Find max image height and width + int maxImageWidth = 0; + int maxImageHeight = 0; + int maxTextWidth = 0; + int maxTextHeight = 0; + + foreach (Item item in items) + { + maxImageWidth = Math.Max(maxImageWidth, item.Image.Width); + maxImageHeight = Math.Max(maxImageHeight, item.Image.Height); + + SizeF textSizeF = g.MeasureString(item.Name, Font, new PointF(0, 0), this.stringFormat); + Size textSize = Size.Ceiling(textSizeF); + + maxTextWidth = Math.Max(textSize.Width, maxTextWidth); + maxTextHeight = Math.Max(textSize.Height, maxTextHeight); + } + + int maxItemWidth = this.imageXInset + maxImageWidth + this.imageXInset + this.textLeftMargin + maxTextWidth + this.textRightMargin; + + int maxItemHeight = Math.Max( + this.imageYInset + maxImageHeight + this.imageYInset, + this.textVMargin + maxTextHeight + this.textVMargin); + + maxItemSizeResult = new Size(maxItemWidth, maxItemHeight); + maxImageSizeResult = new Size(maxImageWidth, maxImageHeight); + } + + public void ShowImageList(Item[] items) + { + HideImageList(); + + this.comboBox.Items.AddRange(items); + + using (Graphics g = CreateGraphics()) + { + DetermineMaxItemSize(g, items, out this.itemSize, out this.maxImageSize); + } + + this.comboBox.ItemHeight = this.itemSize.Height; + this.comboBox.DropDownWidth = this.itemSize.Width + SystemInformation.VerticalScrollBarWidth + UI.ScaleWidth(2); + + // Determine the max drop down height so that we don't cover up the button + Screen ourScreen = Screen.FromControl(this); + Point screenLocation = PointToScreen(new Point(this.comboBox.Left, this.comboBox.Bottom)); + int comboBoxToFloorHeight = ourScreen.WorkingArea.Height - screenLocation.Y; + + // make sure it is an integral multiple of itemSize.Height + comboBoxToFloorHeight = this.itemSize.Height * (comboBoxToFloorHeight / this.itemSize.Height); + // add 2 pixels for border + comboBoxToFloorHeight += 2; + + // But make sure it can hold at least 3 items + int minDropDownHeight = 2 + itemSize.Height * 3; // +2 for combobox's border + + int dropDownHeight = Math.Max(comboBoxToFloorHeight, minDropDownHeight); + + this.comboBox.DropDownHeight = dropDownHeight; + + int selectedIndex = Array.FindIndex( + items, + delegate(Item item) + { + return item.Selected; + }); + + this.comboBox.SelectedIndex = selectedIndex; + + // Make sure the combobox does not spill past the right edge of the screen + int left = PointToScreen(new Point(0, Height)).X; + if (left + this.comboBox.DropDownWidth > ourScreen.WorkingArea.Right) + { + left = ourScreen.WorkingArea.Right - this.comboBox.DropDownWidth; + } + + Point clientPt = PointToClient(new Point(left, screenLocation.Y)); + SuspendLayout(); + this.comboBox.Left = clientPt.X; + ResumeLayout(false); + + // Set focus to it so it can get mouse wheel events, and then show it! + this.comboBox.Focus(); + UI.ShowComboBox(this.comboBox, true); + } + + public void HideImageList() + { + UI.ShowComboBox(this.comboBox, false); + } + + public ImageListMenu() + { + UI.InitScaling(this); + InitializeComponent(); + + this.imageXInset = UI.ScaleWidth(2); + this.imageYInset = UI.ScaleHeight(4); + this.textLeftMargin = UI.ScaleWidth(4); + this.textRightMargin = UI.ScaleWidth(16); + this.textVMargin = UI.ScaleHeight(2); + + this.stringFormat = (StringFormat)StringFormat.GenericTypographic.Clone(); + } + + private void InitializeComponent() + { + this.comboBox = new ComboBox(); + this.comboBox.Name = "comboBox"; + this.comboBox.MeasureItem += new MeasureItemEventHandler(ComboBox_MeasureItem); + this.comboBox.DrawItem += new DrawItemEventHandler(ComboBox_DrawItem); + this.comboBox.DropDown += new EventHandler(ComboBox_DropDown); + this.comboBox.DropDownClosed += new EventHandler(ComboBox_DropDownClosed); + this.comboBox.DropDownStyle = ComboBoxStyle.DropDownList; + this.comboBox.SelectionChangeCommitted += new EventHandler(ComboBox_SelectionChangeCommitted); + + this.comboBox.DrawMode = DrawMode.OwnerDrawFixed; + this.comboBox.Visible = true; + this.TabStop = false; + this.Controls.Add(this.comboBox); + this.Name = "ImageListMenu"; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.stringFormat != null) + { + this.stringFormat.Dispose(); + this.stringFormat = null; + } + + if (this.backBuffer != null) + { + this.backBuffer.Dispose(); + this.backBuffer = null; + } + } + + base.Dispose(disposing); + } + + private void ComboBox_SelectionChangeCommitted(object sender, EventArgs e) + { + int index = this.comboBox.SelectedIndex; + + if (index >= 0 && index < this.comboBox.Items.Count) + { + OnItemClicked((Item)this.comboBox.Items[index]); + } + } + + private void ComboBox_DropDown(object sender, EventArgs e) + { + MenuStripEx.PushMenuActivate(); + } + + private void ComboBox_DropDownClosed(object sender, EventArgs e) + { + MenuStripEx.PopMenuActivate(); + this.comboBox.Items.Clear(); + OnClosed(); + } + + private void ComboBox_DrawItem(object sender, DrawItemEventArgs e) + { + if (e.Index == -1) + { + return; + } + + if (this.backBuffer != null && + (this.backBuffer.Width != e.Bounds.Width || this.backBuffer.Height != e.Bounds.Height)) + { + this.backBuffer.Dispose(); + this.backBuffer = null; + } + + if (this.backBuffer == null) + { + this.backBuffer = new Bitmap(e.Bounds.Width, e.Bounds.Height, PixelFormat.Format24bppRgb); + } + + Item item = (Item)this.comboBox.Items[e.Index]; + + if (item.Image.PixelFormat == PixelFormat.Undefined) + { + return; + } + + using (Graphics g = Graphics.FromImage(this.backBuffer)) + { + Brush backBrush; + Brush textBrush; + bool selected = (e.State & DrawItemState.Selected) != 0; + + if (selected) + { + backBrush = new SolidBrush(Color.FromArgb(128, SystemColors.Highlight)); + textBrush = SystemBrushes.HighlightText; + } + else + { + backBrush = (Brush)SystemBrushes.Window.Clone(); + textBrush = SystemBrushes.WindowText; + } + + Rectangle bounds = new Rectangle(0, 0, this.backBuffer.Width, this.backBuffer.Height); + + g.FillRectangle(SystemBrushes.Window, bounds); + + g.FillRectangle(backBrush, bounds); + + Rectangle imageRect = new Rectangle( + this.imageXInset + (this.maxImageSize.Width - item.Image.Width) / 2, + this.imageYInset + (this.maxImageSize.Height - item.Image.Height) / 2, + item.Image.Width, + item.Image.Height); + + g.DrawImage( + item.Image, + imageRect, + new Rectangle(0, 0, item.Image.Width, item.Image.Height), + GraphicsUnit.Pixel); + + Utility.DrawDropShadow1px(g, Rectangle.Inflate(imageRect, 1, 1)); + + SizeF textSizeF = e.Graphics.MeasureString(item.Name, Font, new PointF(0, 0), this.stringFormat); + Size textSize = Size.Ceiling(textSizeF); + + g.DrawString( + item.Name, + Font, + textBrush, + this.imageXInset + this.maxImageSize.Width + this.imageXInset + this.textLeftMargin, + (this.itemSize.Height - textSize.Height) / 2); + + backBrush.Dispose(); + backBrush = null; + } + + CompositingMode oldCM = e.Graphics.CompositingMode; + e.Graphics.CompositingMode = CompositingMode.SourceCopy; + + e.Graphics.DrawImage( + this.backBuffer, + e.Bounds, + new Rectangle(0, 0, this.backBuffer.Width, this.backBuffer.Height), + GraphicsUnit.Pixel); + + e.Graphics.CompositingMode = oldCM; + } + + private void ComboBox_MeasureItem(object sender, MeasureItemEventArgs e) + { + e.ItemWidth = this.itemSize.Width; + e.ItemHeight = this.itemSize.Height; + } + + protected override void OnLayout(LayoutEventArgs levent) + { + this.comboBox.Location = new Point(0, -this.comboBox.Height); + base.OnLayout(levent); + } + } +} diff --git a/src/Core/ImageStrip.cs b/src/Core/ImageStrip.cs new file mode 100644 index 0000000..c9fc6e5 --- /dev/null +++ b/src/Core/ImageStrip.cs @@ -0,0 +1,1464 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Text; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + /* + * Coordinate spaces: + * + * - Client -- This is the same as the Windows client space. + * - Screen -- This is the same as the Windows screen space. + * - View -- This is a virtual coordinate in the viewable, scrollable area. Client->View is defined as Client.X+ScrollOffset. View->Client is View.X-ScrollOffset + * - Item -- This is relative to an item. Item->View is Item.X+(itemIndex * ItemViewSize.Width) + * + * */ + + public class ImageStrip + : Control + { + public enum ItemPart + { + None, + Image, + CloseButton + } + + public sealed class Item + { + private PushButtonState imageRenderState; + private Image image; + + private bool selected; + + private PushButtonState checkRenderState; + private CheckState checkState; + + private PushButtonState closeRenderState; + + private bool dirty; + + private bool lockedDirtyValue; + private int dirtyValueLockCount = 0; + + private object tag; + + public event EventHandler Changed; + private void OnChanged() + { + if (Changed != null) + { + Changed(this, EventArgs.Empty); + } + } + + public Image Image + { + get + { + return this.image; + } + + set + { + this.image = value; + OnChanged(); + } + } + + public PushButtonState ImageRenderState + { + get + { + return this.imageRenderState; + } + + set + { + if (this.imageRenderState != value) + { + this.imageRenderState = value; + OnChanged(); + } + } + } + + public bool Selected + { + get + { + return this.selected; + } + + set + { + if (this.selected != value) + { + this.selected = value; + OnChanged(); + } + } + } + + public bool Dirty + { + get + { + if (this.dirtyValueLockCount > 0) + { + return this.lockedDirtyValue; + } + else + { + return this.dirty; + } + } + + set + { + if (this.dirty != value) + { + this.dirty = value; + + if (this.dirtyValueLockCount <= 0) + { + OnChanged(); + } + } + } + } + + public void LockDirtyValue(bool forceValue) + { + ++this.dirtyValueLockCount; + + if (this.dirtyValueLockCount == 1) + { + this.lockedDirtyValue = forceValue; + } + } + + public void UnlockDirtyValue() + { + --this.dirtyValueLockCount; + + if (this.dirtyValueLockCount == 0) + { + OnChanged(); + } + else if (this.dirtyValueLockCount < 0) + { + throw new InvalidOperationException("Calls to UnlockDirtyValue() must be matched by a preceding call to LockDirtyValue()"); + } + } + + public bool Checked + { + get + { + return (CheckState == CheckState.Checked); + } + + set + { + if (value) + { + CheckState = CheckState.Checked; + } + else + { + CheckState = CheckState.Unchecked; + } + } + } + + public CheckState CheckState + { + get + { + return this.checkState; + } + + set + { + if (this.checkState != value) + { + this.checkState = value; + OnChanged(); + } + } + } + + public PushButtonState CheckRenderState + { + get + { + return this.checkRenderState; + } + + set + { + if (this.checkRenderState != value) + { + this.checkRenderState = value; + OnChanged(); + } + } + } + + public PushButtonState CloseRenderState + { + get + { + return this.closeRenderState; + } + + set + { + if (this.closeRenderState != value) + { + this.closeRenderState = value; + OnChanged(); + } + } + } + + public void SetPartRenderState(ItemPart itemPart, PushButtonState renderState) + { + switch (itemPart) + { + case ItemPart.None: + break; + + case ItemPart.CloseButton: + CloseRenderState = renderState; + break; + + case ItemPart.Image: + ImageRenderState = renderState; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + + public object Tag + { + get + { + return this.tag; + } + + set + { + this.tag = value; + OnChanged(); + } + } + + public void Update() + { + OnChanged(); + } + + public Item() + { + } + + public Item(Image image) + { + this.image = image; + } + } + + private bool managedFocus = false; + private bool showScrollButtons = false; + private ArrowButton leftScrollButton; + private ArrowButton rightScrollButton; + + private int scrollOffset = 0; + private bool showCloseButtons = false; + private const int closeButtonLength = 13; + private int imagePadding = 2; + private int closeButtonPadding = 2; + + private int mouseOverIndex = -1; + private ItemPart mouseOverItemPart = ItemPart.None; + private bool mouseOverApplyRendering = false; + + private int mouseDownIndex = -1; + private MouseButtons mouseDownButton = MouseButtons.None; + private ItemPart mouseDownItemPart = ItemPart.None; + private bool mouseDownApplyRendering = false; + + private bool drawShadow = true; + private bool drawDirtyOverlay = true; + + public bool DrawShadow + { + get + { + return this.drawShadow; + } + + set + { + if (this.drawShadow != value) + { + this.drawShadow = value; + Refresh(); + } + } + } + + public bool DrawDirtyOverlay + { + get + { + return this.drawDirtyOverlay; + } + + set + { + if (this.drawDirtyOverlay != value) + { + this.drawDirtyOverlay = value; + Refresh(); + } + } + } + + // This is done as an optimization: otherwise we're getting flooded with MouseMove events + // and constantly refreshing our rendering. So CPU usage goes to heck. + private Point lastMouseMovePt = new Point(-32000, -32000); + + private List items = new List(); + + protected ArrowButton LeftScrollButton + { + get + { + return this.leftScrollButton; + } + } + + protected ArrowButton RightScrollButton + { + get + { + return this.rightScrollButton; + } + } + + private void MouseStatesToItemStates() + { + UI.SuspendControlPainting(this); + + for (int i = 0; i < this.items.Count; ++i) + { + this.items[i].CheckRenderState = PushButtonState.Normal; + this.items[i].CloseRenderState = PushButtonState.Normal; + this.items[i].ImageRenderState = PushButtonState.Normal; + this.items[i].Selected = false; + } + + if (this.mouseDownApplyRendering) + { + if (this.mouseDownIndex < 0 || this.mouseDownIndex >= this.items.Count) + { + this.mouseDownApplyRendering = false; + } + else + { + this.items[this.mouseDownIndex].SetPartRenderState(this.mouseDownItemPart, PushButtonState.Pressed); + this.items[this.mouseDownIndex].Selected = true; + } + } + else if (this.mouseOverApplyRendering) + { + if (this.mouseOverIndex < 0 || this.mouseOverIndex >= this.items.Count) + { + this.mouseOverApplyRendering = false; + } + else + { + this.items[this.mouseOverIndex].SetPartRenderState(this.mouseOverItemPart, PushButtonState.Hot); + this.items[this.mouseOverIndex].Selected = true; + } + } + + UI.ResumeControlPainting(this); + Invalidate(); + } + + public ImageStrip() + { + SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); + SetStyle(ControlStyles.Selectable, false); + + DoubleBuffered = true; + ResizeRedraw = true; + + InitializeComponent(); + } + + private void InitializeComponent() + { + this.leftScrollButton = new ArrowButton(); + this.rightScrollButton = new ArrowButton(); + SuspendLayout(); + // + // leftScrollButton + // + this.leftScrollButton.Name = "leftScrollButton"; + this.leftScrollButton.ArrowDirection = ArrowDirection.Left; + this.leftScrollButton.ArrowOutlineWidth = 1.0f; + this.leftScrollButton.Click += new EventHandler(LeftScrollButton_Click); + this.leftScrollButton.DrawWithGradient = true; + // + // rightScrollButton + // + this.rightScrollButton.Name = "rightScrollButton"; + this.rightScrollButton.ArrowDirection = ArrowDirection.Right; + this.rightScrollButton.ArrowOutlineWidth = 1.0f; + this.rightScrollButton.Click += new EventHandler(RightScrollButton_Click); + this.rightScrollButton.DrawWithGradient = true; + // + // ImageStrip + // + this.Name = "ImageStrip"; + this.TabStop = false; + this.Controls.Add(this.leftScrollButton); + this.Controls.Add(this.rightScrollButton); + ResumeLayout(); + PerformLayout(); + } + + public event EventHandler> ScrollArrowClicked; + protected virtual void OnScrollArrowClicked(ArrowDirection arrowDirection) + { + if (ScrollArrowClicked != null) + { + ScrollArrowClicked(this, new EventArgs(arrowDirection)); + } + } + + private void LeftScrollButton_Click(object sender, EventArgs e) + { + Focus(); + OnScrollArrowClicked(ArrowDirection.Left); + } + + private void RightScrollButton_Click(object sender, EventArgs e) + { + Focus(); + OnScrollArrowClicked(ArrowDirection.Right); + } + + /// + /// This event is raised when this control wishes to relinquish focus. + /// + public event EventHandler RelinquishFocus; + + private void OnRelinquishFocus() + { + if (RelinquishFocus != null) + { + RelinquishFocus(this, EventArgs.Empty); + } + } + + /// + /// Gets or sets whether the control manages focus. + /// + /// + /// If this is true, the toolstrip will capture focus when the mouse enters its client area. It will then + /// relinquish focus (via the RelinquishFocus event) when the mouse leaves. It will not capture or + /// attempt to relinquish focus if MenuStripEx.IsAnyMenuActive returns true. + /// + public bool ManagedFocus + { + get + { + return this.managedFocus; + } + + set + { + this.managedFocus = value; + } + } + + public void AddItem(Item newItem) + { + if (this.items.Contains(newItem)) + { + throw new ArgumentException("newItem was already added to this control"); + } + + newItem.Changed += Item_Changed; + this.items.Add(newItem); + + PerformLayout(); + Invalidate(); + } + + public void RemoveItem(Item item) + { + if (!this.items.Contains(item)) + { + throw new ArgumentException("item was never added to this control"); + } + + item.Changed -= Item_Changed; + this.items.Remove(item); + + PerformLayout(); + Invalidate(); + } + + public void ClearItems() + { + SuspendLayout(); + UI.SuspendControlPainting(this); + + while (this.items.Count > 0) + { + RemoveItem(this.items[this.items.Count - 1]); + } + + UI.ResumeControlPainting(this); + ResumeLayout(true); + + Invalidate(); + } + + private void Item_Changed(object sender, EventArgs e) + { + Invalidate(); + } + + /// + /// Raised when an item is clicked on. + /// + /// + /// e.Data.First is a reference to the Item. + /// e.Data.Second is the ItemPart. + /// e.Data.Third is the MouseButtons that was used to click on the ItemPart. + /// + public event EventHandler>> ItemClicked; + protected virtual void OnItemClicked(Item item, ItemPart itemPart, MouseButtons mouseButtons) + { + if (ItemClicked != null) + { + ItemClicked(this, new EventArgs>( + Triple.Create(item, itemPart, mouseButtons))); + } + } + + public void PerformItemClick(int itemIndex, ItemPart itemPart, MouseButtons mouseButtons) + { + PerformItemClick(this.items[itemIndex], itemPart, mouseButtons); + } + + public void PerformItemClick(Item item, ItemPart itemPart, MouseButtons mouseButtons) + { + OnItemClicked(item, itemPart, mouseButtons); + } + + public Item[] Items + { + get + { + return this.items.ToArray(); + } + } + + public int ItemCount + { + get + { + return this.items.Count; + } + } + + public bool ShowScrollButtons + { + get + { + return this.showScrollButtons; + } + + set + { + if (this.showScrollButtons != value) + { + this.showScrollButtons = value; + PerformLayout(); + Invalidate(true); + } + } + } + + public int MinScrollOffset + { + get + { + return 0; + } + } + + public int MaxScrollOffset + { + get + { + Size itemSize = ItemSize; + + int itemsLength = itemSize.Width * this.items.Count; + int viewLength = itemsLength - ClientSize.Width; + int maxScrollOffset = Math.Max(0, viewLength); + return maxScrollOffset; + } + } + + public int ScrollOffset + { + get + { + return this.scrollOffset; + } + + set + { + int clampedValue = Utility.Clamp(value, MinScrollOffset, MaxScrollOffset); + + if (this.scrollOffset != clampedValue) + { + this.scrollOffset = clampedValue; + OnScrollOffsetChanged(); + Invalidate(true); + } + } + } + + public event EventHandler ScrollOffsetChanged; + protected virtual void OnScrollOffsetChanged() + { + PerformLayout(); + + if (ScrollOffsetChanged != null) + { + ScrollOffsetChanged(this, EventArgs.Empty); + } + } + + /// + /// Gets the viewable area, in View coordinate space. + /// + public Rectangle ViewRectangle + { + get + { + Size itemSize = ItemSize; + return new Rectangle(0, 0, itemSize.Width * ItemCount, itemSize.Height); + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int arrowWidth = UI.ScaleWidth(16); + + ScrollOffset = Utility.Clamp(this.scrollOffset, MinScrollOffset, MaxScrollOffset); + + // Determine arrow visibility / position + this.leftScrollButton.Size = new Size(arrowWidth, ClientSize.Height); + this.leftScrollButton.Location = new Point(0, 0); + + this.rightScrollButton.Size = new Size(arrowWidth, ClientSize.Height); + this.rightScrollButton.Location = new Point(ClientSize.Width - this.rightScrollButton.Width, 0); + + bool showEitherButton = this.showScrollButtons && (this.ViewRectangle.Width > ClientRectangle.Width); + bool showRightButton = (this.scrollOffset < MaxScrollOffset) && showEitherButton; + bool showLeftButton = (this.scrollOffset > MinScrollOffset) && showEitherButton; + + this.rightScrollButton.Enabled = showRightButton; + this.rightScrollButton.Visible = showRightButton; + this.leftScrollButton.Enabled = showLeftButton; + this.leftScrollButton.Visible = showLeftButton; + + base.OnLayout(levent); + } + + public bool ShowCloseButtons + { + get + { + return this.showCloseButtons; + } + + set + { + if (this.showCloseButtons != value) + { + this.showCloseButtons = value; + PerformLayout(); + Invalidate(); + } + } + } + + public int PreferredMinClientWidth + { + get + { + if (this.items.Count == 0) + { + return 0; + } + + int minWidth = ItemSize.Width; + + if (this.leftScrollButton.Visible || this.rightScrollButton.Visible) + { + minWidth += this.leftScrollButton.Width; + minWidth += this.rightScrollButton.Width; + } + + minWidth = Math.Min(minWidth, ViewRectangle.Width); + + return minWidth; + } + } + + public Size PreferredImageSize + { + get + { + Rectangle itemRect; + Rectangle imageRect; + + MeasureItemPartRectangles(out itemRect, out imageRect); + return new Size(imageRect.Width - imagePadding * 2, imageRect.Height - imagePadding * 2); + } + } + + public Size ItemSize + { + get + { + Rectangle itemRect; + Rectangle imageRect; + + MeasureItemPartRectangles(out itemRect, out imageRect); + return itemRect.Size; + } + } + + protected virtual void DrawItemBackground(Graphics g, Item item, Rectangle itemRect) + { + } + + protected virtual void DrawItemHighlight( + Graphics g, + Item item, + Rectangle itemRect, + Rectangle highlightRect) + { + Color backFillColor; + Color outlineColor; + + if (item.Checked) + { + backFillColor = Color.FromArgb(192, SystemColors.Highlight); + outlineColor = backFillColor; + } + else if (item.Selected) + { + backFillColor = Color.FromArgb(64, SystemColors.HotTrack); + outlineColor = Color.FromArgb(64, SystemColors.HotTrack); + } + else + { + backFillColor = Color.Transparent; + outlineColor = Color.Transparent; + } + + using (SolidBrush backFillBrush = new SolidBrush(backFillColor)) + { + g.FillRectangle(backFillBrush, highlightRect); + } + + using (Pen outlinePen = new Pen(outlineColor)) + { + g.DrawRectangle(outlinePen, highlightRect.X, highlightRect.Y, highlightRect.Width - 1, highlightRect.Height - 1); + } + } + + protected virtual void DrawItemCloseButton( + Graphics g, + Item item, + Rectangle itemRect, + Rectangle closeButtonRect) + { + if (item.Checked && item.Selected) + { + const string resourceNamePrefix = "Images.ImageStrip.CloseButton."; + const string resourceNameSuffix = ".png"; + string resourceNameInfix = item.CloseRenderState.ToString(); + + string resourceName = resourceNamePrefix + resourceNameInfix + resourceNameSuffix; + + ImageResource imageResource = PdnResources.GetImageResource(resourceName); + Image image = imageResource.Reference; + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + + g.DrawImage(image, closeButtonRect, new Rectangle(0, 0, image.Width, image.Width), GraphicsUnit.Pixel); + } + } + + protected virtual void DrawItemDirtyOverlay( + Graphics g, + Item item, + Rectangle itemRect, + Rectangle dirtyOverlayRect) + { + Color outerPenColor = Color.White; + Color innerPenColor = Color.Orange; + + const int xInset = 2; + int scaledXInset = UI.ScaleWidth(xInset); + + const float outerPenWidth = 4.0f; + const float innerPenWidth = 2.0f; + + float scaledOuterPenWidth = UI.ScaleWidth(outerPenWidth); + float scaledInnerPenWidth = UI.ScaleWidth(innerPenWidth); + + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.AntiAlias; + + int left = dirtyOverlayRect.Left + scaledXInset; + int top = dirtyOverlayRect.Top + scaledXInset; + int right = dirtyOverlayRect.Right - scaledXInset; + int bottom = dirtyOverlayRect.Bottom - scaledXInset; + + float r = Math.Min((right - left) / 2.0f, (bottom - top) / 2.0f); + + PointF centerPt = new PointF((left + right) / 2.0f, (top + bottom) / 2.0f); + float twoPiOver5 = (float)(Math.PI * 0.4); + + PointF a = new PointF(centerPt.X + r * (float)Math.Sin(twoPiOver5), centerPt.Y - r * (float)Math.Cos(twoPiOver5)); + PointF b = new PointF(centerPt.X + r * (float)Math.Sin(2 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(2 * twoPiOver5)); + PointF c = new PointF(centerPt.X + r * (float)Math.Sin(3 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(3 * twoPiOver5)); + PointF d = new PointF(centerPt.X + r * (float)Math.Sin(4 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(4 * twoPiOver5)); + PointF e = new PointF(centerPt.X + r * (float)Math.Sin(5 * twoPiOver5), centerPt.Y - r * (float)Math.Cos(5 * twoPiOver5)); + + PointF[] lines = + new PointF[] + { + centerPt, a, + centerPt, b, + centerPt, c, + centerPt, d, + centerPt, e + }; + + using (Pen outerPen = new Pen(outerPenColor, scaledOuterPenWidth)) + { + for (int i = 0; i < lines.Length; i += 2) + { + g.DrawLine(outerPen, lines[i], lines[i + 1]); + } + } + + using (Pen innerPen = new Pen(innerPenColor, scaledInnerPenWidth)) + { + for (int i = 0; i < lines.Length; i += 2) + { + g.DrawLine(innerPen, lines[i], lines[i + 1]); + } + } + + g.SmoothingMode = oldSM; + } + + protected virtual void DrawItemImageShadow( + Graphics g, + Item item, + Rectangle itemRect, + Rectangle imageRect, + Rectangle imageInsetRect) + { + Rectangle shadowRect = Rectangle.Inflate(imageInsetRect, 1, 1); + Utility.DrawDropShadow1px(g, shadowRect); + } + + protected virtual void DrawItemImage( + Graphics g, + Item item, + Rectangle itemRect, + Rectangle imageRect, + Rectangle imageInsetRect) + { + // Draw the image + if (item.Image != null) + { + g.DrawImage( + item.Image, + imageInsetRect, + new Rectangle(0, 0, item.Image.Width, item.Image.Height), + GraphicsUnit.Pixel); + } + } + + private void DrawItem(Graphics g, Item item, Point offset) + { + Rectangle itemRect; + Rectangle imageRect; + Rectangle imageInsetRect; + Rectangle closeButtonRect; + Rectangle dirtyOverlayRect; + + MeasureItemPartRectangles( + item, + out itemRect, + out imageRect, + out imageInsetRect, + out closeButtonRect, + out dirtyOverlayRect); + + itemRect.X += offset.X; + itemRect.Y += offset.Y; + + imageRect.X += offset.X; + imageRect.Y += offset.Y; + + imageInsetRect.X += offset.X; + imageInsetRect.Y += offset.Y; + + closeButtonRect.X += offset.X; + closeButtonRect.Y += offset.Y; + + dirtyOverlayRect.X += offset.X; + dirtyOverlayRect.Y += offset.Y; + + DrawItemBackground(g, item, itemRect); + + Rectangle highlightRect = itemRect; + DrawItemHighlight(g, item, itemRect, highlightRect); + + // Fill background and draw outline + if (this.drawShadow) + { + DrawItemImageShadow(g, item, itemRect, imageRect, imageInsetRect); + } + + DrawItemImage(g, item, itemRect, imageRect, imageInsetRect); + + if (this.showCloseButtons) + { + DrawItemCloseButton(g, item, itemRect, closeButtonRect); + } + + if (this.drawDirtyOverlay && item.Dirty) + { + DrawItemDirtyOverlay(g, item, itemRect, dirtyOverlayRect); + } + } + + public Point ClientPointToViewPoint(Point clientPt) + { + int viewX = clientPt.X + this.scrollOffset; + return new Point(viewX, clientPt.Y); + } + + public Rectangle ClientRectangleToViewRectangle(Rectangle clientRect) + { + Point viewPt = ClientPointToViewPoint(clientRect.Location); + return new Rectangle(viewPt, clientRect.Size); + } + + public Point ViewPointToClientPoint(Point viewPt) + { + int clientX = viewPt.X - this.scrollOffset; + return new Point(clientX, viewPt.Y); + } + + public Rectangle ViewRectangleToClientRectangle(Rectangle viewRect) + { + Point clientPt = ViewPointToClientPoint(viewRect.Location); + return new Rectangle(clientPt, viewRect.Size); + } + + private Point ViewPointToItemPoint(int itemIndex, Point viewPt) + { + Rectangle itemRect = ItemIndexToItemViewRectangle(itemIndex); + Point itemPt = new Point(viewPt.X - itemRect.X, viewPt.Y); + return itemPt; + } + + private Rectangle ItemIndexToItemViewRectangle(int itemIndex) + { + Size itemSize = ItemSize; + return new Rectangle(itemSize.Width * itemIndex, itemSize.Height, itemSize.Width, itemSize.Height); + } + + public int ViewPointToItemIndex(Point viewPt) + { + if (!ViewRectangle.Contains(viewPt)) + { + return -1; + } + + Size itemSize = ItemSize; + int index = viewPt.X / itemSize.Width; + + return index; + } + + private void MeasureItemPartRectangles( + out Rectangle itemRect, + out Rectangle imageRect) + { + itemRect = new Rectangle( + 0, + 0, + ClientSize.Height, + ClientSize.Height); + + imageRect = new Rectangle( + itemRect.Left, + itemRect.Top, + itemRect.Width, + itemRect.Width); + } + + private void MeasureItemPartRectangles( + Item item, + out Rectangle itemRect, + out Rectangle imageRect, + out Rectangle imageInsetRect, + out Rectangle closeButtonRect, + out Rectangle dirtyOverlayRect) + { + MeasureItemPartRectangles(out itemRect, out imageRect); + + Rectangle imageInsetRectMax = new Rectangle( + imageRect.Left + imagePadding, + imageRect.Top + imagePadding, + imageRect.Width - imagePadding * 2, + imageRect.Height - imagePadding * 2); + + Size imageInsetSize; + + if (item.Image == null) + { + imageInsetSize = imageRect.Size; + } + else + { + imageInsetSize = Utility.ComputeThumbnailSize(item.Image.Size, imageInsetRectMax.Width); + } + + int scaledCloseButtonLength = UI.ScaleWidth(closeButtonLength); + int scaledCloseButtonPadding = UI.ScaleWidth(closeButtonPadding); + + imageInsetRect = new Rectangle( + imageInsetRectMax.Left + (imageInsetRectMax.Width - imageInsetSize.Width) / 2, + imageInsetRectMax.Bottom - imageInsetSize.Height, + imageInsetSize.Width, + imageInsetSize.Height); + + closeButtonRect = new Rectangle( + imageInsetRectMax.Right - scaledCloseButtonLength - scaledCloseButtonPadding, + imageInsetRectMax.Top + scaledCloseButtonPadding, + scaledCloseButtonLength, + scaledCloseButtonLength); + + dirtyOverlayRect = new Rectangle( + imageInsetRectMax.Left + scaledCloseButtonPadding, + imageInsetRectMax.Top + scaledCloseButtonPadding, + scaledCloseButtonLength, + scaledCloseButtonLength); + } + + private ItemPart ItemPointToItemPart(Item item, Point pt) + { + Rectangle itemRect; + Rectangle imageRect; + Rectangle imageInsetRect; + Rectangle closeButtonRect; + Rectangle dirtyOverlayRect; + + MeasureItemPartRectangles( + item, + out itemRect, + out imageRect, + out imageInsetRect, + out closeButtonRect, + out dirtyOverlayRect); + + if (closeButtonRect.Contains(pt)) + { + return ItemPart.CloseButton; + } + + if (imageRect.Contains(pt)) + { + return ItemPart.Image; + } + + return ItemPart.None; + } + + private Rectangle ItemIndexToClientRect(int itemIndex) + { + Size itemSize = ItemSize; + + Rectangle clientRect = new Rectangle( + itemSize.Width * itemIndex, + 0, + itemSize.Width, + itemSize.Height); + + return clientRect; + } + + private void CalculateVisibleScrollOffsets( + int itemIndex, + out int minOffset, + out int maxOffset, + out int minFullyShownOffset, + out int maxFullyShownOffset) + { + Rectangle itemClientRect = ItemIndexToClientRect(itemIndex); + + minOffset = itemClientRect.Left + 1 - ClientSize.Width; + maxOffset = itemClientRect.Right - 1; + minFullyShownOffset = itemClientRect.Right - ClientSize.Width; + maxFullyShownOffset = itemClientRect.Left; + + if (this.leftScrollButton.Visible) + { + maxOffset -= this.leftScrollButton.Width; + maxFullyShownOffset -= this.leftScrollButton.Width; + } + + if (this.rightScrollButton.Visible) + { + minOffset += this.rightScrollButton.Width; + minFullyShownOffset += this.rightScrollButton.Width; + } + } + + public Rectangle ScrolledViewRect + { + get + { + return new Rectangle(this.scrollOffset, 0, ClientSize.Width, ClientSize.Height); + } + } + + public bool IsItemVisible(int index) + { + Rectangle itemRect = ItemIndexToClientRect(index); + Rectangle intersect = Rectangle.Intersect(itemRect, ScrolledViewRect); + return (intersect.Width > 0 || intersect.Height > 0); + } + + public bool IsItemFullyVisible(int index) + { + Rectangle itemRect = ItemIndexToClientRect(index); + Rectangle svRect = ScrolledViewRect; + + if (this.leftScrollButton.Visible) + { + svRect.X += this.leftScrollButton.Width; + svRect.Width -= this.leftScrollButton.Width; + } + + if (this.rightScrollButton.Visible) + { + svRect.Width -= this.rightScrollButton.Width; + } + + Rectangle intersect = Rectangle.Intersect(itemRect, svRect); + return (intersect == itemRect); + } + + public void EnsureItemFullyVisible(Item item) + { + int index = this.items.IndexOf(item); + EnsureItemFullyVisible(index); + } + + public void EnsureItemFullyVisible(int index) + { + if (IsItemFullyVisible(index)) + { + return; + } + + int minOffset; + int maxOffset; + int minFullyShownOffset; + int maxFullyShownOffset; + + CalculateVisibleScrollOffsets(index, out minOffset, out maxOffset, + out minFullyShownOffset, out maxFullyShownOffset); + + // Pick the offset that moves the image the fewest number of pixels + int oldOffset = this.scrollOffset; + int dxMin = Math.Abs(oldOffset - minFullyShownOffset); + int dxMax = Math.Abs(oldOffset - maxFullyShownOffset); + + if (dxMin <= dxMax) + { + this.ScrollOffset = minFullyShownOffset; + } + else + { + this.ScrollOffset = maxFullyShownOffset; + } + } + + protected override void OnPaint(PaintEventArgs e) + { + if (UI.IsControlPaintingEnabled(this)) + { + Size itemSize = ItemSize; + Rectangle firstItemRect = new Rectangle(-this.scrollOffset, 0, itemSize.Width, itemSize.Height); + + for (int i = 0; i < this.items.Count; ++i) + { + if (IsItemVisible(i)) + { + Point itemOffset = new Point(firstItemRect.X + itemSize.Width * i, firstItemRect.Y); + DrawItem(e.Graphics, this.items[i], itemOffset); + } + } + } + + base.OnPaint(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (this.mouseDownButton == MouseButtons.None) + { + Point clientPt = new Point(e.X, e.Y); + Point viewPt = ClientPointToViewPoint(clientPt); + int itemIndex = ViewPointToItemIndex(viewPt); + + if (itemIndex >= 0 && itemIndex < this.items.Count) + { + Item item = this.items[itemIndex]; + Point itemPt = ViewPointToItemPoint(itemIndex, viewPt); + ItemPart itemPart = ItemPointToItemPart(item, itemPt); + + if (itemPart == ItemPart.Image) + { + OnItemClicked(item, itemPart, e.Button); + + this.mouseDownApplyRendering = false; + this.mouseOverIndex = itemIndex; + this.mouseOverItemPart = itemPart; + this.mouseOverApplyRendering = true; + } + else + { + this.mouseDownIndex = itemIndex; + this.mouseDownItemPart = itemPart; + this.mouseDownButton = e.Button; + this.mouseDownApplyRendering = true; + this.mouseOverApplyRendering = false; + } + + MouseStatesToItemStates(); + Refresh(); + } + } + + base.OnMouseDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + GetFocus(); + + Point clientPt = new Point(e.X, e.Y); + + if (clientPt != this.lastMouseMovePt) + { + Point viewPt = ClientPointToViewPoint(clientPt); + int itemIndex = ViewPointToItemIndex(viewPt); + + if (this.mouseDownButton == MouseButtons.None) + { + if (itemIndex >= 0 && itemIndex < this.items.Count) + { + Item item = this.items[itemIndex]; + Point itemPt = ViewPointToItemPoint(itemIndex, viewPt); + ItemPart itemPart = ItemPointToItemPart(item, itemPt); + + this.mouseOverIndex = itemIndex; + this.mouseOverItemPart = itemPart; + this.mouseOverApplyRendering = true; + } + else + { + this.mouseOverApplyRendering = false; + } + } + else + { + this.mouseOverApplyRendering = false; + + if (itemIndex != this.mouseDownIndex) + { + this.mouseDownApplyRendering = false; + } + else if (itemIndex < 0 || itemIndex >= this.items.Count) + { + this.mouseDownApplyRendering = false; + } + else + { + Item item = this.Items[itemIndex]; + Point itemPt = ViewPointToItemPoint(itemIndex, viewPt); + + ItemPart itemPart = ItemPointToItemPart(item, itemPt); + + if (itemPart != this.mouseDownItemPart) + { + this.mouseDownApplyRendering = false; + } + } + } + + MouseStatesToItemStates(); + Refresh(); + } + + this.lastMouseMovePt = clientPt; + base.OnMouseMove(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + bool raisedClickEvent = false; + + if (this.mouseDownButton == e.Button) + { + Point clientPt = new Point(e.X, e.Y); + Point viewPt = ClientPointToViewPoint(clientPt); + int itemIndex = ViewPointToItemIndex(viewPt); + + if (itemIndex >= 0 && itemIndex < this.items.Count) + { + Item item = this.items[itemIndex]; + Point itemPt = ViewPointToItemPoint(itemIndex, viewPt); + ItemPart itemPart = ItemPointToItemPart(item, itemPt); + + if (itemIndex == this.mouseDownIndex && itemPart == this.mouseDownItemPart) + { + if (itemPart == ItemPart.CloseButton && !item.Checked) + { + // Can only close 'checked' images, just like how tab switching+closing works in IE7 + itemPart = ItemPart.Image; + } + + OnItemClicked(item, itemPart, this.mouseDownButton); + raisedClickEvent = true; + } + + this.mouseOverApplyRendering = true; + this.mouseOverItemPart = itemPart; + this.mouseOverIndex = itemIndex; + } + + this.mouseDownApplyRendering = false; + this.mouseDownButton = MouseButtons.None; + + MouseStatesToItemStates(); + Refresh(); + } + + if (raisedClickEvent) + { + ForceMouseMove(); + } + + base.OnMouseUp(e); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + float count = (float)e.Delta / SystemInformation.MouseWheelScrollDelta; + int pixels = (int)(count * ItemSize.Width); + int newSO = ScrollOffset - pixels; + ScrollOffset = newSO; + + ForceMouseMove(); + + base.OnMouseWheel(e); + } + + private void ForceMouseMove() + { + Point clientPt = PointToClient(Control.MousePosition); + this.lastMouseMovePt = new Point(this.lastMouseMovePt.X + 1, this.lastMouseMovePt.Y + 1); + MouseEventArgs me = new MouseEventArgs(MouseButtons.None, 0, clientPt.X, clientPt.Y, 0); + OnMouseMove(me); + } + + protected override void OnMouseEnter(EventArgs e) + { + GetFocus(); + base.OnMouseEnter(e); + } + + private void GetFocus() + { + if (this.managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive) + { + this.Focus(); + } + } + + protected override void OnMouseLeave(EventArgs e) + { + this.mouseDownApplyRendering = false; + this.mouseOverApplyRendering = false; + + MouseStatesToItemStates(); + Refresh(); + + if (this.managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive) + { + OnRelinquishFocus(); + } + + base.OnMouseLeave(e); + } + } +} diff --git a/src/Core/IndexEventArgs.cs b/src/Core/IndexEventArgs.cs new file mode 100644 index 0000000..634e5bd --- /dev/null +++ b/src/Core/IndexEventArgs.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Declares an EventArgs type for an event that needs a single integer, interpreted + /// as an index, as event information. + /// + public sealed class IndexEventArgs + : EventArgs + { + int index; + + public int Index + { + get + { + return index; + } + } + + public IndexEventArgs(int i) + { + this.index = i; + } + } +} diff --git a/src/Core/IndexEventHandler.cs b/src/Core/IndexEventHandler.cs new file mode 100644 index 0000000..d522824 --- /dev/null +++ b/src/Core/IndexEventHandler.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Declares a delegate type for an event that needs a single integer, interpreted + /// as an index, as event information. + /// + public delegate void IndexEventHandler(object sender, IndexEventArgs ce); +} \ No newline at end of file diff --git a/src/Core/IndirectUI/AngleChooserPropertyControl.cs b/src/Core/IndirectUI/AngleChooserPropertyControl.cs new file mode 100644 index 0000000..8101e1b --- /dev/null +++ b/src/Core/IndirectUI/AngleChooserPropertyControl.cs @@ -0,0 +1,246 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Core; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(DoubleProperty), PropertyControlType.AngleChooser)] + internal sealed class AngleChooserPropertyControl + : PropertyControl + { + private HeaderLabel header; + private AngleChooserControl angleChooser; + private NumericUpDown valueNud; + private Button resetButton; + private Label description; + + [PropertyControlProperty(DefaultValue = (object)true)] + public bool ShowResetButton + { + get + { + return this.resetButton.Visible; + } + + set + { + this.resetButton.Visible = value; + PerformLayout(); + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.angleChooser.Enabled = !Property.ReadOnly; + this.valueNud.Enabled = !Property.ReadOnly; + this.resetButton.Enabled = !Property.ReadOnly; + this.description.Enabled = !Property.ReadOnly; + } + + private double FromAngleChooserValue(double angleChooserValue) + { + if (this.Property.MinValue == -180) + { + // property value's range is [-180, +180] + return angleChooserValue; + } + else + { + // property value's range is [0, 360] + if (angleChooserValue > 0) + { + return angleChooserValue; + } + else + { + return angleChooserValue + 360; + } + } + } + + private double ToAngleChooserValue(double nudValue) + { + if (this.Property.MinValue == -180) + { + // property value's range is [-180, +180] + return nudValue; + } + else + { + // property value's range is [0, 360] + if (nudValue <= 180.0) + { + return nudValue; + } + else + { + return nudValue - 360; + } + } + } + + protected override void OnPropertyValueChanged() + { + if (this.angleChooser.ValueDouble != ToAngleChooserValue(Property.Value)) + { + this.angleChooser.ValueDouble = ToAngleChooserValue(Property.Value); + } + + if (this.valueNud.Value != (decimal)Property.Value) + { + this.valueNud.Value = (decimal)Property.Value; + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vMargin = UI.ScaleHeight(4); + int hMargin = UI.ScaleWidth(4); + + this.header.Location = new Point(0, 0); + this.header.Size = string.IsNullOrEmpty(DisplayName) ? new Size(ClientSize.Width, 0) : + this.header.GetPreferredSize(new Size(ClientSize.Width, 1)); + this.header.Visible = !string.IsNullOrEmpty(DisplayName); + + this.resetButton.Width = UI.ScaleWidth(20); + this.resetButton.Location = new Point( + ClientSize.Width - this.resetButton.Width, + this.header.Bottom + vMargin); + + int baseNudWidth = UI.ScaleWidth(70); + this.valueNud.PerformLayout(); + this.valueNud.Width = baseNudWidth; + this.valueNud.Location = new Point( + this.resetButton.Left - hMargin - this.valueNud.Width, + this.header.Bottom + vMargin); + + this.resetButton.Height = this.valueNud.Height; + + this.angleChooser.Size = UI.ScaleSize(new Size(60, 60)); + int angleChooserMinLeft = hMargin; + int angleChooserMaxRight = this.valueNud.Left - hMargin; + double angleChooserCenter = (double)(angleChooserMinLeft + angleChooserMaxRight) / 2.0; + int angleChooserLeft = (int)(angleChooserCenter - ((double)this.angleChooser.Width / 2.0)); + this.angleChooser.Location = new Point(angleChooserLeft, this.header.Bottom + vMargin); + + this.description.Location = new Point(0, Math.Max(this.valueNud.Bottom, Math.Max(this.resetButton.Bottom, this.angleChooser.Bottom))); + this.description.Width = ClientSize.Width; + this.description.Height = string.IsNullOrEmpty(this.description.Text) ? 0 : + this.description.GetPreferredSize(new Size(this.description.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, description.Bottom); + + base.OnLayout(levent); + } + + protected override void OnDisplayNameChanged() + { + this.header.Text = this.DisplayName; + base.OnDisplayNameChanged(); + } + + protected override void OnDescriptionChanged() + { + this.description.Text = this.Description; + base.OnDescriptionChanged(); + } + + public AngleChooserPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + DoubleProperty doubleProp = (DoubleProperty)propInfo.Property; + if (!((doubleProp.MinValue == -180 && doubleProp.MaxValue == +180) || + (doubleProp.MinValue == 0 && doubleProp.MaxValue == 360))) + { + throw new ArgumentException("Only two min/max ranges are allowed for the AngleChooser control type: [-180, +180] and [0, 360]"); + } + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.angleChooser = new AngleChooserControl(); + this.angleChooser.Name = "angleChooser"; + this.angleChooser.ValueChanged += new EventHandler(AngleChooser_ValueChanged); + + this.valueNud = new NumericUpDown(); + this.valueNud.Name = "numericUpDown"; + this.valueNud.Minimum = (decimal)Property.MinValue; + this.valueNud.Maximum = (decimal)Property.MaxValue; + this.valueNud.DecimalPlaces = 2; + this.valueNud.ValueChanged += new EventHandler(ValueNud_ValueChanged); + this.valueNud.TextAlign = HorizontalAlignment.Right; + + this.resetButton = new Button(); + this.resetButton.Name = "resetButton"; + this.resetButton.FlatStyle = FlatStyle.Standard; + this.resetButton.Click += new EventHandler(ResetButton_Click); + this.resetButton.Image = PdnResources.GetImageResource("Icons.ResetIcon.png").Reference; + this.resetButton.Visible = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.ShowResetButton].Value; + this.ToolTip.SetToolTip(this.resetButton, PdnResources.GetString("Form.ResetButton.Text").Replace("&", "")); + + this.description = new Label(); + this.description.Name = "descriptionText"; + this.description.AutoSize = false; + this.description.Text = this.Description; + + SuspendLayout(); + + this.Controls.AddRange( + new Control[] + { + this.header, + this.angleChooser, + this.valueNud, + this.resetButton, + this.description + }); + + ResumeLayout(false); + PerformLayout(); + } + + private void AngleChooser_ValueChanged(object sender, EventArgs e) + { + if (Property.Value != FromAngleChooserValue(this.angleChooser.ValueDouble)) + { + Property.Value = FromAngleChooserValue(this.angleChooser.ValueDouble); + } + } + + private void ValueNud_ValueChanged(object sender, EventArgs e) + { + if (Property.Value != (double)this.valueNud.Value) + { + Property.Value = (double)this.valueNud.Value; + } + } + + private void ResetButton_Click(object sender, EventArgs e) + { + Property.Value = (double)Property.DefaultValue; + } + + protected override bool OnFirstSelect() + { + this.valueNud.Select(); + return true; + } + } +} diff --git a/src/Core/IndirectUI/BooleanCheckBoxPropertyControl.cs b/src/Core/IndirectUI/BooleanCheckBoxPropertyControl.cs new file mode 100644 index 0000000..0705291 --- /dev/null +++ b/src/Core/IndirectUI/BooleanCheckBoxPropertyControl.cs @@ -0,0 +1,114 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Core; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(BooleanProperty), PropertyControlType.CheckBox)] + internal sealed class BooleanCheckBoxPropertyControl + : PropertyControl + { + private HeaderLabel header; + private CheckBox checkBox; + + protected override void OnDisplayNameChanged() + { + this.header.Text = this.DisplayName; + this.checkBox.Text = string.IsNullOrEmpty(this.Description) ? this.DisplayName : this.Description; + base.OnDisplayNameChanged(); + } + + protected override void OnDescriptionChanged() + { + this.checkBox.Text = string.IsNullOrEmpty(this.Description) ? this.DisplayName : this.Description; + base.OnDescriptionChanged(); + } + + public BooleanCheckBoxPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.checkBox = new CheckBox(); + this.checkBox.Name = "checkBox"; + this.checkBox.CheckedChanged += new EventHandler(CheckBox_CheckedChanged); + this.checkBox.FlatStyle = FlatStyle.System; + this.checkBox.Text = string.IsNullOrEmpty(this.Description) ? this.DisplayName : this.Description; + + this.Controls.AddRange( + new Control[] + { + this.header, + this.checkBox + }); + + ResumeLayout(false); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vSpacing = UI.ScaleHeight(4); + + this.header.Location = new Point(0, 0); + this.header.Width = ClientSize.Width; + this.header.Height = string.IsNullOrEmpty(this.header.Text) ? 0 : + this.header.GetPreferredSize(new Size(ClientSize.Width, 1)).Height; + + this.checkBox.Location = new Point(0, this.header.Bottom + vSpacing); + + LayoutUtility.PerformAutoLayout( + this.checkBox, + AutoSizeStrategy.ExpandHeightToContentAndKeepWidth, + EdgeSnapOptions.SnapLeftEdgeToContainerLeftEdge | + EdgeSnapOptions.SnapRightEdgeToContainerRightEdge); + + ClientSize = new Size(ClientSize.Width, this.checkBox.Bottom); + + base.OnLayout(levent); + } + + private void CheckBox_CheckedChanged(object sender, EventArgs e) + { + if (Property.Value != this.checkBox.Checked) + { + Property.Value = this.checkBox.Checked; + } + } + + protected override void OnPropertyValueChanged() + { + this.checkBox.Checked = this.Property.Value; + } + + protected override void OnPropertyReadOnlyChanged() + { + this.checkBox.Enabled = !Property.ReadOnly; + } + + protected override bool OnFirstSelect() + { + this.checkBox.Select(); + return true; + } + } +} \ No newline at end of file diff --git a/src/Core/IndirectUI/ControlInfo.cs b/src/Core/IndirectUI/ControlInfo.cs new file mode 100644 index 0000000..71fec09 --- /dev/null +++ b/src/Core/IndirectUI/ControlInfo.cs @@ -0,0 +1,165 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + public abstract class ControlInfo + : ICloneable + { + private PropertyCollection controlProperties; + private List childControls = new List(); + + public PropertyCollection ControlProperties + { + get + { + return this.controlProperties; + } + + protected set + { + this.controlProperties = value.Clone(); + } + } + + public IList ChildControls + { + get + { + return new List(this.childControls); + } + } + + private static PropertyControlInfo FindControlForPropertyName(object propertyName, ControlInfo control) + { + PropertyControlInfo asPCI = control as PropertyControlInfo; + + if (asPCI != null && asPCI.Property.Name == propertyName.ToString()) + { + return asPCI; + } + else + { + foreach (ControlInfo childControl in control.GetChildControlsCore()) + { + PropertyControlInfo pci = FindControlForPropertyName(propertyName, childControl); + + if (pci != null) + { + return pci; + } + } + } + + return null; + } + + public PropertyControlInfo FindControlForPropertyName(object propertyName) + { + return FindControlForPropertyName(propertyName, this); + } + + public bool SetPropertyControlValue(object propertyName, object controlPropertyName, object propertyValue) + { + PropertyControlInfo pci = FindControlForPropertyName(propertyName); + + if (pci == null) + { + return false; + } + + Property prop = pci.ControlProperties[controlPropertyName]; + + if (prop == null) + { + return false; + } + + prop.Value = propertyValue; + return true; + } + + public bool SetPropertyControlType(object propertyName, PropertyControlType newControlType) + { + PropertyControlInfo pci = FindControlForPropertyName(propertyName); + + if (pci == null) + { + return false; + } + + if (-1 == Array.IndexOf(pci.ControlType.ValueChoices, newControlType)) + { + return false; + } + + pci.ControlType.Value = newControlType; + return true; + } + + protected List GetChildControlsCore() + { + return this.childControls; + } + + internal ControlInfo() + { + this.controlProperties = new PropertyCollection(new Property[0], new PropertyCollectionRule[0]); + } + + internal ControlInfo(PropertyCollection controlProperties) + { + this.controlProperties = controlProperties.Clone(); + } + + internal ControlInfo(ControlInfo cloneMe) + { + this.controlProperties = cloneMe.controlProperties.Clone(); + this.childControls = new List(cloneMe.childControls.Count); + + foreach (ControlInfo ci in cloneMe.childControls) + { + ControlInfo ciClone = ci.Clone(); + this.childControls.Add(ciClone); + } + } + + public object CreateConcreteControl(object uiContainer) + { + return CreateConcreteControl(uiContainer.GetType()); + } + + public object CreateConcreteControl(Type uiContainerType) + { + if (typeof(System.Windows.Forms.Control).IsAssignableFrom(uiContainerType)) + { + return CreateWinFormsControl(); + } + else + { + throw new ArgumentException("uiContainerType is not from a supported UI technology"); + } + } + + internal abstract Control CreateWinFormsControl(); + + public abstract ControlInfo Clone(); + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/Core/IndirectUI/ControlInfoPropertyNames.cs b/src/Core/IndirectUI/ControlInfoPropertyNames.cs new file mode 100644 index 0000000..fbcb595 --- /dev/null +++ b/src/Core/IndirectUI/ControlInfoPropertyNames.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.IndirectUI +{ + public enum ControlInfoPropertyNames + { + DisplayName = 0, + Description = 1, + ControlType = 2, + ButtonText = 3, + UseExponentialScale = 4, + DecimalPlaces = 5, + SliderSmallChange = 6, + SliderSmallChangeX = 7, + SliderSmallChangeY = 8, + SliderLargeChange = 9, + SliderLargeChangeX = 10, + SliderLargeChangeY = 11, + UpDownIncrement = 12, + UpDownIncrementX = 13, + UpDownIncrementY = 14, + StaticImageUnderlay = 15, + Multiline = 16, + ShowResetButton = 17, + SliderShowTickMarks = 18, + SliderShowTickMarksX = 19, + SliderShowTickMarksY = 20, + + WindowTitle = 21, + WindowWidthScale = 22, + WindowIsSizable = 23 + } +} diff --git a/src/Core/IndirectUI/DoubleSliderPropertyControl.cs b/src/Core/IndirectUI/DoubleSliderPropertyControl.cs new file mode 100644 index 0000000..2b8491e --- /dev/null +++ b/src/Core/IndirectUI/DoubleSliderPropertyControl.cs @@ -0,0 +1,148 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Core; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(DoubleProperty), PropertyControlType.Slider, IsDefault = true)] + internal sealed class DoubleSliderPropertyControl + : SliderPropertyControl + { + private bool useExponentialScale = false; + + [PropertyControlProperty(DefaultValue = (object)2)] + public new int DecimalPlaces + { + get + { + return base.DecimalPlaces; + } + + set + { + base.DecimalPlaces = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool UseExponentialScale + { + get + { + return this.useExponentialScale; + } + + set + { + this.useExponentialScale = value; + base.SliderShowTickMarks &= value; + ResetUIRanges(); + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public new double SliderSmallChange + { + get + { + return base.SliderSmallChange; + } + + set + { + base.SliderSmallChange = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)5.0)] + public new double SliderLargeChange + { + get + { + return base.SliderLargeChange; + } + + set + { + base.SliderLargeChange = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public new double UpDownIncrement + { + get + { + return base.UpDownIncrement; + } + + set + { + base.UpDownIncrement = value; + } + } + + public DoubleSliderPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + DecimalPlaces = (int)propInfo.ControlProperties[ControlInfoPropertyNames.DecimalPlaces].Value; + UseExponentialScale = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.UseExponentialScale].Value; + SliderSmallChange = (double)propInfo.ControlProperties[ControlInfoPropertyNames.SliderSmallChange].Value; + SliderLargeChange = (double)propInfo.ControlProperties[ControlInfoPropertyNames.SliderLargeChange].Value; + UpDownIncrement = (double)propInfo.ControlProperties[ControlInfoPropertyNames.UpDownIncrement].Value; + ResumeLayout(false); + } + + protected override int ToSliderValue(double propertyValue) + { + if (this.useExponentialScale) + { + return PropertyControlUtil.ToSliderValueExp(propertyValue, Property.MinValue, Property.MaxValue, DecimalPlaces); + } + else + { + double toIntScale = Math.Pow(10, DecimalPlaces); + double sliderValueDouble = propertyValue * toIntScale; + int sliderValueInt = (int)sliderValueDouble; + return sliderValueInt; + } + } + + protected override double FromSliderValue(int sliderValue) + { + if (this.useExponentialScale) + { + return PropertyControlUtil.FromSliderValueExp(sliderValue, Property.MinValue, Property.MaxValue, DecimalPlaces); + } + else + { + double fromIntScale = Math.Pow(10, -DecimalPlaces); + double valueDouble = (double)sliderValue * fromIntScale; + return valueDouble; + } + } + + protected override decimal ToNudValue(double propertyValue) + { + return (decimal)propertyValue; + } + + protected override double FromNudValue(decimal nudValue) + { + return (double)nudValue; + } + } +} diff --git a/src/Core/IndirectUI/DoubleVectorPanAndSliderPropertyControl.cs b/src/Core/IndirectUI/DoubleVectorPanAndSliderPropertyControl.cs new file mode 100644 index 0000000..0d82f4d --- /dev/null +++ b/src/Core/IndirectUI/DoubleVectorPanAndSliderPropertyControl.cs @@ -0,0 +1,335 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(DoubleVectorProperty), PropertyControlType.PanAndSlider, IsDefault = true)] + internal sealed class DoubleVectorPanAndSliderPropertyControl + : PropertyControl, VectorProperty> + { + private HeaderLabel header; + private PaintDotNet.Core.PanControl panControl; + private DoubleVectorSliderPropertyControl sliders; + private Label textDescription; + + [PropertyControlProperty(DefaultValue = (object)true)] + public bool ShowResetButton + { + get + { + return this.sliders.ShowResetButton; + } + + set + { + this.sliders.ShowResetButton = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)2)] + public int DecimalPlaces + { + get + { + return this.sliders.DecimalPlaces; + } + + set + { + this.sliders.DecimalPlaces = value; + } + } + + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool UseExponentialScale + { + get + { + return this.sliders.UseExponentialScale; + } + + set + { + this.sliders.UseExponentialScale = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public double SliderSmallChangeX + { + get + { + return this.sliders.SliderSmallChangeX; + } + + set + { + this.sliders.SliderSmallChangeX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)5.0)] + public double SliderLargeChangeX + { + get + { + return this.sliders.SliderLargeChangeX; + } + + set + { + this.sliders.SliderLargeChangeX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public double UpDownIncrementX + { + get + { + return this.sliders.UpDownIncrementX; + } + + set + { + this.sliders.UpDownIncrementX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public double SliderSmallChangeY + { + get + { + return this.sliders.SliderSmallChangeY; + } + + set + { + this.sliders.SliderSmallChangeY = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)5.0)] + public double SliderLargeChangeY + { + get + { + return this.sliders.SliderLargeChangeY; + } + + set + { + this.sliders.SliderLargeChangeY = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public double UpDownIncrementY + { + get + { + return this.sliders.UpDownIncrementY; + } + + set + { + this.sliders.UpDownIncrementY = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool SliderShowTickMarksX + { + get + { + return this.sliders.SliderShowTickMarksX; + } + + set + { + this.sliders.SliderShowTickMarksX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool SliderShowTickMarksY + { + get + { + return this.sliders.SliderShowTickMarksY; + } + + set + { + this.sliders.SliderShowTickMarksY = value; + } + } + + [PropertyControlProperty(DefaultValue = null)] + public ImageResource StaticImageUnderlay + { + get + { + return this.panControl.StaticImageUnderlay; + } + + set + { + this.panControl.StaticImageUnderlay = value; + + if (value == null) + { + this.panControl.BorderStyle = BorderStyle.FixedSingle; + } + else + { + this.panControl.BorderStyle = BorderStyle.None; + } + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.panControl.Enabled = !this.Property.ReadOnly; + this.textDescription.Enabled = !this.Property.ReadOnly; + } + + protected override void OnPropertyValueChanged() + { + PointF newPos = new PointF((float)this.Property.ValueX, (float)this.Property.ValueY); + this.panControl.Position = newPos; + } + + private void PanControl_PositionChanged(object sender, EventArgs e) + { + PointF pos = this.panControl.Position; + + PointF clampedPos = new PointF( + Utility.Clamp(pos.X, (float)this.Property.MinValueX, (float)this.Property.MaxValueX), + Utility.Clamp(pos.Y, (float)this.Property.MinValueY, (float)this.Property.MaxValueY)); + + this.panControl.Position = clampedPos; + + Pair pairValue = Pair.Create((double)clampedPos.X, (double)clampedPos.Y); + + this.Property.Value = pairValue; + } + + protected override void OnDisplayNameChanged() + { + this.header.Text = this.DisplayName; + base.OnDisplayNameChanged(); + } + + protected override void OnDescriptionChanged() + { + this.textDescription.Text = this.Description; + base.OnDescriptionChanged(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vMargin = UI.ScaleHeight(4); + int hMargin = UI.ScaleWidth(4); + + this.header.Location = new Point(0, 0); + this.header.Size = + string.IsNullOrEmpty(DisplayName) ? + new Size(ClientSize.Width, 0) : + this.header.GetPreferredSize(new Size(ClientSize.Width, 0)); + + int panControlLength = Math.Min(this.panControl.Width, this.panControl.Height); + int tries = 2; + + while (tries > 0) + { + --tries; + + this.panControl.Location = new Point(0, this.header.Bottom + vMargin); + this.panControl.Size = new Size(panControlLength, panControlLength); + this.panControl.PerformLayout(); + + this.sliders.Location = new Point(this.panControl.Right + hMargin, this.header.Bottom + vMargin); + this.sliders.Width = ClientSize.Width - this.sliders.Left; + this.sliders.PerformLayout(); + + // put some padding to ensure the thumbnail is a little larger + int sliderBottomPlusReserve = this.sliders.Bottom + UI.ScaleHeight(20 + (SliderShowTickMarksX ? 0 : 4) + (SliderShowTickMarksY ? 0 : 4)); + + this.textDescription.Location = new Point( + 0, + (string.IsNullOrEmpty(this.Description) ? 0 : vMargin) + + Math.Max(this.panControl.Bottom, /*this.sliders.Bottom*/ sliderBottomPlusReserve)); + + this.textDescription.Width = ClientSize.Width; + this.textDescription.Height = string.IsNullOrEmpty(this.Description) ? 0 : + this.textDescription.GetPreferredSize(new Size(this.textDescription.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.textDescription.Bottom); + + panControlLength = (this.textDescription.Top - this.panControl.Top - vMargin); + panControlLength |= 1; + } + + base.OnLayout(levent); + } + + public DoubleVectorPanAndSliderPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.panControl = new PaintDotNet.Core.PanControl(); + this.panControl.Name = "panControl"; + this.panControl.StaticImageUnderlay = (ImageResource)propInfo.ControlProperties[ControlInfoPropertyNames.StaticImageUnderlay].Value; + this.panControl.PositionChanged += new EventHandler(PanControl_PositionChanged); + this.panControl.Size = new Size(1, 1); + + this.sliders = new DoubleVectorSliderPropertyControl(propInfo); + this.sliders.Name = "sliders"; + this.sliders.DisplayName = ""; + this.sliders.Description = ""; + + this.textDescription = new Label(); + this.textDescription.Name = "textDescription"; + this.textDescription.Text = Description; + + this.Controls.AddRange( + new Control[] + { + this.header, + this.panControl, + this.sliders, + this.textDescription + }); + + ResumeLayout(false); + } + + protected override bool OnFirstSelect() + { + return ((IFirstSelection)this.sliders).FirstSelect(); + } + } +} diff --git a/src/Core/IndirectUI/DoubleVectorSliderPropertyControl.cs b/src/Core/IndirectUI/DoubleVectorSliderPropertyControl.cs new file mode 100644 index 0000000..01ec69b --- /dev/null +++ b/src/Core/IndirectUI/DoubleVectorSliderPropertyControl.cs @@ -0,0 +1,252 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(DoubleVectorProperty), PropertyControlType.Slider)] + internal sealed class DoubleVectorSliderPropertyControl + : VectorSliderPropertyControl + { + private bool useExponentialScale = false; + + [PropertyControlProperty(DefaultValue = (object)2)] + public new int DecimalPlaces + { + get + { + return base.DecimalPlaces; + } + + set + { + base.DecimalPlaces = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool UseExponentialScale + { + get + { + return this.useExponentialScale; + } + + set + { + this.useExponentialScale = value; + ResetUIRanges(); + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public new double SliderSmallChangeX + { + get + { + return base.SliderSmallChangeX; + } + + set + { + base.SliderSmallChangeX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)5.0)] + public new double SliderLargeChangeX + { + get + { + return base.SliderLargeChangeX; + } + + set + { + base.SliderLargeChangeX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public new double UpDownIncrementX + { + get + { + return base.UpDownIncrementX; + } + + set + { + base.UpDownIncrementX = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public new double SliderSmallChangeY + { + get + { + return base.SliderSmallChangeY; + } + + set + { + base.SliderSmallChangeY = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)5.0)] + public new double SliderLargeChangeY + { + get + { + return base.SliderLargeChangeY; + } + + set + { + base.SliderLargeChangeY = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1.0)] + public new double UpDownIncrementY + { + get + { + return base.UpDownIncrementY; + } + + set + { + base.UpDownIncrementY = value; + } + } + + public DoubleVectorSliderPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + DecimalPlaces = (int)propInfo.ControlProperties[ControlInfoPropertyNames.DecimalPlaces].Value; + SliderSmallChangeX = (double)propInfo.ControlProperties[ControlInfoPropertyNames.SliderSmallChangeX].Value; + SliderLargeChangeX = (double)propInfo.ControlProperties[ControlInfoPropertyNames.SliderLargeChangeX].Value; + UpDownIncrementX = (double)propInfo.ControlProperties[ControlInfoPropertyNames.UpDownIncrementX].Value; + SliderSmallChangeY = (double)propInfo.ControlProperties[ControlInfoPropertyNames.SliderSmallChangeY].Value; + SliderLargeChangeY = (double)propInfo.ControlProperties[ControlInfoPropertyNames.SliderLargeChangeY].Value; + UpDownIncrementY = (double)propInfo.ControlProperties[ControlInfoPropertyNames.UpDownIncrementY].Value; + UseExponentialScale = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.UseExponentialScale].Value; + ResetUIRanges(); + } + + protected override double RoundPropertyValue(double value) + { + double multScale = Math.Pow(10, DecimalPlaces + 1); + double divScale = Math.Pow(10, -(DecimalPlaces + 1)); + + double roundMe = value * multScale; + double rounded = Math.Round(roundMe); + double valueResult = rounded * divScale; + + return valueResult; + } + + private decimal ToNudValue(double propertyValue) + { + return (decimal)propertyValue; + } + + private double FromNudValue(decimal nudValue) + { + return (double)nudValue; + } + + protected override int ToSliderValueX(double propertyValue) + { + if (this.useExponentialScale) + { + return PropertyControlUtil.ToSliderValueExp(propertyValue, Property.MinValueX, Property.MaxValueX, DecimalPlaces); + } + else + { + double toIntScale = Math.Pow(10, DecimalPlaces); + double sliderValueDouble = propertyValue * toIntScale; + int sliderValueInt = (int)sliderValueDouble; + return sliderValueInt; + } + } + + protected override double FromSliderValueX(int sliderValue) + { + if (this.useExponentialScale) + { + return PropertyControlUtil.FromSliderValueExp(sliderValue, Property.MinValueX, Property.MaxValueX, DecimalPlaces); + } + else + { + double fromIntScale = Math.Pow(10, -DecimalPlaces); + double valueDouble = (double)sliderValue * fromIntScale; + return valueDouble; + } + } + + protected override decimal ToNudValueX(double propertyValue) + { + return ToNudValue(propertyValue); + } + + protected override double FromNudValueX(decimal nudValue) + { + return FromNudValue(nudValue); + } + + protected override int ToSliderValueY(double propertyValue) + { + if (this.useExponentialScale) + { + return PropertyControlUtil.ToSliderValueExp(propertyValue, Property.MinValueY, Property.MaxValueY, DecimalPlaces); + } + else + { + double toIntScale = Math.Pow(10, DecimalPlaces); + double sliderValueDouble = propertyValue * toIntScale; + int sliderValueInt = (int)sliderValueDouble; + return sliderValueInt; + } + } + + protected override double FromSliderValueY(int sliderValue) + { + if (this.useExponentialScale) + { + return PropertyControlUtil.FromSliderValueExp(sliderValue, Property.MinValueY, Property.MaxValueY, DecimalPlaces); + } + else + { + double fromIntScale = Math.Pow(10, -DecimalPlaces); + double valueDouble = (double)sliderValue * fromIntScale; + return valueDouble; + } + } + + protected override decimal ToNudValueY(double propertyValue) + { + return ToNudValue(propertyValue); + } + + protected override double FromNudValueY(decimal nudValue) + { + return FromNudValue(nudValue); + } + } +} diff --git a/src/Core/IndirectUI/Int32ColorWheelPropertyControl.cs b/src/Core/IndirectUI/Int32ColorWheelPropertyControl.cs new file mode 100644 index 0000000..a10eab8 --- /dev/null +++ b/src/Core/IndirectUI/Int32ColorWheelPropertyControl.cs @@ -0,0 +1,861 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + // ColorBgra is essentially a 32-bit integer. This control lets you treat an Int32 as an RGB + // color -- alpha is not supported in this version for simplicity's sake. Thus, you must + // use an Int32Property that has a range of [0, 16777215]. + [PropertyControlInfo(typeof(Int32Property), PropertyControlType.ColorWheel)] + internal sealed class Int32ColorWheelPropertyControl + : PropertyControl> + { + private const int requiredMin = 0; + private const int requiredMax = 0xffffff; + + private HeaderLabel header; + private ColorWheel hsvColorWheel; + private ColorGradientControl valueSlider; + private ColorGradientControl saturationSlider; + private ColorRectangleControl colorRectangle; + private Label redLabel; + private PdnNumericUpDown redNud; + private Label greenLabel; + private PdnNumericUpDown greenNud; + private Label blueLabel; + private PdnNumericUpDown blueNud; + private Button resetButton; + private Label description; + + [PropertyControlProperty(DefaultValue = (object)true)] + public bool ShowResetButton + { + get + { + return this.resetButton.Visible; + } + + set + { + this.resetButton.Visible = value; + this.resetButton.AutoSize = value; + PerformLayout(); + } + } + + internal sealed class ColorWheel + : UserControl + { + private Bitmap renderBitmap = null; + private bool tracking = false; + private Point lastMouseXY = new Point(-1, -1); + + // this number controls what you might call the tesselation of the color wheel. higher #'s = slower, lower #'s = looks worse + private const int colorTesselation = 60; + + private PictureBox wheelPictureBox; + + private HsvColor hsvColor; + public HsvColor HsvColor + { + get + { + return hsvColor; + } + + set + { + if (hsvColor != value) + { + HsvColor oldColor = hsvColor; + hsvColor = value; + this.OnColorChanged(); + Refresh(); + } + } + } + + public ColorWheel() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + //wheelRegion = new PdnRegion(); + hsvColor = new HsvColor(0, 0, 0); + } + + private static PointF SphericalToCartesian(float r, float theta) + { + float x; + float y; + + x = r * (float)Math.Cos(theta); + y = r * (float)Math.Sin(theta); + + return new PointF(x,y); + } + + private static PointF[] GetCirclePoints(float r, PointF center) + { + PointF[] points = new PointF[colorTesselation]; + + for (int i = 0; i < colorTesselation; i++) + { + float theta = ((float)i / (float)colorTesselation) * 2 * (float)Math.PI; + points[i] = SphericalToCartesian(r, theta); + points[i].X += center.X; + points[i].Y += center.Y; + } + + return points; + } + + private Color[] GetColors() + { + Color[] colors = new Color[colorTesselation]; + + for (int i = 0; i < colorTesselation; i++) + { + int hue = (i * 360) / colorTesselation; + colors[i] = new HsvColor(hue, 100, 100).ToColor(); + } + + return colors; + } + + protected override void OnLoad(EventArgs e) + { + InitRendering(); + base.OnLoad(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + InitRendering(); + base.OnPaint(e); + } + + private void InitRendering() + { + if (this.renderBitmap == null) + { + InitRenderSurface(); + this.wheelPictureBox.SizeMode = PictureBoxSizeMode.StretchImage; + int size = (int)Math.Ceiling(ComputeDiameter(this.Size)); + this.wheelPictureBox.Size = new Size(size, size); + this.wheelPictureBox.Image = this.renderBitmap; + } + } + + private void WheelPictureBox_Paint(object sender, System.Windows.Forms.PaintEventArgs e) + { + float radius = ComputeRadius(Size); + float theta = ((float)HsvColor.Hue / 360.0f) * 2.0f * (float)Math.PI; + float alpha = ((float)HsvColor.Saturation / 100.0f); + float x = (alpha * (radius - 1) * (float)Math.Cos(theta)) + radius; + float y = (alpha * (radius - 1) * (float)Math.Sin(theta)) + radius; + int ix = (int)x; + int iy = (int)y; + + // Draw the 'target rectangle' + GraphicsContainer container = e.Graphics.BeginContainer(); + e.Graphics.PixelOffsetMode = PixelOffsetMode.None; + e.Graphics.SmoothingMode = SmoothingMode.HighQuality; + e.Graphics.DrawRectangle(Pens.Black, ix - 1, iy - 1, 3, 3); + e.Graphics.DrawRectangle(Pens.White, ix, iy, 1, 1); + e.Graphics.EndContainer(container); + } + + private void InitRenderSurface() + { + if (renderBitmap != null) + { + renderBitmap.Dispose(); + } + + int wheelDiameter = (int)ComputeDiameter(Size); + + renderBitmap = new Bitmap(Math.Max(1, (wheelDiameter * 4) / 3), + Math.Max(1, (wheelDiameter * 4) / 3), PixelFormat.Format24bppRgb); + + using (Graphics g1 = Graphics.FromImage(renderBitmap)) + { + g1.Clear(this.BackColor); + DrawWheel(g1, renderBitmap.Width, renderBitmap.Height); + } + } + + private void DrawWheel(Graphics g, int width, int height) + { + float radius = ComputeRadius(new Size(width, height)); + PointF[] points = GetCirclePoints(Math.Max(1.0f, (float)radius - 1), new PointF(radius, radius)); + + using (PathGradientBrush pgb = new PathGradientBrush(points)) + { + pgb.CenterColor = new HsvColor(0, 0, 100).ToColor(); + pgb.CenterPoint = new PointF(radius, radius); + pgb.SurroundColors = GetColors(); + + g.FillEllipse(pgb, 0, 0, radius * 2, radius * 2); + } + } + + private static float ComputeRadius(Size size) + { + return Math.Min((float)size.Width / 2, (float)size.Height / 2); + } + + private static float ComputeDiameter(Size size) + { + return Math.Min((float)size.Width, (float)size.Height); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize (e); + + if (renderBitmap != null && (ComputeRadius(Size) != ComputeRadius(renderBitmap.Size))) + { + renderBitmap.Dispose(); + renderBitmap = null; + } + + Invalidate(); + } + + public event EventHandler ColorChanged; + private void OnColorChanged() + { + if (ColorChanged != null) + { + ColorChanged(this, EventArgs.Empty); + } + } + + private void GrabColor(Point mouseXY) + { + // center our coordinate system so the middle is (0,0), and positive Y is facing up + int cx = mouseXY.X - (Width / 2); + int cy = mouseXY.Y - (Height / 2); + + double theta = Math.Atan2(cy, cx); + + if (theta < 0) + { + theta += 2 * Math.PI; + } + + double alpha = Math.Sqrt((cx * cx) + (cy * cy)); + + int h = (int)((theta / (Math.PI * 2)) * 360.0); + int s = (int)Math.Min(100.0, (alpha / (double)(Width / 2)) * 100); + int v = 100; + + hsvColor = new HsvColor(h, s, v); + OnColorChanged(); + Invalidate(true); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + tracking = true; + } + + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + if (tracking) + { + GrabColor(new Point(e.X, e.Y)); + } + + tracking = false; + + base.OnMouseUp(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + Point thisMouseXY = new Point(e.X, e.Y); + + if (tracking && thisMouseXY != this.lastMouseXY) + { + GrabColor(new Point(e.X, e.Y)); + } + + this.lastMouseXY = new Point(e.X, e.Y); + + base.OnMouseMove(e); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.wheelPictureBox = new System.Windows.Forms.PictureBox(); + this.SuspendLayout(); + // + // wheelPictureBox + // + this.wheelPictureBox.Location = new System.Drawing.Point(0, 0); + this.wheelPictureBox.Name = "wheelPictureBox"; + this.wheelPictureBox.TabIndex = 0; + this.wheelPictureBox.TabStop = false; + this.wheelPictureBox.Click += new System.EventHandler(this.wheelPictureBox_Click); + this.wheelPictureBox.Paint += new System.Windows.Forms.PaintEventHandler(this.WheelPictureBox_Paint); + this.wheelPictureBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.wheelPictureBox_MouseUp); + this.wheelPictureBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.wheelPictureBox_MouseMove); + this.wheelPictureBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.wheelPictureBox_MouseDown); + // + // ColorWheel + // + this.Controls.Add(this.wheelPictureBox); + this.Name = "ColorWheel"; + this.ResumeLayout(false); + + } + #endregion + + private void wheelPictureBox_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) + { + OnMouseMove(e); + } + + private void wheelPictureBox_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) + { + OnMouseUp(e); + } + + private void wheelPictureBox_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) + { + OnMouseDown(e); + } + + private void wheelPictureBox_Click(object sender, System.EventArgs e) + { + OnClick(e); + } + } + + internal class ColorRectangleControl + : UserControl + { + private Color rectangleColor; + public Color RectangleColor + { + get + { + return rectangleColor; + } + + set + { + rectangleColor = value; + Invalidate(true); + } + } + + public ColorRectangleControl() + { + this.ResizeRedraw = true; + this.DoubleBuffered = true; + } + + protected override void OnPaint(PaintEventArgs e) + { + Utility.DrawColorRectangle(e.Graphics, this.ClientRectangle, rectangleColor, true); + base.OnPaint(e); + } + } + + public Int32ColorWheelPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + if (Property.MinValue != requiredMin || Property.MaxValue != requiredMax) + { + throw new ArgumentException("The only range allowed for this control is [" + requiredMin + ", " + requiredMax + "]"); + } + + SuspendLayout(); + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.colorRectangle = new ColorRectangleControl(); + this.colorRectangle.Name = "colorRectangle"; + this.colorRectangle.TabStop = false; + this.colorRectangle.TabIndex = 0; + + this.hsvColorWheel = new ColorWheel(); + this.hsvColorWheel.Name = "hsvColorWheel"; + this.hsvColorWheel.ColorChanged += new EventHandler(HsvColorWheel_ColorChanged); + this.hsvColorWheel.TabStop = false; + this.hsvColorWheel.TabIndex = 1; + + this.saturationSlider = new ColorGradientControl(); + this.saturationSlider.Name = "saturationSlider"; + this.saturationSlider.Orientation = Orientation.Vertical; + this.saturationSlider.ValueChanged += new IndexEventHandler(SaturationSlider_ValueChanged); + this.saturationSlider.TabStop = false; + this.saturationSlider.TabIndex = 2; + + this.valueSlider = new ColorGradientControl(); + this.valueSlider.Name = "valueSlider"; + this.valueSlider.Orientation = Orientation.Vertical; + this.valueSlider.ValueChanged += new IndexEventHandler(ValueSlider_ValueChanged); + this.valueSlider.TabStop = false; + this.valueSlider.TabIndex = 3; + + this.redLabel = new Label(); + this.redLabel.Name = "redLabel"; + this.redLabel.AutoSize = true; + this.redLabel.Text = PdnResources.GetString("ColorsForm.RedLabel.Text"); + + this.redNud = new PdnNumericUpDown(); + this.redNud.Name = "redNud"; + this.redNud.Minimum = 0; + this.redNud.Maximum = 255; + this.redNud.TextAlign = HorizontalAlignment.Right; + this.redNud.ValueChanged += new EventHandler(RedNud_ValueChanged); + this.redNud.TabIndex = 4; + + this.greenLabel = new Label(); + this.greenLabel.Name = "greenLabel"; + this.greenLabel.AutoSize = true; + this.greenLabel.Text = PdnResources.GetString("ColorsForm.GreenLabel.Text"); + + this.greenNud = new PdnNumericUpDown(); + this.greenNud.Name = "greenNud"; + this.greenNud.Minimum = 0; + this.greenNud.Maximum = 255; + this.greenNud.TextAlign = HorizontalAlignment.Right; + this.greenNud.ValueChanged += new EventHandler(GreenNud_ValueChanged); + this.greenNud.TabIndex = 5; + + this.blueLabel = new Label(); + this.blueLabel.Name = "blueLabel"; + this.blueLabel.AutoSize = true; + this.blueLabel.Text = PdnResources.GetString("ColorsForm.BlueLabel.Text"); + + this.blueNud = new PdnNumericUpDown(); + this.blueNud.Name = "blueNud"; + this.blueNud.Minimum = 0; + this.blueNud.Maximum = 255; + this.blueNud.TextAlign = HorizontalAlignment.Right; + this.blueNud.ValueChanged += new EventHandler(BlueNud_ValueChanged); + this.blueNud.TabIndex = 6; + + this.resetButton = new Button(); + this.resetButton.AutoSize = true; + this.resetButton.Name = "resetButton"; + this.resetButton.FlatStyle = FlatStyle.Standard; + this.resetButton.Click += new EventHandler(ResetButton_Click); + this.resetButton.Image = PdnResources.GetImage("Icons.ResetIcon.png"); + this.resetButton.Width = 1; + this.resetButton.Visible = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.ShowResetButton].Value; + this.ToolTip.SetToolTip(this.resetButton, PdnResources.GetString("Form.ResetButton.Text").Replace("&", "")); + this.resetButton.TabIndex = 7; + + this.description = new Label(); + this.description.Name = "description"; + this.description.Text = this.Description; + + this.Controls.AddRange( + new Control[] + { + this.header, + this.hsvColorWheel, + this.saturationSlider, + this.valueSlider, + this.colorRectangle, + this.redLabel, + this.redNud, + this.greenLabel, + this.greenNud, + this.blueLabel, + this.blueNud, + this.resetButton, + this.description + }); + + ResumeLayout(false); + } + + private void ResetButton_Click(object sender, EventArgs e) + { + if (Property.Value != Property.DefaultValue) + { + Property.Value = Property.DefaultValue; + } + } + + private int changingStack = 0; + + private void HsvColorWheel_ColorChanged(object sender, EventArgs e) + { + if (this.changingStack == 0) + { + ++this.changingStack; + + HsvColor hsv = this.hsvColorWheel.HsvColor; + SetPropertyValueFromHsv(hsv); + + --this.changingStack; + } + } + + private void ValueSlider_ValueChanged(object sender, IndexEventArgs ce) + { + if (this.changingStack == 0) + { + ++this.changingStack; + + HsvColor hsv = this.hsvColorWheel.HsvColor; + hsv.Value = (this.valueSlider.Value * 100) / 255; + SetPropertyValueFromHsv(hsv); + + --this.changingStack; + } + } + + private void SaturationSlider_ValueChanged(object sender, IndexEventArgs ce) + { + if (this.changingStack == 0) + { + ++this.changingStack; + + HsvColor hsv = this.hsvColorWheel.HsvColor; + hsv.Saturation = (this.saturationSlider.Value * 100) / 255; + SetPropertyValueFromHsv(hsv); + + --this.changingStack; + } + } + + private void RedNud_ValueChanged(object sender, EventArgs e) + { + if (this.changingStack == 0) + { + ++this.changingStack; + SetPropertyValueFromRgb((int)this.redNud.Value, (int)this.greenNud.Value, (int)this.blueNud.Value); + --this.changingStack; + } + } + + private void GreenNud_ValueChanged(object sender, EventArgs e) + { + if (this.changingStack == 0) + { + ++this.changingStack; + SetPropertyValueFromRgb((int)this.redNud.Value, (int)this.greenNud.Value, (int)this.blueNud.Value); + --this.changingStack; + } + } + + private void BlueNud_ValueChanged(object sender, EventArgs e) + { + if (this.changingStack == 0) + { + ++this.changingStack; + SetPropertyValueFromRgb((int)this.redNud.Value, (int)this.greenNud.Value, (int)this.blueNud.Value); + --this.changingStack; + } + } + + private void SetPropertyValueFromRgb(int red, int green, int blue) + { + UI.SuspendControlPainting(this); + + try + { + if (this.redNud.Value != red) + { + this.redNud.Value = red; + } + + if (this.greenNud.Value != green) + { + this.greenNud.Value = green; + } + + if (this.blueNud.Value != blue) + { + this.blueNud.Value = blue; + } + + if (this.inOnPropertyValueChanged == 0) + { + int newValue = (red << 16) | (green << 8) | blue; + if (Property.Value != newValue) + { + Property.Value = newValue; + } + } + + RgbColor rgb = new RgbColor(red, green, blue); + HsvColor hsv = rgb.ToHsv(); + this.hsvColorWheel.HsvColor = hsv; + this.valueSlider.Value = (hsv.Value * 255) / 100; + this.saturationSlider.Value = (hsv.Saturation * 255) / 100; + + HsvColor hsvValMin = hsv; + hsvValMin.Value = 0; + HsvColor hsvValMax = hsv; + hsvValMax.Value = 100; + this.valueSlider.MinColor = hsvValMin.ToColor(); + this.valueSlider.MaxColor = hsvValMax.ToColor(); + + HsvColor hsvSatMin = hsv; + hsvSatMin.Saturation = 0; + HsvColor hsvSatMax = hsv; + hsvSatMax.Saturation = 100; + this.saturationSlider.MinColor = hsvSatMin.ToColor(); + this.saturationSlider.MaxColor = hsvSatMax.ToColor(); + } + + finally + { + UI.ResumeControlPainting(this); + + if (UI.IsControlPaintingEnabled(this)) + { + Refresh(); + } + } + } + + private void SetPropertyValueFromHsv(HsvColor hsv) + { + UI.SuspendControlPainting(this); + + try + { + RgbColor rgb = hsv.ToRgb(); + SetPropertyValueFromRgb(rgb.Red, rgb.Green, rgb.Blue); + + if (this.hsvColorWheel.HsvColor != hsv) + { + this.hsvColorWheel.HsvColor = hsv; + } + + if (this.valueSlider.Value != (hsv.Value * 255) / 100) + { + this.valueSlider.Value = (hsv.Value * 255) / 100; + } + + if (this.saturationSlider.Value != (hsv.Saturation * 255) / 100) + { + this.saturationSlider.Value = (hsv.Saturation * 255) / 100; + } + + HsvColor hsvValMin = hsv; + hsvValMin.Value = 0; + HsvColor hsvValMax = hsv; + hsvValMax.Value = 100; + this.valueSlider.MinColor = hsvValMin.ToColor(); + this.valueSlider.MaxColor = hsvValMax.ToColor(); + + HsvColor hsvSatMin = hsv; + hsvSatMin.Saturation = 0; + HsvColor hsvSatMax = hsv; + hsvSatMax.Saturation = 100; + this.saturationSlider.MinColor = hsvSatMin.ToColor(); + this.saturationSlider.MaxColor = hsvSatMax.ToColor(); + } + + finally + { + UI.ResumeControlPainting(this); + + if (UI.IsControlPaintingEnabled(this)) + { + Refresh(); + } + } + } + + protected override void OnLayout(LayoutEventArgs e) + { + int vMargin = UI.ScaleHeight(4); + int hMargin = UI.ScaleWidth(4); + + this.header.Location = new Point(0, 0); + this.header.Size = string.IsNullOrEmpty(DisplayName) ? + new Size(0, 0) : + this.header.GetPreferredSize(new Size(ClientSize.Width, 0)); + + if (this.resetButton.Visible) + { + this.resetButton.PerformLayout(); + } + else + { + this.resetButton.Size = new Size(0, 0); + } + + int baseNudWidth = UI.ScaleWidth(50); + int nudWidth = Math.Max(baseNudWidth, this.resetButton.Width); + + this.redNud.PerformLayout(); + this.redNud.Width = nudWidth; + this.redNud.Location = new Point( + ClientSize.Width - this.redNud.Width, + this.header.Bottom + vMargin); + + this.redLabel.PerformLayout(); + this.redLabel.Location = new Point( + this.redNud.Left - this.redLabel.Width - hMargin, + this.redNud.Top + (this.redNud.Height - this.redLabel.Height) / 2); + + this.greenNud.PerformLayout(); + this.greenNud.Width = nudWidth; + this.greenNud.Location = new Point( + ClientSize.Width - this.greenNud.Width, + Math.Max(this.redNud.Bottom, this.redLabel.Bottom) + vMargin); + + this.greenLabel.PerformLayout(); + this.greenLabel.Location = new Point( + this.greenNud.Left - this.greenLabel.Width - hMargin, + this.greenNud.Top + (this.greenNud.Height - this.greenLabel.Height) / 2); + + this.blueNud.PerformLayout(); + this.blueNud.Width = nudWidth; + this.blueNud.Location = new Point( + ClientSize.Width - this.blueNud.Width, + Math.Max(this.greenNud.Bottom, this.greenLabel.Bottom) + vMargin); + + this.blueLabel.PerformLayout(); + this.blueLabel.Location = new Point( + this.blueNud.Left - this.blueLabel.Width - hMargin, + this.blueNud.Top + (this.blueNud.Height - this.blueLabel.Height) / 2); + + this.resetButton.Location = new Point( + ClientSize.Width - this.resetButton.Width, + Math.Max(this.blueNud.Bottom, this.blueLabel.Bottom) + vMargin); + this.resetButton.Width = Math.Max(this.resetButton.Width, nudWidth); + + int colorsMaxRight = Math.Min(this.redLabel.Left, Math.Min(this.greenLabel.Left, this.blueLabel.Left)); + int colorsMinLeft = 0; + int colorsAvailableWidth = colorsMaxRight - colorsMinLeft; + + this.colorRectangle.Top = this.header.Bottom + vMargin; + this.hsvColorWheel.Top = this.header.Bottom + vMargin; + + int hsvSide = this.resetButton.Bottom - this.hsvColorWheel.Top; + + this.colorRectangle.Size = UI.ScaleSize(new Size(28, 28)); + + this.hsvColorWheel.Size = new Size(hsvSide, hsvSide); + + this.saturationSlider.Top = this.header.Bottom + vMargin; + this.saturationSlider.Size = new Size(UI.ScaleWidth(20), this.hsvColorWheel.Height); + + this.valueSlider.Top = this.header.Bottom + vMargin; + this.valueSlider.Size = new Size(UI.ScaleWidth(20), this.hsvColorWheel.Height); + + int colorsWidth = + this.colorRectangle.Width + hMargin + + this.hsvColorWheel.Width + hMargin + + this.saturationSlider.Width + hMargin + + this.valueSlider.Width; + + int colorsLeft = colorsMinLeft + (colorsAvailableWidth - colorsWidth) / 2; + + this.colorRectangle.Left = colorsLeft; + this.hsvColorWheel.Left = this.colorRectangle.Right + hMargin; + this.saturationSlider.Left = this.hsvColorWheel.Right + hMargin; + this.valueSlider.Left = this.saturationSlider.Right + hMargin; + + this.description.Location = new Point( + 0, + (string.IsNullOrEmpty(Description) ? 0 : vMargin) + this.hsvColorWheel.Bottom); + + this.description.Width = ClientSize.Width; + this.description.Height = string.IsNullOrEmpty(Description) ? 0 : + this.description.GetPreferredSize(new Size(this.description.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.description.Bottom); + + base.OnLayout(e); + } + + protected override void OnPropertyReadOnlyChanged() + { + this.hsvColorWheel.Enabled = !Property.ReadOnly; + this.valueSlider.Enabled = !Property.ReadOnly; + this.redNud.Enabled = !Property.ReadOnly; + this.redLabel.Enabled = !Property.ReadOnly; + this.greenNud.Enabled = !Property.ReadOnly; + this.greenLabel.Enabled = !Property.ReadOnly; + this.blueNud.Enabled = !Property.ReadOnly; + this.blueLabel.Enabled = !Property.ReadOnly; + this.resetButton.Enabled = !Property.ReadOnly; + } + + private int inOnPropertyValueChanged = 0; // this field is necessary to avoid having Effect`1.OnSetRenderInfo() called 3-4+ times at effect startup + protected override void OnPropertyValueChanged() + { + ++this.inOnPropertyValueChanged; + + try + { + int value = Property.Value; + int red = (value >> 16) & 0xff; + int green = (value >> 8) & 0xff; + int blue = value & 0xff; + SetPropertyValueFromRgb(red, green, blue); + + ColorBgra color = ColorBgra.FromBgr((byte)blue, (byte)green, (byte)red); + this.colorRectangle.RectangleColor = color.ToColor(); + } + + finally + { + --this.inOnPropertyValueChanged; + } + } + + protected override bool OnFirstSelect() + { + this.redNud.Select(); + return true; + } + } +} diff --git a/src/Core/IndirectUI/Int32IncrementButtonPropertyControl.cs b/src/Core/IndirectUI/Int32IncrementButtonPropertyControl.cs new file mode 100644 index 0000000..91960f1 --- /dev/null +++ b/src/Core/IndirectUI/Int32IncrementButtonPropertyControl.cs @@ -0,0 +1,134 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(Int32Property), PropertyControlType.IncrementButton)] + internal sealed class Int32IncrementButtonPropertyControl + : PropertyControl + { + private HeaderLabel header; + private Button incrementButton; + private Label descriptionText; + + [PropertyControlProperty(DefaultValue = "+")] + public string ButtonText + { + get + { + return this.incrementButton.Text; + } + + set + { + this.incrementButton.Text = value; + } + } + + public Int32IncrementButtonPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.Text = this.DisplayName; + this.header.RightMargin = 0; + + this.incrementButton = new Button(); + this.incrementButton.Name = "incrementButton"; + this.incrementButton.AutoSize = true; + this.incrementButton.FlatStyle = FlatStyle.System; + this.incrementButton.Text = (string)propInfo.ControlProperties[ControlInfoPropertyNames.ButtonText].Value; + this.incrementButton.Click += new EventHandler(IncrementButton_Click); + + this.descriptionText = new Label(); + this.descriptionText.Name = "descriptionText"; + this.descriptionText.AutoSize = false; + this.descriptionText.Text = this.Description; + + this.Controls.AddRange( + new Control[] + { + this.header, + this.incrementButton, + this.descriptionText + }); + + ResumeLayout(false); + PerformLayout(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vSpacing = UI.ScaleHeight(4); + + this.header.Location = new Point(0, 0); + this.header.Width = ClientSize.Width; + this.header.Height = string.IsNullOrEmpty(this.header.Text) ? 0 : this.header.GetPreferredSize(new Size(this.header.Width, 1)).Height; + + this.incrementButton.PerformLayout(); + this.incrementButton.Location = new Point( + 0, + this.header.Bottom + (string.IsNullOrEmpty(this.header.Text) ? 0 : vSpacing)); + + this.descriptionText.Location = new Point( + 0, + this.incrementButton.Bottom + (string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : vSpacing)); + this.descriptionText.Width = ClientSize.Width; + this.descriptionText.Height = string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : + this.descriptionText.GetPreferredSize(new Size(this.descriptionText.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.descriptionText.Bottom); + + base.OnLayout(levent); + } + + private void IncrementButton_Click(object sender, EventArgs e) + { + long minValue = (long)Property.MinValue; + long maxValue = (long)Property.MaxValue; + long deltaCount = maxValue - minValue + 1; + + if (deltaCount != 0) + { + long value1 = (long)Property.Value; + long value2 = 1 + value1; + long value3 = ((value2 - minValue) % deltaCount) + minValue; + long newValue = value3; + + Property.Value = (int)newValue; + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.header.Enabled = !Property.ReadOnly; + this.incrementButton.Enabled = !Property.ReadOnly; + } + + protected override void OnPropertyValueChanged() + { + // nothing to do really + } + + protected override bool OnFirstSelect() + { + this.incrementButton.Select(); + return true; + } + } +} diff --git a/src/Core/IndirectUI/Int32SliderPropertyControl.cs b/src/Core/IndirectUI/Int32SliderPropertyControl.cs new file mode 100644 index 0000000..247b114 --- /dev/null +++ b/src/Core/IndirectUI/Int32SliderPropertyControl.cs @@ -0,0 +1,95 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using System; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(Int32Property), PropertyControlType.Slider, IsDefault = true)] + internal sealed class Int32SliderPropertyControl + : SliderPropertyControl + { + private const int maxMax = 100000000; + private const int minMin = -100000000; + + [PropertyControlProperty(DefaultValue = (object)1)] + public new int SliderSmallChange + { + get + { + return base.SliderSmallChange; + } + + set + { + base.SliderSmallChange = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)5)] + public new int SliderLargeChange + { + get + { + return base.SliderLargeChange; + } + + set + { + base.SliderLargeChange = value; + } + } + + [PropertyControlProperty(DefaultValue = (object)1)] + public new int UpDownIncrement + { + get + { + return base.UpDownIncrement; + } + + set + { + base.UpDownIncrement = value; + } + } + + public Int32SliderPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + SliderSmallChange = (int)propInfo.ControlProperties[ControlInfoPropertyNames.SliderSmallChange].Value; + SliderLargeChange = (int)propInfo.ControlProperties[ControlInfoPropertyNames.SliderLargeChange].Value; + UpDownIncrement = (int)propInfo.ControlProperties[ControlInfoPropertyNames.UpDownIncrement].Value; + ResumeLayout(false); + } + + protected override int ToSliderValue(int propertyValue) + { + return Utility.Clamp(propertyValue, minMin, maxMax); + } + + protected override int FromSliderValue(int sliderValue) + { + return Utility.Clamp(sliderValue, minMin, maxMax); + } + + protected override decimal ToNudValue(int propertyValue) + { + return (decimal)Utility.Clamp(propertyValue, minMin, maxMax); + } + + protected override int FromNudValue(decimal nudValue) + { + return Utility.Clamp((int)nudValue, minMin, maxMax); + } + } +} diff --git a/src/Core/IndirectUI/PanelControl.cs b/src/Core/IndirectUI/PanelControl.cs new file mode 100644 index 0000000..38b66c3 --- /dev/null +++ b/src/Core/IndirectUI/PanelControl.cs @@ -0,0 +1,83 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + internal sealed class PanelControl + : UserControl, + IFirstSelection + { + private List controls = new List(); + + protected override void OnLayout(LayoutEventArgs levent) + { + int vSpacing = UI.ScaleHeight(6); + int insetWidth = Width; + int y = 0; + + foreach (Control control in this.controls) + { + control.Location = new Point(0, y); + control.Width = insetWidth; + control.PerformLayout(); + + y = control.Bottom + vSpacing; + } + + ClientSize = new Size(ClientSize.Width, (y == 0) ? 0 : (y - vSpacing)); + + base.OnLayout(levent); + } + + public PanelControl(PanelControlInfo panelInfo) + { + SuspendLayout(); + + DoubleBuffered = true; + + int tabIndex = 0; + + foreach (ControlInfo controlInfo in panelInfo.ChildControls) + { + Control childControl = controlInfo.CreateWinFormsControl(); + childControl.TabIndex = tabIndex; + ++tabIndex; + this.controls.Add(childControl); + } + + Controls.AddRange(this.controls.ToArray()); + + ResumeLayout(false); + } + + bool IFirstSelection.FirstSelect() + { + foreach (Control control in this.controls) + { + IFirstSelection asIFS = control as IFirstSelection; + + if (asIFS != null) + { + if (asIFS.FirstSelect()) + { + return true; + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Core/IndirectUI/PanelControlInfo.cs b/src/Core/IndirectUI/PanelControlInfo.cs new file mode 100644 index 0000000..4a30ed5 --- /dev/null +++ b/src/Core/IndirectUI/PanelControlInfo.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + public sealed class PanelControlInfo + : ControlInfo + { + public T AddChildControl(T controlInfo) + where T : ControlInfo + { + GetChildControlsCore().Add(controlInfo); + return controlInfo; + } + + public void RemoveChildControl(ControlInfo controlInfo) + { + GetChildControlsCore().Remove(controlInfo); + } + + public PanelControlInfo() + { + } + + private PanelControlInfo(PanelControlInfo cloneMe) + : base(cloneMe) + { + } + + internal override Control CreateWinFormsControl() + { + return new PanelControl(this); + } + + public override ControlInfo Clone() + { + return new PanelControlInfo(this); + } + } +} diff --git a/src/Core/IndirectUI/PropertyControl.cs b/src/Core/IndirectUI/PropertyControl.cs new file mode 100644 index 0000000..bd84851 --- /dev/null +++ b/src/Core/IndirectUI/PropertyControl.cs @@ -0,0 +1,147 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + internal abstract class PropertyControl + : UserControl, + IPropertyRef, + IFirstSelection + { + private Property property; + private string displayName; + private string description; + private ToolTip toolTip; + + public Property Property + { + get + { + return this.property; + } + } + + protected ToolTip ToolTip + { + get + { + if (this.toolTip == null) + { + this.toolTip = new ToolTip(); + } + + return this.toolTip; + } + } + + protected virtual void OnDisplayNameChanged() + { + } + + [PropertyControlProperty(DefaultValue = "")] + public string DisplayName + { + get + { + return this.displayName; + } + + set + { + if (this.displayName != value) + { + this.displayName = value; + OnDisplayNameChanged(); + PerformLayout(); + } + } + } + + protected virtual void OnDescriptionChanged() + { + } + + [PropertyControlProperty(DefaultValue = "")] + public string Description + { + get + { + return this.description; + } + + set + { + this.description = value; + OnDescriptionChanged(); + PerformLayout(); + } + } + + internal PropertyControl(PropertyControlInfo propInfo) + { + this.property = propInfo.Property; + this.property.ValueChanged += new EventHandler(Property_ValueChanged); + this.property.ReadOnlyChanged += new EventHandler(Property_ReadOnlyChanged); + this.displayName = (string)propInfo.ControlProperties[ControlInfoPropertyNames.DisplayName].Value; + this.description = (string)propInfo.ControlProperties[ControlInfoPropertyNames.Description].Value; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.toolTip != null) + { + this.toolTip.Dispose(); + this.toolTip = null; + } + } + + base.Dispose(disposing); + } + + protected override void OnLoad(EventArgs e) + { + OnPropertyValueChanged(); + OnPropertyReadOnlyChanged(); + base.OnLoad(e); + } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + } + + protected abstract void OnPropertyReadOnlyChanged(); + + private void Property_ReadOnlyChanged(object sender, EventArgs e) + { + OnPropertyReadOnlyChanged(); + } + + protected abstract void OnPropertyValueChanged(); + + private void Property_ValueChanged(object sender, EventArgs e) + { + OnPropertyValueChanged(); + } + + protected abstract bool OnFirstSelect(); + + bool IFirstSelection.FirstSelect() + { + return OnFirstSelect(); + } + } +} diff --git a/src/Core/IndirectUI/PropertyControlInfo.cs b/src/Core/IndirectUI/PropertyControlInfo.cs new file mode 100644 index 0000000..28ae1cc --- /dev/null +++ b/src/Core/IndirectUI/PropertyControlInfo.cs @@ -0,0 +1,199 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + public sealed class PropertyControlInfo + : ControlInfo, + IPropertyRef + { + // NOTE: In these descriptions, do not confuse Property.GetType() with Property.ValueType. + + // Maps from Property.GetType() to set of PropertyControlType + private static Dictionary> propertyTypeToControlType; + + // Maps from Property.GetType() to the default PropertyControlType (some property types get more than 1) + private static Dictionary propertyTypeToDefaultControlType; + + // Maps from {Property.GetType(), PropertyControlType} to PropertyControl.GetType() + private static Dictionary, Type> controlTypeToPropertyControlType; + + // Maps from {Property.GetType(), PropertyControlType} to its PropertyControlInfo properties + private static Dictionary, PropertyCollection> controlTypeToProperties; + + static PropertyControlInfo() + { + Tracing.Enter(); + + try + { + BuildStaticMaps(); + } + + finally + { + Tracing.Leave(); + } + } + + private static void BuildStaticMaps() + { + propertyTypeToControlType = new Dictionary>(); + propertyTypeToDefaultControlType = new Dictionary(); + controlTypeToPropertyControlType = new Dictionary,Type>(); + controlTypeToProperties = new Dictionary, PropertyCollection>(); + + Assembly thisAssembly = Assembly.GetExecutingAssembly(); + Type[] types = thisAssembly.GetTypes(); + + foreach (Type propertyControlType in types) + { + if (propertyControlType.IsAbstract) + { + continue; + } + + if (!propertyControlType.IsSubclassOf(typeof(PropertyControl))) + { + continue; + } + + object[] infoAttributes = propertyControlType.GetCustomAttributes(typeof(PropertyControlInfoAttribute), false); + PropertyControlInfoAttribute infoAttribute; + + if (infoAttributes.Length == 1) + { + infoAttribute = (PropertyControlInfoAttribute)infoAttributes[0]; + + if (!propertyTypeToControlType.ContainsKey(infoAttribute.PropertyType)) + { + propertyTypeToControlType.Add(infoAttribute.PropertyType, new Set()); + } + + propertyTypeToControlType[infoAttribute.PropertyType].Add(infoAttribute.ControlType); + controlTypeToPropertyControlType[Pair.Create(infoAttribute.PropertyType, infoAttribute.ControlType)] = propertyControlType; + + if (!propertyTypeToDefaultControlType.ContainsKey(infoAttribute.PropertyType) || + infoAttribute.IsDefault) + { + propertyTypeToDefaultControlType[infoAttribute.PropertyType] = infoAttribute.ControlType; + } + + List controlProps = new List(); + PropertyInfo[] props = propertyControlType.GetProperties(); + + foreach (PropertyInfo prop in props) + { + object[] propAttributes = prop.GetCustomAttributes(typeof(PropertyControlPropertyAttribute), true); + + if (propAttributes.Length == 1) + { + string propName = prop.Name; + Type propType = prop.PropertyType; + object propDefault = ((PropertyControlPropertyAttribute)propAttributes[0]).DefaultValue; + Property propProp = Property.Create(propType, propName, propDefault); + controlProps.Add(propProp); + } + } + + PropertyCollection controlProps2 = new PropertyCollection(controlProps); + controlTypeToProperties[Pair.Create(infoAttribute.PropertyType, infoAttribute.ControlType)] = controlProps2; + } + } + } + + private Property property; + private StaticListChoiceProperty controlType; + private Dictionary valueDisplayNames = new Dictionary(); + + public Property Property + { + get + { + return this.property; + } + } + + public StaticListChoiceProperty ControlType + { + get + { + return this.controlType; + } + } + + public void SetValueDisplayName(object value, string displayName) + { + this.valueDisplayNames[value] = displayName; + } + + public string GetValueDisplayName(object value) + { + string valueDisplayName; + this.valueDisplayNames.TryGetValue(value, out valueDisplayName); + return valueDisplayName ?? value.ToString(); + } + + private PropertyControlInfo(Property property) + : base() + { + this.property = property; + PropertyControlType defaultControlType = propertyTypeToDefaultControlType[this.property.GetType()]; + this.controlType = StaticListChoiceProperty.CreateForEnum(ControlInfoPropertyNames.ControlType, defaultControlType, false); + this.controlType.ValueChanged += new EventHandler(ControlType_ValueChanged); + this.ControlProperties = controlTypeToProperties[Pair.Create(property.GetType(), (PropertyControlType)this.controlType.Value)].Clone(); + } + + private PropertyControlInfo(PropertyControlInfo cloneMe) + : base(cloneMe) + { + this.property = cloneMe.property; + this.controlType = (StaticListChoiceProperty)cloneMe.controlType.Clone(); + this.valueDisplayNames = new Dictionary(cloneMe.valueDisplayNames); + } + + private void ControlType_ValueChanged(object sender, EventArgs e) + { + PropertyCollection newProps = controlTypeToProperties[Pair.Create(this.Property.GetType(), + (PropertyControlType)this.controlType.Value)].Clone(); + + newProps.CopyCompatibleValuesFrom(this.ControlProperties); + + this.ControlProperties = newProps; + } + + public static PropertyControlInfo CreateFor(Property property) + { + return new PropertyControlInfo(property); + } + + internal override Control CreateWinFormsControl() + { + Type propertyControlType = controlTypeToPropertyControlType[Pair.Create( + Property.GetType(), (PropertyControlType)this.controlType.Value)]; + + PropertyControl propertyControl = (PropertyControl)Activator.CreateInstance(propertyControlType, this); + + return propertyControl; + } + + public override ControlInfo Clone() + { + return new PropertyControlInfo(this); + } + } +} diff --git a/src/Core/IndirectUI/PropertyControlInfoAttribute.cs b/src/Core/IndirectUI/PropertyControlInfoAttribute.cs new file mode 100644 index 0000000..c208e34 --- /dev/null +++ b/src/Core/IndirectUI/PropertyControlInfoAttribute.cs @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.PropertySystem; +using System; + +namespace PaintDotNet.IndirectUI +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + internal class PropertyControlInfoAttribute + : Attribute + { + private Type propertyType; + private PropertyControlType controlType; + private bool isDefault; + + public Type PropertyType + { + get + { + return this.propertyType; + } + } + + public PropertyControlType ControlType + { + get + { + return this.controlType; + } + } + + public bool IsDefault + { + get + { + return this.isDefault; + } + + set + { + this.isDefault = value; + } + } + + public PropertyControlInfoAttribute(Type propertyType, PropertyControlType controlType) + { + if (!typeof(Property).IsAssignableFrom(propertyType)) + { + throw new ArgumentException("propertyType must be a type that derives from Property"); + } + + this.propertyType = propertyType; + this.controlType = controlType; + } + } +} diff --git a/src/Core/IndirectUI/PropertyControlPropertyAttribute.cs b/src/Core/IndirectUI/PropertyControlPropertyAttribute.cs new file mode 100644 index 0000000..097d04c --- /dev/null +++ b/src/Core/IndirectUI/PropertyControlPropertyAttribute.cs @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + internal class PropertyControlPropertyAttribute + : Attribute + { + private object defaultValue; + private string defaultValueResourceName; + + public object DefaultValue + { + get + { + if (string.IsNullOrEmpty(this.defaultValueResourceName)) + { + return this.defaultValue; + } + else + { + return PdnResources.GetString(this.defaultValueResourceName); + } + } + + set + { + this.defaultValue = value; + } + } + + public string DefaultValueResourceName + { + get + { + return this.defaultValueResourceName; + } + + set + { + this.defaultValueResourceName = value; + } + } + + public PropertyControlPropertyAttribute() + { + } + } +} \ No newline at end of file diff --git a/src/Core/IndirectUI/PropertyControlType.cs b/src/Core/IndirectUI/PropertyControlType.cs new file mode 100644 index 0000000..3cd9e6b --- /dev/null +++ b/src/Core/IndirectUI/PropertyControlType.cs @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.IndirectUI +{ + public enum PropertyControlType + : int + { + AngleChooser = 0, + CheckBox = 1, + PanAndSlider = 2, + Slider = 3, + IncrementButton = 4, + DropDown = 5, + TextBox = 6, + RadioButton = 7, + ColorWheel = 8 + } +} diff --git a/src/Core/IndirectUI/PropertyControlUtil.cs b/src/Core/IndirectUI/PropertyControlUtil.cs new file mode 100644 index 0000000..c1e2041 --- /dev/null +++ b/src/Core/IndirectUI/PropertyControlUtil.cs @@ -0,0 +1,149 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + internal static class PropertyControlUtil + { + public static int GetGoodSliderHeight(TrackBar slider) + { + if (slider.AutoSize) + { + return slider.Height; + } + else if (slider.TickStyle == TickStyle.BottomRight || slider.TickStyle == TickStyle.TopLeft) + { + return UI.ScaleHeight(35); // determined experimentally + } + else if (slider.TickStyle == TickStyle.None) + { + return UI.ScaleHeight(25); // determined experimentally + } + else if (slider.TickStyle == TickStyle.Both) + { + return UI.ScaleHeight(45); // pulled from default Height value when AutoSize=true + } + else + { + throw new InvalidEnumArgumentException(); + } + } + + public static int GetGoodSliderTickFrequency(TrackBar slider) + { + int delta = Math.Abs(slider.Maximum - slider.Minimum); + int goodTicks = Math.Max(1, delta / 20); + return goodTicks; + } + + public static int ToSliderValueExpCore(double propertyValue, double minValue, double maxValue, int scaleLog10) + { + int scaleLog10Plus1 = 1 + scaleLog10; + + int toIntScale = (int)Math.Pow(10, scaleLog10Plus1); + double toDoubleScale = Math.Pow(10, -scaleLog10Plus1); + + double lerp = Math.Abs((propertyValue - minValue) / (maxValue - minValue)); + double lerp2 = Math.Sqrt(lerp); + double newPropertyValue = minValue + (lerp2 * (maxValue - minValue)); + double clampedNewPropertyValue = Utility.Clamp(newPropertyValue, minValue, maxValue); + double newSliderValueDouble = clampedNewPropertyValue * toIntScale; + long newSliderValueLong = (long)newSliderValueDouble; + int newSliderValueInt = (int)newSliderValueLong; + + return newSliderValueInt; + } + + public static int ToSliderValueExp(double propertyValue, double minValue, double maxValue, int scaleLog10) + { + if (propertyValue == 0) + { + // Zero is always zero. + return 0; + } + else if (minValue >= 0 && maxValue >= 0) + { + // All positive range: normal math + return ToSliderValueExpCore(propertyValue, minValue, maxValue, scaleLog10); + } + else if (minValue <= 0 && maxValue <= 0) + { + // All negative range: negate values, convert, then negate back over to positve range again + return -ToSliderValueExpCore(-propertyValue, -minValue, -maxValue, scaleLog10); + } + else if (propertyValue > 0) + { + return ToSliderValueExpCore(propertyValue, 0, maxValue, scaleLog10); + } + else if (propertyValue < 0) + { + return -ToSliderValueExpCore(-propertyValue, 0, -minValue, scaleLog10); + } + else + { + throw new InvalidOperationException(); + } + } + + public static double FromSliderValueExpCore(int sliderValue, double minValue, double maxValue, int scaleLog10) + { + int scaleLog10Plus1 = 1 + scaleLog10; + + int toIntScale = (int)Math.Pow(10, scaleLog10Plus1); + double toDoubleScale = Math.Pow(10, -scaleLog10Plus1); + + double newPropertyValue = (double)sliderValue * toDoubleScale; + double lerp2 = (newPropertyValue - minValue) / (maxValue - minValue); + double lerp = lerp2 * lerp2; + double propertyValue = minValue + (lerp * (maxValue - minValue)); + return Utility.Clamp(propertyValue, minValue, maxValue); + } + + public static double FromSliderValueExp(int sliderValue, double minValue, double maxValue, int scaleLog10) + { + if (sliderValue == 0) + { + // Zero is always zero. + return 0; + } + else if (minValue >= 0 && maxValue >= 0) + { + // All positive range: normal math + return FromSliderValueExpCore(sliderValue, minValue, maxValue, scaleLog10); + } + else if (minValue <= 0 && maxValue <= 0) + { + // All negative range: negate values, convert, then negate back over to positve range again + return -FromSliderValueExpCore(-sliderValue, -minValue, -maxValue, scaleLog10); + } + else if (sliderValue > 0) + { + // Split range, and value is in the positive side. + return FromSliderValueExpCore(sliderValue, 0, maxValue, scaleLog10); + } + else if (sliderValue < 0) + { + // Split range, and value is in the negative side. + return -FromSliderValueExpCore(-sliderValue, 0, -minValue, scaleLog10); + } + else + { + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/Core/IndirectUI/PropertyControl`1.cs b/src/Core/IndirectUI/PropertyControl`1.cs new file mode 100644 index 0000000..6b0961a --- /dev/null +++ b/src/Core/IndirectUI/PropertyControl`1.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + internal abstract class PropertyControl + : PropertyControl + where TProperty : Property + { + public new TProperty Property + { + get + { + return (TProperty)base.Property; + } + } + + internal PropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + } + } +} diff --git a/src/Core/IndirectUI/SliderPropertyControl.cs b/src/Core/IndirectUI/SliderPropertyControl.cs new file mode 100644 index 0000000..91241e7 --- /dev/null +++ b/src/Core/IndirectUI/SliderPropertyControl.cs @@ -0,0 +1,329 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + internal abstract class SliderPropertyControl + : PropertyControl> + where TValue : struct, IComparable + { + private HeaderLabel header; + private TrackBar slider; + private PdnNumericUpDown numericUpDown; + private Button resetButton; + private Label descriptionText; + + [PropertyControlProperty(DefaultValue = (object)true)] + public bool ShowResetButton + { + get + { + return this.resetButton.Visible; + } + + set + { + this.resetButton.Visible = value; + this.resetButton.AutoSize = value; + PerformLayout(); + } + } + + protected int DecimalPlaces + { + get + { + return this.numericUpDown.DecimalPlaces; + } + + set + { + this.numericUpDown.DecimalPlaces = value; + ResetUIRanges(); + } + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool SliderShowTickMarks + { + get + { + return this.slider.TickStyle != TickStyle.None; + } + + set + { + this.slider.TickStyle = value ? TickStyle.BottomRight : TickStyle.None; + } + } + + protected TValue SliderSmallChange + { + get + { + return FromSliderValue(this.slider.SmallChange); + } + + set + { + this.slider.SmallChange = ToSliderValue(value); + } + } + + protected TValue SliderLargeChange + { + get + { + return FromSliderValue(this.slider.LargeChange); + } + + set + { + this.slider.LargeChange = ToSliderValue(value); + } + } + + protected TValue UpDownIncrement + { + get + { + return FromNudValue(this.numericUpDown.Increment); + } + + set + { + this.numericUpDown.Increment = ToNudValue(value); + } + } + + protected abstract int ToSliderValue(TValue propertyValue); + protected abstract TValue FromSliderValue(int sliderValue); + + protected abstract decimal ToNudValue(TValue propertyValue); + protected abstract TValue FromNudValue(decimal nudValue); + + protected override void OnLayout(LayoutEventArgs levent) + { + int vMargin = UI.ScaleHeight(4); + int hMargin = UI.ScaleWidth(4); + + this.header.Location = new Point(0, 0); + this.header.Width = ClientSize.Width; + this.header.Height = string.IsNullOrEmpty(DisplayName) ? 0 : + this.header.GetPreferredSize(new Size(this.header.Width, 0)).Height; + + this.resetButton.Width = UI.ScaleWidth(20); + this.resetButton.Location = new Point( + ClientSize.Width - this.resetButton.Width, + this.header.Bottom + vMargin); + + int nudWidth = UI.ScaleWidth(70); + + this.numericUpDown.PerformLayout(); + this.numericUpDown.Width = nudWidth; + this.numericUpDown.Location = new Point( + (this.resetButton.Visible ? (this.resetButton.Left - hMargin) : ClientSize.Width) - this.numericUpDown.Width, + this.header.Bottom + vMargin); + + this.resetButton.Height = this.numericUpDown.Height; + + this.slider.Location = new Point(0, this.header.Bottom + vMargin); + this.slider.Size = new Size( + this.numericUpDown.Left - hMargin, + PropertyControlUtil.GetGoodSliderHeight(this.slider)); + + this.descriptionText.Location = new Point( + 0, + (string.IsNullOrEmpty(this.Description) ? 0 : vMargin) + Utility.Max(this.resetButton.Bottom, this.slider.Bottom, this.numericUpDown.Bottom)); + + this.descriptionText.Width = ClientSize.Width; + this.descriptionText.Height = string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : + this.descriptionText.GetPreferredSize(new Size(this.descriptionText.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.descriptionText.Bottom); + + base.OnLayout(levent); + } + + protected override void OnDisplayNameChanged() + { + this.header.Text = this.DisplayName; + base.OnDisplayNameChanged(); + } + + protected override void OnDescriptionChanged() + { + this.descriptionText.Text = this.Description; + base.OnDescriptionChanged(); + } + + private void ValidateUIRanges() + { + try + { + int value1 = ToSliderValue(Property.MinValue); + int value2 = ToSliderValue(Property.MaxValue); + + decimal value3 = ToNudValue(Property.MinValue); + decimal value4 = ToNudValue(Property.MaxValue); + + TValue value5 = FromSliderValue(ToSliderValue(Property.MinValue)); + TValue value6 = FromSliderValue(ToSliderValue(Property.MaxValue)); + + TValue value7 = FromNudValue(ToNudValue(Property.MinValue)); + TValue value8 = FromNudValue(ToNudValue(Property.MaxValue)); + } + + catch (Exception ex) + { + string message = string.Format( + "The property's range, [{0}, {1}], cannot be accomodated. Try a smaller range, or a smaller value for DecimalPlaces.", + Property.MinValue, + Property.MaxValue); + + throw new PdnException(message, ex); + } + } + + protected void ResetUIRanges() + { + this.numericUpDown.Minimum = ToNudValue(Property.MinValue); + this.numericUpDown.Maximum = ToNudValue(Property.MaxValue); + this.slider.Minimum = ToSliderValue(Property.MinValue); + this.slider.Maximum = ToSliderValue(Property.MaxValue); + this.slider.TickFrequency = PropertyControlUtil.GetGoodSliderTickFrequency(this.slider); + } + + public SliderPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + this.header = new HeaderLabel(); + this.slider = new TrackBar(); + this.numericUpDown = new PdnNumericUpDown(); + this.resetButton = new Button(); + this.descriptionText = new Label(); + + this.slider.BeginInit(); + + SuspendLayout(); + + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.numericUpDown.DecimalPlaces = 0; + this.numericUpDown.Name = "numericUpDown"; + this.numericUpDown.TextAlign = HorizontalAlignment.Right; + this.numericUpDown.TabIndex = 1; + + this.slider.Name = "slider"; + this.slider.AutoSize = false; + this.slider.Orientation = Orientation.Horizontal; + this.slider.TabIndex = 0; + this.SliderShowTickMarks = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.SliderShowTickMarks].Value; + + this.resetButton.AutoSize = false; + this.resetButton.Name = "resetButton"; + this.resetButton.FlatStyle = FlatStyle.Standard; + this.resetButton.Click += new EventHandler(ResetButton_Click); + this.resetButton.Image = PdnResources.GetImageResource("Icons.ResetIcon.png").Reference; + this.resetButton.TabIndex = 2; + this.resetButton.Visible = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.ShowResetButton].Value; + this.ToolTip.SetToolTip(this.resetButton, PdnResources.GetString("Form.ResetButton.Text").Replace("&", "")); + + this.descriptionText.Name = "descriptionText"; + this.descriptionText.AutoSize = false; + this.descriptionText.Text = this.Description; + + // In order to make sure that setting the ranges on the controls doesn't affect the property in weird ways, + // we don't set up our ValueChanged handlers until after we set up the controls. + ValidateUIRanges(); + ResetUIRanges(); + + this.numericUpDown.ValueChanged += new EventHandler(NumericUpDown_ValueChanged); + this.slider.ValueChanged += new EventHandler(Slider_ValueChanged); + + Controls.AddRange( + new Control[] + { + this.header, + this.slider, + this.numericUpDown, + this.resetButton, + this.descriptionText + }); + + this.slider.EndInit(); + + ResumeLayout(false); + } + + private void ResetButton_Click(object sender, EventArgs e) + { + Property.Value = (TValue)Property.DefaultValue; + } + + protected override void OnPropertyValueChanged() + { + if (this.numericUpDown.Value != ToNudValue(Property.Value)) + { + decimal newNudValue = ToNudValue(Property.Value); + this.numericUpDown.Value = newNudValue; + } + + if (this.slider.Value != ToSliderValue(Property.Value)) + { + int newSliderValue = ToSliderValue(Property.Value); + int clampedValue = Utility.Clamp(newSliderValue, this.slider.Minimum, this.slider.Maximum); + this.slider.Value = clampedValue; + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.numericUpDown.Enabled = !Property.ReadOnly; + this.slider.Enabled = !Property.ReadOnly; + this.resetButton.Enabled = !Property.ReadOnly; + this.descriptionText.Enabled = !Property.ReadOnly; + } + + private void Slider_ValueChanged(object sender, EventArgs e) + { + if (ToSliderValue(Property.Value) != ToSliderValue(FromSliderValue(this.slider.Value))) + { + TValue fromSliderValue = FromSliderValue(this.slider.Value); + TValue clampedValue = Property.ClampPotentialValue(fromSliderValue); + Property.Value = clampedValue; + } + } + + private void NumericUpDown_ValueChanged(object sender, EventArgs e) + { + if (ToNudValue(Property.Value) != ToNudValue(FromNudValue(this.numericUpDown.Value))) + { + TValue fromNudValue = FromNudValue(this.numericUpDown.Value); + TValue clampedValue = Property.ClampPotentialValue(fromNudValue); + Property.Value = clampedValue; + } + } + + protected override bool OnFirstSelect() + { + this.numericUpDown.Select(); + return true; + } + } +} diff --git a/src/Core/IndirectUI/StaticListDropDownPropertyControl.cs b/src/Core/IndirectUI/StaticListDropDownPropertyControl.cs new file mode 100644 index 0000000..f519d22 --- /dev/null +++ b/src/Core/IndirectUI/StaticListDropDownPropertyControl.cs @@ -0,0 +1,126 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Core; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(StaticListChoiceProperty), PropertyControlType.DropDown)] + internal sealed class StaticListDropDownPropertyControl + : PropertyControl + { + private HeaderLabel header; + private ComboBox comboBox; + private Label descriptionText; + + public StaticListDropDownPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.comboBox = new ComboBox(); + this.comboBox.Name = "comboBox"; + this.comboBox.FlatStyle = FlatStyle.System; + this.comboBox.SelectedIndexChanged += new EventHandler(ComboBox_SelectedIndexChanged); + this.comboBox.DropDownStyle = ComboBoxStyle.DropDownList; + + foreach (object choice in Property.ValueChoices) + { + string valueText = propInfo.GetValueDisplayName(choice); + this.comboBox.Items.Add(valueText); + } + + this.descriptionText = new Label(); + this.descriptionText.Name = "descriptionText"; + this.descriptionText.AutoSize = false; + this.descriptionText.Text = this.Description; + + this.Controls.AddRange( + new Control[] + { + this.header, + this.comboBox, + this.descriptionText + }); + + ResumeLayout(false); + PerformLayout(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vSpacing = UI.ScaleHeight(4); + + this.header.Location = new Point(0, 0); + this.header.Size = string.IsNullOrEmpty(DisplayName) ? + new Size(ClientSize.Width, 0) : + this.header.GetPreferredSize(new Size(ClientSize.Width, 1)); + + this.comboBox.Location = new Point(0, this.header.Bottom + vSpacing); + this.comboBox.Width = ClientSize.Width; + this.comboBox.PerformLayout(); + + this.descriptionText.Location = new Point( + 0, + this.comboBox.Bottom + (string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : vSpacing)); + + this.descriptionText.Width = ClientSize.Width; + this.descriptionText.Height = string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : + this.descriptionText.GetPreferredSize(new Size(this.descriptionText.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.descriptionText.Bottom); + + base.OnLayout(levent); + } + + private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + int valueIndex = Array.IndexOf(Property.ValueChoices, Property.Value); + + if (valueIndex != this.comboBox.SelectedIndex) + { + Property.Value = Property.ValueChoices[this.comboBox.SelectedIndex]; + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.header.Enabled = !Property.ReadOnly; + this.comboBox.Enabled = !Property.ReadOnly; + } + + protected override void OnPropertyValueChanged() + { + int valueIndex = Array.IndexOf(Property.ValueChoices, Property.Value); + + if (this.comboBox.SelectedIndex != valueIndex) + { + this.comboBox.SelectedIndex = valueIndex; + } + } + + protected override bool OnFirstSelect() + { + this.comboBox.Select(); + return true; + } + } +} diff --git a/src/Core/IndirectUI/StaticListRadioButtonPropertyControl.cs b/src/Core/IndirectUI/StaticListRadioButtonPropertyControl.cs new file mode 100644 index 0000000..5c0b97d --- /dev/null +++ b/src/Core/IndirectUI/StaticListRadioButtonPropertyControl.cs @@ -0,0 +1,163 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Core; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(StaticListChoiceProperty), PropertyControlType.RadioButton)] + internal sealed class StaticListRadioButtonPropertyControl + : PropertyControl + { + private HeaderLabel header; + + // radioButtons[i] corresponds to Property.ValueChoices[i] + private RadioButton[] radioButtons; + + private Label descriptionText; + + public StaticListRadioButtonPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + object[] valueChoices = Property.ValueChoices; // cache this to avoid making N copies + this.radioButtons = new RadioButton[valueChoices.Length]; + + for (int i = 0; i < this.radioButtons.Length; ++i) + { + this.radioButtons[i] = new RadioButton(); + this.radioButtons[i].Name = "radioButton" + i.ToString(CultureInfo.InvariantCulture); + this.radioButtons[i].FlatStyle = FlatStyle.System; + this.radioButtons[i].CheckedChanged += new EventHandler(RadioButton_CheckedChanged); + + string valueText = propInfo.GetValueDisplayName(valueChoices[i]); + this.radioButtons[i].Text = valueText; + } + + this.descriptionText = new Label(); + this.descriptionText.Name = "descriptionText"; + this.descriptionText.AutoSize = false; + this.descriptionText.Text = this.Description; + + this.Controls.Add(this.header); + this.Controls.AddRange(this.radioButtons); + this.Controls.Add(this.descriptionText); + + ResumeLayout(false); + PerformLayout(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vSpacing = UI.ScaleHeight(4); + + this.header.Location = new Point(0, 0); + this.header.Size = string.IsNullOrEmpty(DisplayName) ? + new Size(ClientSize.Width, 0) : + this.header.GetPreferredSize(new Size(ClientSize.Width, 1)); + + int currentY = this.header.Bottom; + for (int i = 0; i < this.radioButtons.Length; ++i) + { + this.radioButtons[i].Location = new Point(0, currentY + vSpacing); + this.radioButtons[i].Width = ClientSize.Width; + + LayoutUtility.PerformAutoLayout( + this.radioButtons[i], + AutoSizeStrategy.ExpandHeightToContentAndKeepWidth, + EdgeSnapOptions.SnapLeftEdgeToContainerLeftEdge | + EdgeSnapOptions.SnapRightEdgeToContainerRightEdge); + + currentY = this.radioButtons[i].Bottom; + } + + this.descriptionText.Location = new Point( + 0, + currentY + (string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : vSpacing)); + + this.descriptionText.Width = ClientSize.Width; + this.descriptionText.Height = string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : + this.descriptionText.GetPreferredSize(new Size(this.descriptionText.Width, 1)).Height; + + currentY = this.descriptionText.Bottom; + + ClientSize = new Size(ClientSize.Width, currentY); + + base.OnLayout(levent); + } + + private void RadioButton_CheckedChanged(object sender, EventArgs e) + { + RadioButton senderRB = (RadioButton)sender; + + if (senderRB.Checked) + { + int rbIndex = Array.IndexOf(this.radioButtons, senderRB); + + object value = Property.ValueChoices[rbIndex]; + + if (!Property.Value.Equals(value)) + { + Property.Value = value; + } + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.header.Enabled = !Property.ReadOnly; + + foreach (RadioButton rb in this.radioButtons) + { + rb.Enabled = !Property.ReadOnly; + } + } + + protected override void OnPropertyValueChanged() + { + int valueIndex = Array.IndexOf(Property.ValueChoices, Property.Value); + + if (valueIndex >= 0 && valueIndex < this.radioButtons.Length) + { + if (this.radioButtons[valueIndex].Checked != true) + { + this.radioButtons[valueIndex].Checked = true; + } + } + } + + protected override bool OnFirstSelect() + { + foreach (RadioButton radioButton in this.radioButtons) + { + if (radioButton.Checked) + { + radioButton.Select(); + return true; + } + } + + return false; + } + } +} diff --git a/src/Core/IndirectUI/StringTextBoxPropertyControl.cs b/src/Core/IndirectUI/StringTextBoxPropertyControl.cs new file mode 100644 index 0000000..ea453f6 --- /dev/null +++ b/src/Core/IndirectUI/StringTextBoxPropertyControl.cs @@ -0,0 +1,155 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + [PropertyControlInfo(typeof(StringProperty), PropertyControlType.TextBox, IsDefault = true)] + internal sealed class StringTextBoxPropertyControl + : PropertyControl + { + private HeaderLabel header; + private TextBox textBox; + private Label description; + private int baseTextBoxHeight; + + [PropertyControlProperty(DefaultValue = false)] + public bool Multiline + { + get + { + return this.textBox.Multiline; + } + + set + { + this.textBox.Multiline = value; + this.textBox.AcceptsReturn = value; + PerformLayout(); + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int vMargin = UI.ScaleHeight(4); + int hMargin = UI.ScaleWidth(4); + + this.header.Location = new Point(0, 0); + this.header.Size = string.IsNullOrEmpty(DisplayName) ? + new Size(ClientSize.Width, 0) : + this.header.GetPreferredSize(new Size(ClientSize.Width, 1)); + + this.textBox.Location = new Point(0, this.header.Bottom + hMargin); + this.textBox.Width = ClientSize.Width; + this.textBox.Height = this.textBox.Multiline ? this.baseTextBoxHeight * 4 : this.baseTextBoxHeight; + + this.description.Location = new Point(0, + (string.IsNullOrEmpty(this.Description) ? 0 : vMargin) + this.textBox.Bottom); + + this.description.Width = ClientSize.Width; + this.description.Height = string.IsNullOrEmpty(this.description.Text) ? 0 : + this.description.GetPreferredSize(new Size(this.description.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.description.Bottom); + + base.OnLayout(levent); + } + + public StringTextBoxPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.textBox = new TextBox(); + this.description = new Label(); + + this.header.Name = "header"; + this.header.Text = DisplayName; + this.header.RightMargin = 0; + + this.description.Name = "description"; + this.description.Text = this.Description; + + this.textBox.Name = "textBox"; + this.textBox.TextChanged += new EventHandler(TextBox_TextChanged); + this.textBox.MaxLength = Property.MaxLength; + this.baseTextBoxHeight = this.textBox.Height; + this.Multiline = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.Multiline].Value; + + this.Controls.AddRange( + new Control[] + { + this.header, + this.textBox, + this.description + }); + + ResumeLayout(false); + } + + private void TextBox_TextChanged(object sender, EventArgs e) + { + string newValue; + + if (this.textBox.Text.Length > Property.MaxLength) + { + newValue = this.textBox.Text.Substring(Property.MaxLength); + } + else + { + newValue = this.textBox.Text; + } + + if (Property.Value != newValue) + { + Property.Value = newValue; + } + } + + protected override void OnDisplayNameChanged() + { + this.header.Text = DisplayName; + base.OnDisplayNameChanged(); + } + + protected override void OnDescriptionChanged() + { + this.description.Text = Description; + base.OnDescriptionChanged(); + } + + protected override void OnPropertyReadOnlyChanged() + { + this.textBox.Enabled = !Property.ReadOnly; + this.textBox.ReadOnly = Property.ReadOnly; + this.description.Enabled = !Property.ReadOnly; + } + + protected override void OnPropertyValueChanged() + { + if (this.textBox.Text != Property.Value) + { + this.textBox.Text = Property.Value; + } + } + + protected override bool OnFirstSelect() + { + this.textBox.Select(); + return true; + } + } +} \ No newline at end of file diff --git a/src/Core/IndirectUI/VectorSliderPropertyControl.cs b/src/Core/IndirectUI/VectorSliderPropertyControl.cs new file mode 100644 index 0000000..0e39d6b --- /dev/null +++ b/src/Core/IndirectUI/VectorSliderPropertyControl.cs @@ -0,0 +1,484 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.IndirectUI +{ + internal abstract class VectorSliderPropertyControl + : PropertyControl, VectorProperty> + where TValue : struct, IComparable + { + private int decimalPlaces = 2; + private HeaderLabel header; + private TrackBar sliderX; + private PdnNumericUpDown numericUpDownX; + private Button resetButtonX; + private TrackBar sliderY; + private PdnNumericUpDown numericUpDownY; + private Button resetButtonY; + private Label descriptionText; + + [PropertyControlProperty(DefaultValue = (object)true)] + public bool ShowResetButton + { + get + { + return this.resetButtonX.Visible && this.resetButtonY.Visible; + } + + set + { + this.resetButtonX.Visible = value; + this.resetButtonY.Visible = value; + PerformLayout(); + } + } + + protected virtual void OnDecimalPlacesChanged() + { + ResetUIRanges(); + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool SliderShowTickMarksX + { + get + { + return this.sliderX.TickStyle != TickStyle.None; + } + + set + { + this.sliderX.TickStyle = value ? TickStyle.BottomRight : TickStyle.None; + } + } + + [PropertyControlProperty(DefaultValue = (object)false)] + public bool SliderShowTickMarksY + { + get + { + return this.sliderY.TickStyle != TickStyle.None; + } + + set + { + this.sliderY.TickStyle = value ? TickStyle.BottomRight : TickStyle.None; + } + } + + protected int DecimalPlaces + { + get + { + return this.decimalPlaces; + } + + set + { + this.decimalPlaces = value; + this.numericUpDownX.DecimalPlaces = value; + this.numericUpDownY.DecimalPlaces = value; + OnDecimalPlacesChanged(); + } + } + + protected TValue SliderSmallChangeX + { + get + { + return FromSliderValueX(this.sliderX.SmallChange); + } + + set + { + this.sliderX.SmallChange = ToSliderValueX(value); + } + } + + protected TValue SliderSmallChangeY + { + get + { + return FromSliderValueY(this.sliderY.SmallChange); + } + + set + { + this.sliderY.SmallChange = ToSliderValueY(value); + } + } + + protected TValue SliderLargeChangeX + { + get + { + return FromSliderValueX(this.sliderX.LargeChange); + } + + set + { + this.sliderX.LargeChange = ToSliderValueX(value); + } + } + + protected TValue SliderLargeChangeY + { + get + { + return FromSliderValueY(this.sliderY.LargeChange); + } + + set + { + this.sliderY.LargeChange = ToSliderValueY(value); + } + } + + protected TValue UpDownIncrementX + { + get + { + return FromNudValueX(this.numericUpDownX.Increment); + } + + set + { + this.numericUpDownX.Increment = ToNudValueX(value); + } + } + + protected TValue UpDownIncrementY + { + get + { + return FromNudValueY(this.numericUpDownY.Increment); + } + + set + { + this.numericUpDownY.Increment = ToNudValueY(value); + } + } + + protected abstract int ToSliderValueX(TValue propertyValue); + protected abstract TValue FromSliderValueX(int sliderValue); + protected abstract decimal ToNudValueX(TValue propertyValue); + protected abstract TValue FromNudValueX(decimal nudValue); + + protected abstract int ToSliderValueY(TValue propertyValue); + protected abstract TValue FromSliderValueY(int sliderValue); + protected abstract decimal ToNudValueY(TValue propertyValue); + protected abstract TValue FromNudValueY(decimal nudValue); + + protected abstract TValue RoundPropertyValue(TValue value); + + protected override void OnLayout(LayoutEventArgs levent) + { + int vMargin = UI.ScaleHeight(4); + int hMargin = UI.ScaleWidth(4); + + this.header.Location = new Point(0, 0); + this.header.Width = ClientSize.Width; + this.header.Height = string.IsNullOrEmpty(DisplayName) ? 0 : this.header.GetPreferredSize(new Size(this.header.Width, 0)).Height; + + int nudWidth = UI.ScaleWidth(70); + + // X slider, nud, reset button + int xTop = this.header.Bottom + vMargin; + + this.resetButtonX.Width = UI.ScaleWidth(20); + this.resetButtonX.Location = new Point( + ClientSize.Width - this.resetButtonX.Width, + xTop); + + this.numericUpDownX.PerformLayout(); + this.numericUpDownX.Width = nudWidth; + this.numericUpDownX.Location = new Point( + (this.resetButtonX.Visible ? (this.resetButtonX.Left - hMargin) : ClientSize.Width) - this.numericUpDownX.Width, + xTop); + + this.resetButtonX.Height = this.numericUpDownX.Height; + + this.sliderX.Location = new Point(0, xTop); + this.sliderX.Size = new Size( + this.numericUpDownX.Left - hMargin, + PropertyControlUtil.GetGoodSliderHeight(this.sliderX)); + + // Y slider, nud, reset button + int yTop = vMargin + Utility.Max(this.resetButtonX.Bottom, this.numericUpDownX.Bottom, this.sliderX.Bottom); + + this.resetButtonY.Width = UI.ScaleWidth(20); + this.resetButtonY.Location = new Point( + ClientSize.Width - this.resetButtonY.Width, + yTop); + + this.numericUpDownY.PerformLayout(); + this.numericUpDownY.Width = nudWidth; + this.numericUpDownY.Location = new Point( + (this.resetButtonY.Visible ? (this.resetButtonY.Left - hMargin) : ClientSize.Width) - this.numericUpDownY.Width, + yTop); + + this.resetButtonY.Height = this.numericUpDownY.Height; + + this.sliderY.Location = new Point(0, yTop); + this.sliderY.Size = new Size( + this.numericUpDownY.Left - hMargin, + PropertyControlUtil.GetGoodSliderHeight(this.sliderY)); + + // Description + this.descriptionText.Location = new Point(0, Utility.Max(this.resetButtonY.Bottom, this.sliderY.Bottom, this.numericUpDownY.Bottom)); + this.descriptionText.Width = ClientSize.Width; + this.descriptionText.Height = string.IsNullOrEmpty(this.descriptionText.Text) ? 0 : + this.descriptionText.GetPreferredSize(new Size(this.descriptionText.Width, 1)).Height; + + ClientSize = new Size(ClientSize.Width, this.descriptionText.Bottom); + + base.OnLayout(levent); + } + + protected override void OnDisplayNameChanged() + { + this.header.Text = this.DisplayName; + base.OnDisplayNameChanged(); + } + + protected override void OnDescriptionChanged() + { + this.descriptionText.Text = this.Description; + base.OnDescriptionChanged(); + } + + protected void ResetUIRanges() + { + this.sliderX.Minimum = ToSliderValueX(Property.MinValueX); + this.sliderX.Maximum = ToSliderValueX(Property.MaxValueX); + this.sliderX.TickFrequency = PropertyControlUtil.GetGoodSliderTickFrequency(this.sliderX); + + this.numericUpDownX.Minimum = ToNudValueX(Property.MinValueX); + this.numericUpDownX.Maximum = ToNudValueX(Property.MaxValueX); + + this.sliderY.Minimum = ToSliderValueY(Property.MinValueY); + this.sliderY.Maximum = ToSliderValueY(Property.MaxValueY); + this.sliderY.TickFrequency = PropertyControlUtil.GetGoodSliderTickFrequency(this.sliderY); + + this.numericUpDownY.Minimum = ToNudValueY(Property.MinValueY); + this.numericUpDownY.Maximum = ToNudValueY(Property.MaxValueY); + } + + private void ValidateUIRanges() + { + try + { + int value1 = ToSliderValueX(Property.MinValueX); + int value2 = ToSliderValueX(Property.MaxValueX); + int value3 = ToSliderValueY(Property.MinValueY); + int value4 = ToSliderValueY(Property.MaxValueY); + + decimal value5 = ToNudValueX(Property.MinValueX); + decimal value6 = ToNudValueX(Property.MaxValueX); + decimal value7 = ToNudValueY(Property.MinValueY); + decimal value8 = ToNudValueY(Property.MaxValueY); + + TValue value9 = FromSliderValueX(ToSliderValueX(Property.MinValueX)); + TValue value10 = FromSliderValueX(ToSliderValueX(Property.MaxValueX)); + TValue value11 = FromSliderValueY(ToSliderValueY(Property.MinValueY)); + TValue value12 = FromSliderValueY(ToSliderValueY(Property.MaxValueY)); + } + + catch (Exception ex) + { + string message = string.Format( + "The property's range, [({0},{1}), ({2},{3})], cannot be accomodated. Try a smaller range, or a smaller value for DecimalPlaces.", + Property.MinValueX, + Property.MinValueY, + Property.MaxValueX, + Property.MaxValueY); + + throw new PdnException(message, ex); + } + } + + public VectorSliderPropertyControl(PropertyControlInfo propInfo) + : base(propInfo) + { + SuspendLayout(); + + this.header = new HeaderLabel(); + this.sliderX = new TrackBar(); + this.numericUpDownX = new PdnNumericUpDown(); + this.resetButtonX = new Button(); + this.sliderY = new TrackBar(); + this.numericUpDownY = new PdnNumericUpDown(); + this.resetButtonY = new Button(); + this.descriptionText = new Label(); + + this.header.Name = "header"; + this.header.RightMargin = 0; + this.header.Text = this.DisplayName; + + this.sliderX.Name = "sliderX"; + this.sliderX.AutoSize = false; + this.sliderX.ValueChanged += new EventHandler(SliderX_ValueChanged); + this.sliderX.Orientation = Orientation.Horizontal; + this.SliderShowTickMarksX = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.SliderShowTickMarksX].Value; + + this.numericUpDownX.Name = "numericUpDownX"; + this.numericUpDownX.ValueChanged += new EventHandler(NumericUpDownX_ValueChanged); + this.numericUpDownX.TextAlign = HorizontalAlignment.Right; + + this.resetButtonX.Name = "resetButtonX"; + this.resetButtonX.AutoSize = false; + this.resetButtonX.FlatStyle = FlatStyle.Standard; + this.resetButtonX.Click += new EventHandler(ResetButtonX_Click); + this.resetButtonX.Image = PdnResources.GetImageResource("Icons.ResetIcon.png").Reference; + this.resetButtonX.Visible = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.ShowResetButton].Value; + this.ToolTip.SetToolTip(this.resetButtonX, PdnResources.GetString("Form.ResetButton.Text").Replace("&", "")); + + this.sliderY.Name = "sliderY"; + this.sliderY.AutoSize = false; + this.sliderY.ValueChanged += new EventHandler(SliderY_ValueChanged); + this.sliderY.Orientation = Orientation.Horizontal; + this.SliderShowTickMarksY = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.SliderShowTickMarksY].Value; + + this.numericUpDownY.Name = "numericUpDownY"; + this.numericUpDownY.ValueChanged += new EventHandler(NumericUpDownY_ValueChanged); + this.numericUpDownY.TextAlign = HorizontalAlignment.Right; + + this.resetButtonY.Name = "resetButtonY"; + this.resetButtonY.AutoSize = false; + this.resetButtonY.FlatStyle = FlatStyle.Standard; + this.resetButtonY.Click += new EventHandler(ResetButtonY_Click); + this.resetButtonY.Image = PdnResources.GetImageResource("Icons.ResetIcon.png").Reference; + this.resetButtonY.Visible = (bool)propInfo.ControlProperties[ControlInfoPropertyNames.ShowResetButton].Value; + this.ToolTip.SetToolTip(this.resetButtonY, PdnResources.GetString("Form.ResetButton.Text").Replace("&", "")); + + this.descriptionText.Name = "descriptionText"; + this.descriptionText.AutoSize = false; + this.descriptionText.Text = this.Description; + + ValidateUIRanges(); + + ResetUIRanges(); + + Controls.AddRange( + new Control[] + { + this.header, + this.sliderX, + this.numericUpDownX, + this.resetButtonX, + this.sliderY, + this.numericUpDownY, + this.resetButtonY, + this.descriptionText + }); + + ResumeLayout(false); + } + + protected override void OnTextChanged(EventArgs e) + { + this.header.Text = this.Text; + base.OnTextChanged(e); + } + + private void ResetButtonX_Click(object sender, EventArgs e) + { + Property.ValueX = Property.DefaultValueX; + } + + private void ResetButtonY_Click(object sender, EventArgs e) + { + Property.ValueY = Property.DefaultValueY; + } + + private bool IsEqualTo(TValue lhs, TValue rhs) + { + return ScalarProperty.IsEqualTo(lhs, rhs); + } + + protected override void OnPropertyValueChanged() + { + if (!IsEqualTo(RoundPropertyValue(FromNudValueX(this.numericUpDownX.Value)), RoundPropertyValue(Property.ValueX))) + { + this.numericUpDownX.Value = ToNudValueX(RoundPropertyValue(Property.ValueX)); + } + + if (this.sliderX.Value != ToSliderValueX(RoundPropertyValue(Property.ValueX))) + { + this.sliderX.Value = ToSliderValueX(RoundPropertyValue(Property.ValueX)); + } + + if (!IsEqualTo(RoundPropertyValue(FromNudValueY(this.numericUpDownY.Value)), RoundPropertyValue(Property.ValueY))) + { + this.numericUpDownY.Value = ToNudValueY(RoundPropertyValue(Property.ValueY)); + } + + if (this.sliderY.Value != ToSliderValueY(RoundPropertyValue(Property.ValueY))) + { + this.sliderY.Value = ToSliderValueY(RoundPropertyValue(Property.ValueY)); + } + } + + protected override void OnPropertyReadOnlyChanged() + { + this.numericUpDownX.Enabled = !Property.ReadOnly; + this.sliderX.Enabled = !Property.ReadOnly; + this.resetButtonX.Enabled = !Property.ReadOnly; + this.numericUpDownY.Enabled = !Property.ReadOnly; + this.sliderY.Enabled = !Property.ReadOnly; + this.resetButtonY.Enabled = !Property.ReadOnly; + } + + private void SliderX_ValueChanged(object sender, EventArgs e) + { + if (ToSliderValueX(Property.ValueX) != ToSliderValueX(FromSliderValueX(this.sliderX.Value))) + { + Property.ValueX = FromSliderValueX(this.sliderX.Value); + } + } + + private void NumericUpDownX_ValueChanged(object sender, EventArgs e) + { + if (!IsEqualTo(RoundPropertyValue(Property.ValueX), RoundPropertyValue(FromNudValueX(this.numericUpDownX.Value)))) + { + Property.ValueX = RoundPropertyValue(FromNudValueX(this.numericUpDownX.Value)); + } + } + + private void SliderY_ValueChanged(object sender, EventArgs e) + { + if (ToSliderValueY(Property.ValueY) != ToSliderValueY(FromSliderValueY(this.sliderY.Value))) + { + Property.ValueY = FromSliderValueY(this.sliderY.Value); + } + } + + private void NumericUpDownY_ValueChanged(object sender, EventArgs e) + { + if (!IsEqualTo(RoundPropertyValue(Property.ValueY), RoundPropertyValue(FromNudValueY(this.numericUpDownY.Value)))) + { + Property.ValueY = RoundPropertyValue(FromNudValueY(this.numericUpDownY.Value)); + } + } + + protected override bool OnFirstSelect() + { + this.numericUpDownX.Select(); + return true; + } + } +} diff --git a/src/Core/IrregularSurface.cs b/src/Core/IrregularSurface.cs new file mode 100644 index 0000000..954bed1 --- /dev/null +++ b/src/Core/IrregularSurface.cs @@ -0,0 +1,265 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Drawing; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + /// + /// Defines a surface that is irregularly shaped, defined by a Region. + /// Works by containing an array of PlacedSurface instances. + /// Similar to IrregularImage, but works with Surface objects instead. + /// Instances of this class are immutable once created. + /// + [Serializable] + public sealed class IrregularSurface + : ISurfaceDraw, + IDisposable, + ICloneable, + IDeserializationCallback + { + private ArrayList placedSurfaces; + + [NonSerialized] + private PdnRegion region; + + /// + /// The Region that the irregular image fills. + /// + public PdnRegion Region + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("IrregularSurface"); + } + + return this.region; + } + } + + /// + /// Constructs an IrregularSurface by copying the given region-of-interest from an Image. + /// + /// The Surface to copy pixels from. + /// Defines the Region from which to copy pixels from the Image. + public IrregularSurface (Surface source, PdnRegion roi) + { + PdnRegion roiClipped = (PdnRegion)roi.Clone(); + roiClipped.Intersect(source.Bounds); + + Rectangle[] rects = roiClipped.GetRegionScansReadOnlyInt(); + this.placedSurfaces = new ArrayList(rects.Length); + + foreach (Rectangle rect in rects) + { + this.placedSurfaces.Add(new PlacedSurface(source, rect)); + } + + this.region = roiClipped; + } + + public IrregularSurface (Surface source, RectangleF[] roi) + { + this.placedSurfaces = new ArrayList(roi.Length); + + foreach (RectangleF rectF in roi) + { + RectangleF ri = RectangleF.Intersect(source.Bounds, rectF); + + if (!ri.IsEmpty) + { + this.placedSurfaces.Add(new PlacedSurface(source, Rectangle.Truncate(ri))); + } + } + + this.region = Utility.RectanglesToRegion(roi); + this.region.Intersect(source.Bounds); + } + + public IrregularSurface(Surface source, Rectangle[] roi) + { + this.placedSurfaces = new ArrayList(roi.Length); + + foreach (Rectangle rect in roi) + { + Rectangle ri = Rectangle.Intersect(source.Bounds, rect); + + if (!ri.IsEmpty) + { + this.placedSurfaces.Add(new PlacedSurface(source, ri)); + } + } + + this.region = Utility.RectanglesToRegion(roi); + this.region.Intersect(source.Bounds); + } + + /// + /// Constructs an IrregularSurface by copying the given rectangle-of-interest from an Image. + /// + /// The Surface to copy pixels from. + /// Defines the Rectangle from which to copy pixels from the Image. + public IrregularSurface (Surface source, Rectangle roi) + { + this.placedSurfaces = new ArrayList(); + this.placedSurfaces.Add(new PlacedSurface(source, roi)); + this.region = new PdnRegion(roi); + } + + private IrregularSurface (IrregularSurface cloneMe) + { + this.placedSurfaces = new ArrayList(cloneMe.placedSurfaces.Count); + + foreach (PlacedSurface ps in cloneMe.placedSurfaces) + { + this.placedSurfaces.Add(ps.Clone()); + } + + this.region = (PdnRegion)cloneMe.Region.Clone(); + } + + ~IrregularSurface() + { + Dispose(false); + } + + /// + /// Draws the IrregularSurface on to the given Surface. + /// + /// The Surface to draw to. + public void Draw(Surface dst) + { + if (disposed) + { + throw new ObjectDisposedException("IrregularSurface"); + } + + foreach (PlacedSurface ps in placedSurfaces) + { + ps.Draw(dst); + } + } + + public void Draw(Surface dst, IPixelOp pixelOp) + { + if (this.disposed) + { + throw new ObjectDisposedException("IrregularSurface"); + } + + foreach (PlacedSurface ps in this.placedSurfaces) + { + ps.Draw(dst, pixelOp); + } + } + + /// + /// Draws the IrregularSurface on to the given Surface starting at the given (x,y) offset. + /// + /// The Surface to draw to. + /// The value to be added to every X coordinate that is used for drawing. + /// The value to be added to every Y coordinate that is used for drawing. + public void Draw(Surface dst, int tX, int tY) + { + if (this.disposed) + { + throw new ObjectDisposedException("IrregularSurface"); + } + + foreach (PlacedSurface ps in this.placedSurfaces) + { + ps.Draw(dst, tX, tY); + } + } + + public void Draw(Surface dst, int tX, int tY, IPixelOp pixelOp) + { + if (this.disposed) + { + throw new ObjectDisposedException("IrregularSurface"); + } + + foreach (PlacedSurface ps in this.placedSurfaces) + { + ps.Draw(dst, tX, tY, pixelOp); + } + } + + #region IDisposable Members + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!this.disposed) + { + // TODO: FXCOP: call Dispose() on this.region + + this.disposed = true; + + if (disposing) + { + foreach (PlacedSurface ps in this.placedSurfaces) + { + ps.Dispose(); + } + + this.placedSurfaces.Clear(); + this.placedSurfaces = null; + } + } + } + #endregion + + #region ICloneable Members + + /// + /// Clones the IrregularSurface. + /// + /// A copy of the current state of this PlacedSurface. + public object Clone() + { + if (disposed) + { + throw new ObjectDisposedException("IrregularSurface"); + } + + return new IrregularSurface(this); + } + #endregion + + #region IDeserializationCallback Members + + public void OnDeserialization(object sender) + { + region = PdnRegion.CreateEmpty(); + + Rectangle[] rects = new Rectangle[placedSurfaces.Count]; + + for (int i = 0; i < placedSurfaces.Count; ++i) + { + rects[i] = ((PlacedSurface)placedSurfaces[i]).Bounds; + } + + region = Utility.RectanglesToRegion(rects); + } + + #endregion + } +} diff --git a/src/Core/LayoutUtility.cs b/src/Core/LayoutUtility.cs new file mode 100644 index 0000000..c5c5717 --- /dev/null +++ b/src/Core/LayoutUtility.cs @@ -0,0 +1,71 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public static class LayoutUtility + { + public static void PerformAutoLayout( + System.Windows.Forms.ButtonBase button, + AutoSizeStrategy autoSizeLayoutStrategy, + EdgeSnapOptions edgeSnapOptions) + { + Control container = button.Parent; + + // Make sure no extra, not-yet-defined options were snuck in + EdgeSnapOptions allEdgeSnapOptions = EdgeSnapOptions.SnapLeftEdgeToContainerLeftEdge | EdgeSnapOptions.SnapRightEdgeToContainerRightEdge; + if (0 != (edgeSnapOptions & ~allEdgeSnapOptions)) + { + throw new InvalidEnumArgumentException("edgeSnapOptions"); + } + + if ((edgeSnapOptions & EdgeSnapOptions.SnapLeftEdgeToContainerLeftEdge) != 0) + { + int oldLeft = button.Left; + button.Left = 0; + button.Width += oldLeft; + } + + if (container != null && (edgeSnapOptions & EdgeSnapOptions.SnapRightEdgeToContainerRightEdge) != 0) + { + button.Width = container.Width - button.Left; + } + + switch (autoSizeLayoutStrategy) + { + case AutoSizeStrategy.AutoHeightAndExpandWidthToContent: + button.Size = button.GetPreferredSize(new Size(0, 0)); + break; + + case AutoSizeStrategy.ExpandHeightToContentAndKeepWidth: + if (button.Width != 0) + { + Size preferredSizeP = button.GetPreferredSize(new Size(button.Width, 1)); + Size preferredSize = new Size((preferredSizeP.Width * 11) / 10, preferredSizeP.Height); // add 10% padding + + int lineHeight = preferredSize.Height; + int overageScale = (preferredSize.Width + (button.Width - 1)) / button.Width; + button.Height = lineHeight * overageScale; + } + break; + + case AutoSizeStrategy.None: + break; + + default: + throw new InvalidEnumArgumentException("autoSizeLayoutStrategy"); + } + } + } +} diff --git a/src/Core/List.cs b/src/Core/List.cs new file mode 100644 index 0000000..ecbed58 --- /dev/null +++ b/src/Core/List.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// A very simple linked-list class, done functional style. Use null for + /// the tail to indicate the end of a list. + /// + public sealed class List + { + private object head; + public object Head + { + get + { + return head; + } + } + + private List tail; + public List Tail + { + get + { + return tail; + } + } + + public List(object head, List tail) + { + this.head = head; + this.tail = tail; + } + } +} diff --git a/src/Core/MaskedSurface.cs b/src/Core/MaskedSurface.cs new file mode 100644 index 0000000..4317440 --- /dev/null +++ b/src/Core/MaskedSurface.cs @@ -0,0 +1,658 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; +using System.Threading; + +namespace PaintDotNet +{ + /// + /// Defines a surface that is irregularly shaped, defined by a Region. + /// Works by encapsulating a Surface that that is the size of the region's + /// bounding box, and then storing the region to mask drawing operations. + /// Similar to IrregularImage, makes working with transformations much + /// easier. + /// Instances of this class are immutable once created. + /// This class is not thread-safe, and its properties and fields must only + /// be executing in one thread at a time. However, it may be serialized + /// by one thread while being accessed in the aforementioned manner by + /// another thread. It may not be serialized by more than one thread at once. + /// + [Serializable] + public sealed class MaskedSurface + : ICloneable, + IDisposable, + IDeserializationCallback + { + private bool disposed = false; + private Surface surface; + + // Use one of these + private PdnRegion region; + + // Whenever you set this field, you must Clone() it and store that into shadowPath -- + // in other words, use SetPathField() and never set this field directly + // And, never call methods or properties of the path field's object, always use shadowPath. + // (it is fine to do a null check on path) + private PdnGraphicsPath path; + + private void SetPathField(PdnGraphicsPath newPath) + { + this.path = newPath; + this.shadowPath = newPath.Clone(); + } + + // We create one copy of the path so that when we go to Draw(), we can Clone() it without + // running into a race condition. PdnGraphicsPath is not thread safe, and the MoveTool + // is going the performant route of serializing data in the background while continuing + // on with its work. This work includes calling our Draw() method which then Clone()s the + // path while the path is still being serialized. Since GDI+ is fussy about cross-thread + // use of its objects, it throws an exception. + // This does not introduce thread safety into PdnGraphicsPath, but it does relieve a certain + // amount of transitive responsibility from users of this class. + [NonSerialized] + private PdnGraphicsPath shadowPath; + + [NonSerialized] + private static PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(); + + /// + /// Do not modify the surface. Treat it as immutable. + /// + public Surface SurfaceReadOnly + { + get + { + return this.surface; + } + } + + public bool IsDisposed + { + get + { + return this.disposed; + } + } + + private PdnRegion GetRegion() + { + if (this.region == null) + { + this.region = new PdnRegion(this.shadowPath); + } + + return this.region; + } + + public PdnRegion CreateRegion() + { + if (this.disposed) + { + throw new ObjectDisposedException("MaskedSurface"); + } + + return GetRegion().Clone(); + } + + private PdnGraphicsPath GetPath() + { + if (this.path == null) + { + // TODO: FromRegion() is a VERY expensive call! + PdnGraphicsPath newPath = PdnGraphicsPath.FromRegion(this.region); + SetPathField(newPath); + } + + return this.shadowPath; + } + + public PdnGraphicsPath CreatePath() + { + if (this.disposed) + { + throw new ObjectDisposedException("MaskedSurface"); + } + + return GetPath().Clone(); + } + + private MaskedSurface() + { + } + + /// + /// Constructs a MaskSurface by copying the given region-of-interest from an Image. + /// + /// The Surface to copy pixels from. + /// Defines the Region from which to copy pixels from the Image. + public MaskedSurface(Surface source, PdnRegion roi) + { + PdnRegion roiClipped = (PdnRegion)roi.Clone(); + roiClipped.Intersect(source.Bounds); + + Rectangle boundsClipped = roiClipped.GetBoundsInt(); + this.surface = new Surface(boundsClipped.Size); + this.surface.Clear(ColorBgra.FromUInt32(0x00ffffff)); + + Rectangle rect = boundsClipped; + Point dstOffset = new Point(rect.X - boundsClipped.X, rect.Y - boundsClipped.Y); + this.surface.CopySurface(source, dstOffset, rect); + + this.region = roiClipped; + // TODO: FromRegion() is a VERY expensive call for what we are doing! + PdnGraphicsPath newPath = PdnGraphicsPath.FromRegion(this.region); + SetPathField(newPath); + } + + public MaskedSurface(Surface source, PdnGraphicsPath path) + { + RectangleF boundsF = path.GetBounds(); + Rectangle bounds = Utility.RoundRectangle(boundsF); + + Rectangle boundsClipped = Rectangle.Intersect(bounds, source.Bounds); + Rectangle boundsRead; + + if (bounds != boundsClipped) + { + PdnRegion region = new PdnRegion(path); + region.Intersect(source.Bounds); + SetPathField(PdnGraphicsPath.FromRegion(region)); + this.region = region; + boundsRead = region.GetBoundsInt(); + } + else + { + SetPathField(path.Clone()); + this.region = new PdnRegion(this.path); + boundsRead = boundsClipped; + } + + if (boundsRead.Width > 0 && boundsRead.Height > 0) + { + this.surface = new Surface(boundsRead.Size); + this.surface.CopySurface(source, boundsRead); + } + else + { + this.surface = null; + } + } + + public void OnDeserialization(object sender) + { + threadPool = new PaintDotNet.Threading.ThreadPool(); + + if (this.path != null) + { + this.shadowPath = this.path.Clone(); + } + } + + public MaskedSurface Clone() + { + if (this.disposed) + { + throw new ObjectDisposedException("MaskedSurface"); + } + + MaskedSurface ms = new MaskedSurface(); + + if (this.region != null) + { + ms.region = this.region.Clone(); + } + + if (this.path != null) + { + ms.SetPathField(this.shadowPath.Clone()); + } + + if (this.surface != null) + { + ms.surface = this.surface.Clone(); + } + + return ms; + } + + object ICloneable.Clone() + { + return Clone(); + } + + ~MaskedSurface() + { + Dispose(false); + } + + // Constants that define fixed-point precision and arithmetic + private const int fp_ShiftFactor = 14; + private const float fp_MultFactor = (float)(1 << fp_ShiftFactor); + private const float fp_MaxValue = (float)((1 << (31 - fp_ShiftFactor)) - 1); + private const int fp_RoundFactor = ((1 << fp_ShiftFactor) >> 1) - 1; + + private class DrawContext + { + public Surface src; + public Surface dst; + public float dsxddx; + public float dsyddx; + public float dsxddy; + public float dsyddy; + public int fp_dsxddx; + public int fp_dsyddx; + public int fp_dsxddy; + public int fp_dsyddy; + public Rectangle[] dstScans; + public Matrix[] inverses; + public int boundsX; + public int boundsY; + + private static int Clamp(int x, int min, int max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + public unsafe void DrawScansNearestNeighbor(object cpuNumberObj) + { + int cpuNumber = (int)cpuNumberObj; + int inc = Processor.LogicalCpuCount; + void* scan0 = src.Scan0.VoidStar; + int stride = src.Stride; + PointF[] pts = new PointF[1]; + + for (int i = cpuNumber; i < this.dstScans.Length; i += inc) + { + Rectangle dstRect = this.dstScans[i]; + + dstRect.Intersect(dst.Bounds); + + if (dstRect.Width == 0 || dstRect.Height == 0) + { + continue; + } + + pts[0] = new PointF(dstRect.Left, dstRect.Top); + + this.inverses[cpuNumber].TransformPoints(pts); + + pts[0].X -= this.boundsX; + pts[0].Y -= this.boundsY; + + int fp_srcPtRowX = (int)(pts[0].X * fp_MultFactor); + int fp_srcPtRowY = (int)(pts[0].Y * fp_MultFactor); + + for (int dstY = dstRect.Top; dstY < dstRect.Bottom; ++dstY) + { + int fp_srcPtColX = fp_srcPtRowX; + int fp_srcPtColY = fp_srcPtRowY; + fp_srcPtRowX += this.fp_dsxddy; + fp_srcPtRowY += this.fp_dsyddy; + + if (dstY >= 0) + { + // We render the left side, then the right side, then the in-between pixels. + // The reason for this is that the left and right sides have the chance that, + // due to lack of enough precision, we will dive off the end of the surface + // and into memory that we can't actually read from. To solve this, the left + // and right sides will clamp the pixel coordinates that they read from, + // and then keep track of when they were able to stomp clamping these values. + // Then, rendering the middle part of the scanline is able to be completed + // without the expensive clamping operation. + + int dstX = dstRect.Left; + ColorBgra* dstPtr = dst.GetPointAddress(dstX, dstY); + ColorBgra* dstPtrEnd = dstPtr + dstRect.Width; + int fp_srcPtColLastX = fp_srcPtColX + (this.fp_dsxddx * (dstRect.Width - 1)); + int fp_srcPtColLastY = fp_srcPtColY + (this.fp_dsyddx * (dstRect.Width - 1)); + + // Left side + while (dstPtr < dstPtrEnd) + { + int srcPtColX = (fp_srcPtColX + fp_RoundFactor) >> fp_ShiftFactor; + int srcPtColY = (fp_srcPtColY + fp_RoundFactor) >> fp_ShiftFactor; + + int srcX = Clamp(srcPtColX, 0, src.Width - 1); + int srcY = Clamp(srcPtColY, 0, src.Height - 1); + *dstPtr = this.src.GetPointUnchecked(srcX, srcY); + + ++dstPtr; + fp_srcPtColX += this.fp_dsxddx; + fp_srcPtColY += this.fp_dsyddx; + + if (srcX == srcPtColX && srcY == srcPtColY) + { + break; + } + } + + ColorBgra* startFastPtr = dstPtr; + dstPtr = dstPtrEnd - 1; + + // Right side + while (dstPtr >= startFastPtr) + { + int srcPtColX = (fp_srcPtColLastX + fp_RoundFactor) >> fp_ShiftFactor; + int srcPtColY = (fp_srcPtColLastY + fp_RoundFactor) >> fp_ShiftFactor; + + int srcX = Clamp(srcPtColX, 0, src.Width - 1); + int srcY = Clamp(srcPtColY, 0, src.Height - 1); + *dstPtr = this.src.GetPointUnchecked(srcX, srcY); + + if (srcX == srcPtColX && srcY == srcPtColY) + { + break; + } + + --dstPtr; + fp_srcPtColLastX -= this.fp_dsxddx; + fp_srcPtColLastY -= this.fp_dsyddx; + } + + ColorBgra* endFastPtr = dstPtr; + + // Middle + while (startFastPtr < endFastPtr) + { + int srcPtColX = (fp_srcPtColX + fp_RoundFactor) >> fp_ShiftFactor; + int srcPtColY = (fp_srcPtColY + fp_RoundFactor) >> fp_ShiftFactor; + + // This is GetPointUnchecked inlined -- avoid the overhead, especially, of the call to MemoryBlock.VoidStar + // which has the potential to throw an exception which is then NOT inlined. + startFastPtr->Bgra = (unchecked(srcPtColX + (ColorBgra *)(((byte *)scan0) + (srcPtColY * stride))))->Bgra; + + ++startFastPtr; + fp_srcPtColX += this.fp_dsxddx; + fp_srcPtColY += this.fp_dsyddx; + } + } + } + } + } + + public unsafe void DrawScansBilinear(object cpuNumberObj) + { + int cpuNumber = (int)cpuNumberObj; + int inc = Processor.LogicalCpuCount; + PointF[] pts2 = new PointF[1]; + + for (int i = cpuNumber; i < this.dstScans.Length; i += inc) + { + Rectangle dstRect = this.dstScans[i]; + dstRect.Intersect(dst.Bounds); + + pts2[0] = new PointF(dstRect.Left, dstRect.Top); + + this.inverses[cpuNumber].TransformPoints(pts2); + + pts2[0].X -= this.boundsX; + pts2[0].Y -= this.boundsY; + + // Sometimes pts2 ends up being infintessimally small (1 x 10^-5 or -6) but negative + // This throws off GetBilinearSample and it returns transparent colors, which looks horribly wrong. + // So we fix that! + if (pts2[0].X < 0) + { + pts2[0].X = 0; + } + + if (pts2[0].Y < 0) + { + pts2[0].Y = 0; + } + + PointF srcPtRow = pts2[0]; + + for (int dstY = dstRect.Top; dstY < dstRect.Bottom; ++dstY) + { + PointF srcPtCol = srcPtRow; + srcPtRow.X += this.dsxddy; + srcPtRow.Y += this.dsyddy; + + if (dstY >= 0) + { + int dstX = dstRect.Left; + + while (dstX < dstRect.Right) + { + ColorBgra srcPixel = this.src.GetBilinearSample(srcPtCol.X, srcPtCol.Y); + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(dstX, dstY); + *dstPtr = srcPixel; + + srcPtCol.X += this.dsxddx; + srcPtCol.Y += this.dsyddx; + ++dstX; + } + } + } + } + } + } + + public void Draw(Surface dst) + { + Draw(dst, 0, 0); + } + + public void Draw(Surface dst, int tX, int tY) + { + if (this.disposed) + { + throw new ObjectDisposedException("MaskedSurface"); + } + + using (Matrix m = new Matrix()) + { + m.Reset(); + m.Translate(tX, tY, MatrixOrder.Append); + Draw(dst, m, ResamplingAlgorithm.Bilinear); + } + } + + public unsafe void Draw(Surface dst, Matrix transform, ResamplingAlgorithm sampling) + { + if (this.disposed) + { + throw new ObjectDisposedException("MaskedSurface"); + } + + if (this.surface == null || !transform.IsInvertible) + { + return; + } + + PdnRegion theRegion; + Rectangle regionBounds; + + if (this.path == null) + { + theRegion = this.region.Clone(); + regionBounds = this.region.GetBoundsInt(); + theRegion.Transform(transform); + } + else + { + using (PdnGraphicsPath mPath = this.shadowPath.Clone()) + { + regionBounds = Rectangle.Truncate(mPath.GetBounds()); + mPath.Transform(transform); + theRegion = new PdnRegion(mPath); + } + } + + DrawContext dc = new DrawContext(); + + dc.boundsX = regionBounds.X; + dc.boundsY = regionBounds.Y; + + Matrix inverse = transform.Clone(); + inverse.Invert(); + + dc.inverses = new Matrix[Processor.LogicalCpuCount]; + for (int i = 0; i < dc.inverses.Length; ++i) + { + dc.inverses[i] = inverse.Clone(); + } + + // change in source-[X|Y] w.r.t. destination-[X|Y] + PointF[] pts = new PointF[] { + new PointF(1, 0), + new PointF(0, 1) + }; + + inverse.TransformVectors(pts); + inverse.Dispose(); + inverse = null; + + dc.dsxddx = pts[0].X; + + if (Math.Abs(dc.dsxddx) > fp_MaxValue) + { + dc.dsxddx = 0.0f; + } + + dc.dsyddx = pts[0].Y; + + if (Math.Abs(dc.dsyddx) > fp_MaxValue) + { + dc.dsyddx = 0.0f; + } + + dc.dsxddy = pts[1].X; + + if (Math.Abs(dc.dsxddy) > fp_MaxValue) + { + dc.dsxddy = 0.0f; + } + + dc.dsyddy = pts[1].Y; + + if (Math.Abs(dc.dsyddy) > fp_MaxValue) + { + dc.dsyddy = 0.0f; + } + + dc.fp_dsxddx = (int)(dc.dsxddx * fp_MultFactor); + dc.fp_dsyddx = (int)(dc.dsyddx * fp_MultFactor); + dc.fp_dsxddy = (int)(dc.dsxddy * fp_MultFactor); + dc.fp_dsyddy = (int)(dc.dsyddy * fp_MultFactor); + + dc.dst = dst; + dc.src = this.surface; + Rectangle[] scans = theRegion.GetRegionScansReadOnlyInt(); + + if (scans.Length == 1) + { + dc.dstScans = new Rectangle[Processor.LogicalCpuCount]; + Utility.SplitRectangle(scans[0], dc.dstScans); + } + else + { + dc.dstScans = scans; + } + + WaitCallback wc; + + switch (sampling) + { + case ResamplingAlgorithm.NearestNeighbor: + wc = new WaitCallback(dc.DrawScansNearestNeighbor); + break; + + case ResamplingAlgorithm.Bilinear: + wc = new WaitCallback(dc.DrawScansBilinear); + break; + + default: + throw new System.ComponentModel.InvalidEnumArgumentException(); + } + + for (int i = 0; i < Processor.LogicalCpuCount; ++i) + { + if (i == Processor.LogicalCpuCount - 1) + { + // Don't queue the last work item into a separate thread + wc(BoxedConstants.GetInt32(i)); + } + else + { + threadPool.QueueUserWorkItem(wc, BoxedConstants.GetInt32(i)); + } + } + + threadPool.Drain(); + + for (int i = 0; i < Processor.LogicalCpuCount; ++i) + { + dc.inverses[i].Dispose(); + dc.inverses[i] = null; + } + + dc.src = null; + + theRegion.Dispose(); + theRegion = null; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.surface != null) + { + this.surface.Dispose(); + this.surface = null; + } + + if (this.region != null) + { + this.region.Dispose(); + this.region = null; + } + + if (this.path != null) + { + this.path.Dispose(); + this.path = null; + } + + if (this.shadowPath != null) + { + this.shadowPath.Dispose(); + this.shadowPath = null; + } + } + + this.disposed = true; + } + } +} diff --git a/src/Core/MeasurementUnit.cs b/src/Core/MeasurementUnit.cs new file mode 100644 index 0000000..e4cfc41 --- /dev/null +++ b/src/Core/MeasurementUnit.cs @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Specifies the unit of measure for the given data. + /// + /// + /// These enumeration values correspond to the values used in the EXIF ResolutionUnit tag. + /// + public enum MeasurementUnit + : int + { + Pixel = 1, + Inch = 2, + Centimeter = 3 + } +} diff --git a/src/Core/MemoryBlock.cs b/src/Core/MemoryBlock.cs new file mode 100644 index 0000000..e45e107 --- /dev/null +++ b/src/Core/MemoryBlock.cs @@ -0,0 +1,1095 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Drawing; +using System.Globalization; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; +using System.Threading; + +namespace PaintDotNet +{ + /// + /// Manages an arbitrarily sized block of memory. You can also create child MemoryBlocks + /// which reference a portion of the memory allocated by a parent MemoryBlock. If the parent + /// is disposed, the children will not be valid. + /// + [Serializable] + public unsafe sealed class MemoryBlock + : IDisposable, + ICloneable, + IDeferredSerializable + { + // serialize 1MB at a time: this enables us to serialize very large blocks, and to conserve memory while doing so + private const int serializationChunkSize = 1048576; + + // blocks this size or larger are allocated with AllocateLarge (VirtualAlloc) instead of Allocate (HeapAlloc) + private const long largeBlockThreshold = 65536; + + private long length; + + // if parentBlock == null, then we allocated the pointer and are responsible for deallocating it + // if parentBlock != null, then the parentBlock allocated it, not us + [NonSerialized] + private void *voidStar; + + [NonSerialized] + private bool valid; // if voidStar is null, and this is false, we know that it's null because allocation failed. otherwise we have a real error + + private MemoryBlock parentBlock = null; + + [NonSerialized] + private IntPtr bitmapHandle = IntPtr.Zero; // if allocated using the "width, height" constructor, we keep track of a bitmap handle + private int bitmapWidth; + private int bitmapHeight; + + private bool disposed = false; + + public MemoryBlock Parent + { + get + { + return this.parentBlock; + } + } + + public long Length + { + get + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + return length; + } + } + + public IntPtr Pointer + { + get + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + return new IntPtr(voidStar); + } + } + + public IntPtr BitmapHandle + { + get + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + return this.bitmapHandle; + } + } + + public void *VoidStar + { + get + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + return voidStar; + } + } + + public byte this[long index] + { + get + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + if (index < 0 || index >= length) + { + throw new ArgumentOutOfRangeException("index must be positive and less than Length"); + } + + unsafe + { + return ((byte *)this.VoidStar)[index]; + } + } + + set + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + if (index < 0 || index >= length) + { + throw new ArgumentOutOfRangeException("index must be positive and less than Length"); + } + + unsafe + { + ((byte *)this.VoidStar)[index] = value; + } + } + } + + public bool MaySetAllowWrites + { + get + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + if (this.parentBlock != null) + { + return this.parentBlock.MaySetAllowWrites; + } + else + { + return (this.length >= largeBlockThreshold && this.bitmapHandle != IntPtr.Zero); + } + } + } + + /// + /// Sets a flag indicating whether the memory that this instance of MemoryBlock points to + /// may be written to. + /// + /// + /// This flag is meant to be set to false for short periods of time. The value of this + /// property is not persisted with serialization. + /// + public bool AllowWrites + { + set + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + if (!MaySetAllowWrites) + { + throw new InvalidOperationException("May not set write protection on this memory block"); + } + + Memory.ProtectBlockLarge(new IntPtr(this.voidStar), (ulong)this.length, true, value); + } + } + + /// + /// Copies bytes from one area of memory to another. Since this function works + /// with MemoryBlock instances, it does bounds checking. + /// + /// The MemoryBlock to copy bytes to. + /// The offset within dst to copy bytes to. + /// The MemoryBlock to copy bytes from. + /// The offset within src to copy bytes from. + /// The number of bytes to copy. + public static void CopyBlock(MemoryBlock dst, long dstOffset, MemoryBlock src, long srcOffset, long length) + { + if ((dstOffset + length > dst.length) || (srcOffset + length > src.length)) + { + throw new ArgumentOutOfRangeException("", "copy ranges were out of bounds"); + } + + if (dstOffset < 0) + { + throw new ArgumentOutOfRangeException("dstOffset", dstOffset, "must be >= 0"); + } + + if (srcOffset < 0) + { + throw new ArgumentOutOfRangeException("srcOffset", srcOffset, "must be >= 0"); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException("length", length, "must be >= 0"); + } + + void *dstPtr = (void *)((byte *)dst.VoidStar + dstOffset); + void *srcPtr = (void *)((byte *)src.VoidStar + srcOffset); + Memory.Copy(dstPtr, srcPtr, (ulong)length); + } + + /// + /// Creates a new parent MemoryBlock and copies our contents into it + /// + object ICloneable.Clone() + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + return (object)Clone(); + } + + /// + /// Creates a new parent MemoryBlock and copies our contents into it + /// + public MemoryBlock Clone() + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + MemoryBlock dupe = new MemoryBlock(this.length); + CopyBlock(dupe, 0, this, 0, length); + return dupe; + } + + /// + /// Creates a new MemoryBlock instance and allocates the requested number of bytes. + /// + /// + public MemoryBlock(long bytes) + { + if (bytes <= 0) + { + throw new ArgumentOutOfRangeException("bytes", bytes, "Bytes must be greater than zero"); + } + + this.length = bytes; + this.parentBlock = null; + this.voidStar = Allocate(bytes).ToPointer(); + this.valid = true; + } + + public MemoryBlock(int width, int height) + { + if (width < 0 && height < 0) + { + throw new ArgumentOutOfRangeException("width/height", new Size(width, height), "width and height must be >= 0"); + } + else if (width < 0) + { + throw new ArgumentOutOfRangeException("width", width, "width must be >= 0"); + } + else if (height < 0) + { + throw new ArgumentOutOfRangeException("height", width, "height must be >= 0"); + } + + this.length = width * height * ColorBgra.SizeOf; + this.parentBlock = null; + this.voidStar = Allocate(width, height, out this.bitmapHandle).ToPointer(); + this.valid = true; + this.bitmapWidth = width; + this.bitmapHeight = height; + } + + /// + /// Creates a new MemoryBlock instance that refers to part of another MemoryBlock. + /// The other MemoryBlock is the parent, and this new instance is the child. + /// + public unsafe MemoryBlock(MemoryBlock parentBlock, long offset, long length) + { + if (offset + length > parentBlock.length) + { + throw new ArgumentOutOfRangeException(); + } + + this.parentBlock = parentBlock; + byte *bytePointer = (byte *)parentBlock.VoidStar; + bytePointer += offset; + this.voidStar = (void *)bytePointer; + this.valid = true; + this.length = length; + } + + ~MemoryBlock() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + + if (disposing) + { + } + + if (this.valid && parentBlock == null) + { + if (this.bitmapHandle != IntPtr.Zero) + { + Memory.FreeBitmap(this.bitmapHandle, this.bitmapWidth, this.bitmapHeight); + } + else if (this.length >= largeBlockThreshold) + { + Memory.FreeLarge(new IntPtr(voidStar), (ulong)this.length); + } + else + { + Memory.Free(new IntPtr(voidStar)); + } + } + + parentBlock = null; + voidStar = null; + this.valid = false; + } + } + + private static IntPtr Allocate(int width, int height, out IntPtr handle) + { + return Allocate(width, height, out handle, true); + } + + private static IntPtr Allocate(int width, int height, out IntPtr handle, bool allowRetry) + { + IntPtr block; + + try + { + block = Memory.AllocateBitmap(width, height, out handle); + } + + catch (OutOfMemoryException) + { + if (allowRetry) + { + Utility.GCFullCollect(); + return Allocate(width, height, out handle, false); + } + else + { + throw; + } + } + + return block; + } + + private static IntPtr Allocate(long bytes) + { + return Allocate(bytes, true); + } + + private static IntPtr Allocate(long bytes, bool allowRetry) + { + IntPtr block; + + try + { + if (bytes >= largeBlockThreshold) + { + block = Memory.AllocateLarge((ulong)bytes); + } + else + { + block = Memory.Allocate((ulong)bytes); + } + } + + catch (OutOfMemoryException) + { + if (allowRetry) + { + Utility.GCFullCollect(); + return Allocate(bytes, false); + } + else + { + throw; + } + } + + return block; + } + + public byte[] ToByteArray() + { + return ToByteArray(0, this.length); + } + + public byte[] ToByteArray(long startOffset, long lengthDesired) + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + if (startOffset < 0) + { + throw new ArgumentOutOfRangeException("startOffset", "must be greater than or equal to zero"); + } + + if (lengthDesired < 0) + { + throw new ArgumentOutOfRangeException("length", "must be greater than or equal to zero"); + } + + if (startOffset + lengthDesired > this.length) + { + throw new ArgumentOutOfRangeException("startOffset, length", "startOffset + length must be less than Length"); + } + + byte[] dstArray = new byte[lengthDesired]; + byte *pbSrcArray = (byte *)this.VoidStar; + + fixed (byte *pbDstArray = dstArray) + { + Memory.Copy(pbDstArray, pbSrcArray + startOffset, (ulong)lengthDesired); + } + + return dstArray; + } + + private class OurSerializationException + : SerializationException + { + public OurSerializationException() + { + } + + public OurSerializationException(string message) + : base(message) + { + } + + public OurSerializationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public OurSerializationException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + private MemoryBlock(SerializationInfo info, StreamingContext context) + { + disposed = false; + + // Try to read a 64-bit value, and for backwards compatibility fall back on a 32-bit value. + try + { + this.length = info.GetInt64("length64"); + } + + catch (SerializationException) + { + this.length = (long)info.GetInt32("length"); + } + + try + { + this.bitmapWidth = (int)info.GetInt32("bitmapWidth"); + this.bitmapHeight = (int)info.GetInt32("bitmapHeight"); + + if (this.bitmapWidth != 0 || this.bitmapHeight != 0) + { + long bytes = (long)this.bitmapWidth * (long)this.bitmapHeight * (long)ColorBgra.SizeOf; + + if (bytes != this.length) + { + throw new ApplicationException("Invalid file format: width * height * 4 != length"); + } + } + } + + catch (SerializationException) + { + this.bitmapWidth = 0; + this.bitmapHeight = 0; + } + + bool hasParent = info.GetBoolean("hasParent"); + + if (hasParent) + { + this.parentBlock = (MemoryBlock)info.GetValue("parentBlock", typeof(MemoryBlock)); + + // Try to read a 64-bit value, and for backwards compatibility fall back on a 32-bit value. + long parentOffset; + + try + { + parentOffset = info.GetInt64("parentOffset64"); + } + + catch (SerializationException) + { + parentOffset = (long)info.GetInt32("parentOffset"); + } + + this.voidStar = (void *)((byte *)parentBlock.VoidStar + parentOffset); + this.valid = true; + } + else + { + DeferredFormatter deferredFormatter = context.Context as DeferredFormatter; + bool deferred = false; + + // Was this stream serialized with deferment? + foreach (SerializationEntry entry in info) + { + if (entry.Name == "deferred") + { + deferred = (bool)entry.Value; + break; + } + } + + if (deferred && deferredFormatter != null) + { + // The newest PDN files use deferred deserialization. This lets us read straight from the stream, + // minimizing memory use and adding the potential for multithreading + // Deserialization will complete in IDeferredDeserializer.FinishDeserialization() + deferredFormatter.AddDeferredObject(this, this.length); + } + else if (deferred && deferredFormatter == null) + { + throw new InvalidOperationException("stream has deferred serialization streams, but a DeferredFormatter was not provided"); + } + else + { + this.voidStar = Allocate(this.length).ToPointer(); + this.valid = true; + + // Non-deferred format serializes one big byte[] chunk. This is also + // how PDN files were saved with v2.1 Beta 2 and before. + byte[] array = (byte[])info.GetValue("pointerData", typeof(byte[])); + + fixed (byte *pbArray = array) + { + Memory.Copy(this.VoidStar, (void *)pbArray, (ulong)array.LongLength); + } + } + } + } + + public void WriteFormat1Data(SerializationInfo info, StreamingContext context) + { + byte[] bytes = this.ToByteArray(); + info.AddValue("pointerData", bytes, typeof(byte[])); + } + + public void WriteFormat2Data(SerializationInfo info, StreamingContext context) + { + DeferredFormatter deferred = context.Context as DeferredFormatter; + + if (deferred != null) + { + info.AddValue("deferred", true); + deferred.AddDeferredObject(this, this.length); + } + else + { + WriteFormat1Data(info, context); + } + } + + private static void WriteUInt(Stream output, UInt32 theUInt) + { + output.WriteByte((byte)((theUInt >> 24) & 0xff)); + output.WriteByte((byte)((theUInt >> 16) & 0xff)); + output.WriteByte((byte)((theUInt >> 8) & 0xff)); + output.WriteByte((byte)(theUInt & 0xff)); + } + + private static uint ReadUInt(Stream output) + { + uint theUInt = 0; + + for (int i = 0; i < 4; ++i) + { + theUInt <<= 8; + + int theByte = output.ReadByte(); + + if (theByte == -1) + { + throw new EndOfStreamException(); + } + + theUInt += (UInt32)theByte; + } + + return theUInt; + } + + // Data starts with: + // 1 byte: formatVersion + // 0 for compressed w/ gzip chunks + // 1 for non-compressed chunks + // + // IF formatVersion == 0: + // 4 byte uint: chunkSize + // + // then compute: chunkCount = (length + chunkSize - 1) / chunkSize + // 'length' is written as part of the usual .NET Serialization process in GetObjectData() + // + // Each chunk has the following format: + // 4 byte uint: chunkNumber + // 4 byte uint: raw dataSize 'N' bytes (this will expand to more bytes after decompression) + // N bytes: data + // + // The chunks may appear in any order; that is, chunk N is not necessarily followed by N+1, + // nor is it necessarily preceded by N-1. + // + // uints are written in big-endian order. + + private class DecompressChunkParms + { + private byte[] compressedBytes; + private uint chunkSize; + private long chunkOffset; + private DeferredFormatter deferredFormatter; + private ArrayList exceptions; + + public byte[] CompressedBytes + { + get + { + return compressedBytes; + } + } + + public uint ChunkSize + { + get + { + return chunkSize; + } + } + + public long ChunkOffset + { + get + { + return chunkOffset; + } + } + + public DeferredFormatter DeferredFormatter + { + get + { + return deferredFormatter; + } + } + + public ArrayList Exceptions + { + get + { + return exceptions; + } + } + + public DecompressChunkParms(byte[] compressedBytes, uint chunkSize, long chunkOffset, DeferredFormatter deferredFormatter, ArrayList exceptions) + { + this.compressedBytes = compressedBytes; + this.chunkSize = chunkSize; + this.chunkOffset = chunkOffset; + this.deferredFormatter = deferredFormatter; + this.exceptions = exceptions; + } + } + + private void DecompressChunk(object context) + { + DecompressChunkParms parms = (DecompressChunkParms)context; + + try + { + DecompressChunk(parms.CompressedBytes, parms.ChunkSize, parms.ChunkOffset, parms.DeferredFormatter); + } + + catch (Exception ex) + { + parms.Exceptions.Add(ex); + } + } + + private void DecompressChunk(byte[] compressedBytes, uint chunkSize, long chunkOffset, DeferredFormatter deferredFormatter) + { + // decompress data + MemoryStream compressedStream = new MemoryStream(compressedBytes, false); + GZipStream gZipStream = new GZipStream(compressedStream, CompressionMode.Decompress, true); + + byte[] decompressedBytes = new byte[chunkSize]; + + int dstOffset = 0; + while (dstOffset < decompressedBytes.Length) + { + int bytesRead = gZipStream.Read(decompressedBytes, dstOffset, (int)chunkSize - dstOffset); + + if (bytesRead == 0) + { + throw new SerializationException("ran out of data to decompress"); + } + + dstOffset += bytesRead; + deferredFormatter.ReportBytes((long)bytesRead); + } + + // copy data + fixed (byte *pbDecompressedBytes = decompressedBytes) + { + byte *pbDst = (byte *)this.VoidStar + chunkOffset; + Memory.Copy(pbDst, pbDecompressedBytes, (ulong)chunkSize); + } + } + + void IDeferredSerializable.FinishDeserialization(Stream input, DeferredFormatter context) + { + // Allocate the memory + if (this.bitmapWidth != 0 && this.bitmapHeight != 0) + { + this.voidStar = Allocate(this.bitmapWidth, this.bitmapHeight, out this.bitmapHandle).ToPointer(); + this.valid = true; + } + else + { + this.voidStar = Allocate(this.length).ToPointer(); + this.valid = true; + } + + // formatVersion should equal 0 + int formatVersion = input.ReadByte(); + + if (formatVersion == -1) + { + throw new EndOfStreamException(); + } + + if (formatVersion != 0 && formatVersion != 1) + { + throw new SerializationException("formatVersion was neither zero nor one"); + } + + // chunkSize + uint chunkSize = ReadUInt(input); + + PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(Processor.LogicalCpuCount); + ArrayList exceptions = new ArrayList(Processor.LogicalCpuCount); + WaitCallback callback = new WaitCallback(DecompressChunk); + + // calculate chunkCount + uint chunkCount = (uint)((this.length + (long)chunkSize - 1) / (long)chunkSize); + bool[] chunksFound = new bool[chunkCount]; + + for (uint i = 0; i < chunkCount; ++i) + { + // chunkNumber + uint chunkNumber = ReadUInt(input); + + if (chunkNumber >= chunkCount) + { + throw new SerializationException("chunkNumber read from stream is out of bounds"); + } + + if (chunksFound[chunkNumber]) + { + throw new SerializationException("already encountered chunk #" + chunkNumber.ToString()); + } + + chunksFound[chunkNumber] = true; + + // dataSize + uint dataSize = ReadUInt(input); + + // calculate chunkOffset + long chunkOffset = (long)chunkNumber * (long)chunkSize; + + // calculate decompressed chunkSize + uint thisChunkSize = Math.Min(chunkSize, (uint)(this.length - chunkOffset)); + + // bounds checking + if (chunkOffset < 0 || chunkOffset >= this.length || chunkOffset + thisChunkSize > this.length) + { + throw new SerializationException("data was specified to be out of bounds"); + } + + // read compressed data + byte[] compressedBytes = new byte[dataSize]; + Utility.ReadFromStream(input, compressedBytes, 0, compressedBytes.Length); + + // decompress data + if (formatVersion == 0) + { + DecompressChunkParms parms = new DecompressChunkParms(compressedBytes, thisChunkSize, chunkOffset, context, exceptions); + threadPool.QueueUserWorkItem(callback, parms); + } + else + { + fixed (byte *pbSrc = compressedBytes) + { + Memory.Copy((void *)((byte *)this.VoidStar + chunkOffset), (void *)pbSrc, thisChunkSize); + } + } + } + + threadPool.Drain(); + + if (exceptions.Count > 0) + { + throw new SerializationException("Exception thrown by worker thread", (Exception)exceptions[0]); + } + } + + private class SerializeChunkParms + { + private Stream output; + private uint chunkNumber; + private long chunkOffset; + private long chunkSize; + private object previousLock; + private DeferredFormatter deferredFormatter; + private ArrayList exceptions; + + public Stream Output + { + get + { + return output; + } + } + + public uint ChunkNumber + { + get + { + return chunkNumber; + } + } + + public long ChunkOffset + { + get + { + return chunkOffset; + } + } + + public long ChunkSize + { + get + { + return chunkSize; + } + } + + public object PreviousLock + { + get + { + return (previousLock == null) ? this : previousLock; + } + } + + public DeferredFormatter DeferredFormatter + { + get + { + return deferredFormatter; + } + } + + public ArrayList Exceptions + { + get + { + return exceptions; + } + } + + public SerializeChunkParms(Stream output, uint chunkNumber, long chunkOffset, long chunkSize, object previousLock, + DeferredFormatter deferredFormatter, ArrayList exceptions) + { + this.output = output; + this.chunkNumber = chunkNumber; + this.chunkOffset = chunkOffset; + this.chunkSize = chunkSize; + this.previousLock = previousLock; + this.deferredFormatter = deferredFormatter; + this.exceptions = exceptions; + } + } + + private void SerializeChunk(object context) + { + SerializeChunkParms parms = (SerializeChunkParms)context; + + try + { + SerializeChunk(parms.Output, parms.ChunkNumber, parms.ChunkOffset, parms.ChunkSize, parms, parms.PreviousLock, parms.DeferredFormatter); + } + + catch (Exception ex) + { + parms.Exceptions.Add(ex); + } + } + + private void SerializeChunk(Stream output, uint chunkNumber, long chunkOffset, long chunkSize, + object currentLock, object previousLock, DeferredFormatter deferredFormatter) + { + lock (currentLock) + { + bool useCompression = deferredFormatter.UseCompression; + + MemoryStream chunkOutput = new MemoryStream(); + + // chunkNumber + WriteUInt(chunkOutput, chunkNumber); + + // dataSize + long rewindPos = chunkOutput.Position; + WriteUInt(chunkOutput, 0); // we'll rewind and write this later + long startPos = chunkOutput.Position; + + // Compress data + byte[] array = new byte[chunkSize]; + + fixed (byte *pbArray = array) + { + Memory.Copy(pbArray, (byte *)this.VoidStar + chunkOffset, (ulong)chunkSize); + } + + chunkOutput.Flush(); + + if (useCompression) + { + GZipStream gZipStream = new GZipStream(chunkOutput, CompressionMode.Compress, true); + gZipStream.Write(array, 0, array.Length); + gZipStream.Close(); + } + else + { + chunkOutput.Write(array, 0, array.Length); + } + + long endPos = chunkOutput.Position; + + // dataSize + chunkOutput.Position = rewindPos; + uint dataSize = (uint)(endPos - startPos); + WriteUInt(chunkOutput, dataSize); + + // bytes + chunkOutput.Flush(); + + lock (previousLock) + { + output.Write(chunkOutput.GetBuffer(), 0, (int)chunkOutput.Length); + deferredFormatter.ReportBytes(chunkSize); + } + } + } + + void IDeferredSerializable.FinishSerialization(Stream output, DeferredFormatter context) + { + bool useCompression = context.UseCompression; + + // formatVersion = 0 for GZIP, or 1 for uncompressed + if (useCompression) + { + output.WriteByte(0); + } + else + { + output.WriteByte(1); + } + + // chunkSize + WriteUInt(output, serializationChunkSize); + + uint chunkCount = (uint)((this.length + (long)serializationChunkSize - 1) / (long)serializationChunkSize); + + PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(Processor.LogicalCpuCount); + ArrayList exceptions = ArrayList.Synchronized(new ArrayList(Processor.LogicalCpuCount)); + WaitCallback callback = new WaitCallback(SerializeChunk); + + object previousLock = null; + for (uint chunk = 0; chunk < chunkCount; ++chunk) + { + long chunkOffset = (long)chunk * (long)serializationChunkSize; + uint chunkSize = Math.Min((uint)serializationChunkSize, (uint)(this.length - chunkOffset)); + SerializeChunkParms parms = new SerializeChunkParms(output, chunk, chunkOffset, chunkSize, previousLock, context, exceptions); + threadPool.QueueUserWorkItem(callback, parms); + previousLock = parms; + } + + threadPool.Drain(); + output.Flush(); + + if (exceptions.Count > 0) + { + throw new SerializationException("Exception thrown by worker thread", (Exception)exceptions[0]); + } + + return; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (disposed) + { + throw new ObjectDisposedException("MemoryBlock"); + } + + info.AddValue("length64", this.length); + + if (this.bitmapWidth != 0 || this.bitmapHeight != 0 || this.bitmapHandle != IntPtr.Zero) + { + info.AddValue("bitmapWidth", bitmapWidth); + info.AddValue("bitmapHeight", bitmapHeight); + } + + info.AddValue("hasParent", this.parentBlock != null); + + if (parentBlock == null) + { + WriteFormat2Data(info, context); + } + else + { + info.AddValue("parentBlock", parentBlock, typeof(MemoryBlock)); + info.AddValue("parentOffset64", (long)((byte *)voidStar - (byte *)parentBlock.VoidStar)); + } + } + } +} diff --git a/src/Core/MovingEventArgs.cs b/src/Core/MovingEventArgs.cs new file mode 100644 index 0000000..9e376e7 --- /dev/null +++ b/src/Core/MovingEventArgs.cs @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class MovingEventArgs + : EventArgs + { + private Rectangle rectangle; + public Rectangle Rectangle + { + get + { + return this.rectangle; + } + + set + { + this.rectangle = value; + } + } + + public MovingEventArgs(Rectangle rect) + { + this.rectangle = rect; + } + } +} diff --git a/src/Core/MovingEventHandler.cs b/src/Core/MovingEventHandler.cs new file mode 100644 index 0000000..5cd0f68 --- /dev/null +++ b/src/Core/MovingEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate void MovingEventHandler(object sender, MovingEventArgs e); +} diff --git a/src/Core/ObsoleteClasses.cs b/src/Core/ObsoleteClasses.cs new file mode 100644 index 0000000..de2e680 --- /dev/null +++ b/src/Core/ObsoleteClasses.cs @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + // TODO: Remove +#if false + [Obsolete("Use Function instead.", true)] + public delegate bool BoolObjectDelegate(object o); + + [Obsolete("Use Procedure instead.", true)] + public delegate bool BoolVoidDelegate(); + + [Obsolete("Use EventArgs instead", true)] + public class DataEventArgs + : EventArgs + { + private T data; + public T Data + { + get + { + return data; + } + } + + public DataEventArgs(T data) + { + this.data = data; + } + } + + [Obsolete("Use Procedure instead", true)] + public delegate void VoidObjectDelegate(object obj); + + [Obsolete("Use Procedure instead", true)] + public delegate void VoidVoidDelegate(); + + [Obsolete("Use Procedure instead", true)] + public delegate void ProcedureDelegate(); + + [Obsolete("Use Procedure`1 instead", true)] + public delegate void UnaryProcedureDelegate(T parameter); + + [Obsolete("Use Procedure`2 instead", true)] + public delegate void BinaryProcedureDelegate(T first, U second); + + [Obsolete("Use Function`2 instead", true)] + public delegate R UnaryFunctionDelegate(T parameter); + + [Obsolete("Use Function`3 instead", true)] + public delegate R BinaryFunctionDelegate(T first, U second); +#endif +} \ No newline at end of file diff --git a/src/Core/PaintEventArgs2.cs b/src/Core/PaintEventArgs2.cs new file mode 100644 index 0000000..02cb140 --- /dev/null +++ b/src/Core/PaintEventArgs2.cs @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Gets around a limitation in System.Windows.Forms.PaintEventArgs in that it disposes + /// the Graphics instance that is associated with it when it is disposed. + /// + public sealed class PaintEventArgs2 + : EventArgs + { + private Graphics graphics; + public Graphics Graphics + { + get + { + return graphics; + } + } + + private Rectangle clipRectangle; + public Rectangle ClipRectangle + { + get + { + return clipRectangle; + } + } + + public PaintEventArgs2(Graphics graphics, Rectangle clipRectangle) + { + this.graphics = graphics; + this.clipRectangle = clipRectangle; + } + } +} diff --git a/src/Core/PaintEventHandler2.cs b/src/Core/PaintEventHandler2.cs new file mode 100644 index 0000000..9709227 --- /dev/null +++ b/src/Core/PaintEventHandler2.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Gets around a limitation in System.Windows.Forms.PaintEventArgs in that it disposes + /// the Graphics instance that is associated with it when it is disposed. + /// + public delegate void PaintEventHandler2(object sender, PaintEventArgs2 e); +} diff --git a/src/Core/PanControl.cs b/src/Core/PanControl.cs new file mode 100644 index 0000000..527f764 --- /dev/null +++ b/src/Core/PanControl.cs @@ -0,0 +1,428 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.Core +{ + internal sealed class PanControl + : UserControl + { + private bool mouseDown = false; + private PointF startPosition = new PointF(0, 0); + private Point startMouse = new Point(0, 0); + private Bitmap renderSurface = null; // used for double-buffering + private Cursor handCursor; + private Cursor handMouseDownCursor; + private ImageResource staticImageUnderlay; + private Bitmap cachedUnderlay; + private Rectangle dragAreaRect; + + public ImageResource StaticImageUnderlay + { + get + { + return this.staticImageUnderlay; + } + + set + { + this.staticImageUnderlay = value; + + if (this.cachedUnderlay != null) + { + this.cachedUnderlay.Dispose(); + this.cachedUnderlay = null; + } + + RefreshDragAreaRect(); + Invalidate(true); + } + } + + protected override void OnSizeChanged(EventArgs e) + { + if (this.cachedUnderlay != null) + { + this.cachedUnderlay.Dispose(); + this.cachedUnderlay = null; + } + + RefreshDragAreaRect(); + + base.OnSizeChanged(e); + } + + private void RefreshDragAreaRect() + { + if (this.staticImageUnderlay == null) + { + this.dragAreaRect = ClientRectangle; + } + else + { + Image image = this.staticImageUnderlay.Reference; + Rectangle srcRect = new Rectangle(0, 0, image.Width, image.Height); + + Size maxThumbSize = new Size( + Math.Min(ClientSize.Width - 4, srcRect.Width), + Math.Min(ClientSize.Height - 4, srcRect.Height)); + + Size dstSize = Utility.ComputeThumbnailSize(image.Size, Math.Min(maxThumbSize.Width, maxThumbSize.Height)); + Rectangle dstRect = new Rectangle((ClientSize.Width - dstSize.Width) / 2, (ClientSize.Height - dstSize.Height) / 2, dstSize.Width, dstSize.Height); + + this.dragAreaRect = new Rectangle(ClientRectangle.Left + dstRect.Left, ClientRectangle.Top + dstRect.Top, dstRect.Width, dstRect.Height); + } + } + + public PanControl() + { + if (!this.DesignMode) + { + handCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur")); + handMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur")); + this.Cursor = handCursor; + } + + InitializeComponent(); + + RefreshDragAreaRect(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (handCursor != null) + { + handCursor.Dispose(); + handCursor = null; + } + + if (handMouseDownCursor != null) + { + handMouseDownCursor.Dispose(); + handMouseDownCursor = null; + } + } + + base.Dispose(disposing); + } + + private void InitializeComponent() + { + // + // PanControl + // + this.Name = "PanControl"; + this.Size = new System.Drawing.Size(184, 168); + this.TabStop = false; + } + + private PointF position = new PointF(0, 0); + + public PointF Position + { + get + { + return position; + } + + set + { + if (position != value) + { + position = value; + + this.Invalidate(); + OnPositionChanged(); + this.Update(); + } + } + } + + public event EventHandler PositionChanged; + private void OnPositionChanged() + { + if (PositionChanged != null) + { + PositionChanged(this, EventArgs.Empty); + } + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (!Enabled) + { + return; + } + + if (mouseDown) + { + return; + } + + if (e.Button == MouseButtons.Left) + { + mouseDown = true; + startPosition = position; + startMouse = new Point(e.X, e.Y); + + Cursor = handMouseDownCursor; + } + } + + private PointF MousePtToPosition(Point clientMousePt) + { + float centerX = ClientRectangle.Left + ((ClientRectangle.Right - ClientRectangle.Left) / 2.0f); + float centerY = ClientRectangle.Top + ((ClientRectangle.Bottom - ClientRectangle.Top) / 2.0f); + + float deltaX = clientMousePt.X - centerX; + float deltaY = clientMousePt.Y - centerY; + + float posX = deltaX / (this.dragAreaRect.Width / 2.0f); + float posY = deltaY / (this.dragAreaRect.Height / 2.0f); + + return new PointF(posX, posY); + } + + private PointF PositionToClientPt(PointF pos) + { + float centerX = ClientRectangle.Left + ((ClientRectangle.Right - ClientRectangle.Left) / 2.0f); + float centerY = ClientRectangle.Top + ((ClientRectangle.Bottom - ClientRectangle.Top) / 2.0f); + + float halfWidth = this.dragAreaRect.Width / 2.0f; + float halfHeight = this.dragAreaRect.Height / 2.0f; + + float ptX = centerX + pos.X * halfWidth; + float ptY = centerY + pos.Y * halfHeight; + + return new PointF(ptX, ptY); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (mouseDown && e.Button == MouseButtons.Left) + { + Position = MousePtToPosition(new Point(e.X, e.Y)); + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (mouseDown) + { + if (e.Button == MouseButtons.Left) + { + Position = MousePtToPosition(new Point(e.X, e.Y)); + } + + Cursor = handCursor; + mouseDown = false; + } + } + + private void CheckRenderSurface() + { + if (renderSurface != null && renderSurface.Size != Size) + { + renderSurface.Dispose(); + renderSurface = null; + } + + if (renderSurface == null) + { + renderSurface = new Bitmap(Width, Height); + + using (Graphics g = Graphics.FromImage(renderSurface)) + { + DrawToGraphics(g); + } + } + } + + private void DoPaint(Graphics g) + { + CheckRenderSurface(); + g.DrawImage(renderSurface, ClientRectangle, ClientRectangle, GraphicsUnit.Pixel); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + renderSurface = null; + DoPaint(e.Graphics); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + DoPaint(pevent.Graphics); + } + + private void DrawToGraphics(Graphics g) + { + PointF clientPos = new PointF( + (this.position.X * this.dragAreaRect.Width) / ClientSize.Width, + (this.position.Y * this.dragAreaRect.Height) / ClientSize.Height); + + PointF ptCenter = new PointF(ClientSize.Width / 2.0f, ClientSize.Height / 2.0f); + PointF ptDot = new PointF((1 + clientPos.X) * ClientSize.Width / 2.0f, (1 + clientPos.Y) * ClientSize.Height / 2.0f); + PointF ptArrow; + + if (-1 <= clientPos.X && clientPos.X <= 1 && + -1 <= clientPos.Y && clientPos.Y <= 1) + { + ptArrow = new PointF((1 + clientPos.X) * ClientSize.Width / 2, (1 + clientPos.Y) * ClientSize.Height / 2); + } + else + { + ptArrow = new PointF((1 + clientPos.X) * ClientSize.Width / 2, (1 + clientPos.Y) * ClientSize.Height / 2); + + if (Math.Abs(clientPos.X) > Math.Abs(clientPos.Y)) + { + if (clientPos.X > 0) + { + ptArrow.X = ClientSize.Width - 1; + ptArrow.Y = (1 + clientPos.Y / clientPos.X) * ClientSize.Height / 2; + } + else + { + ptArrow.X = 0; + ptArrow.Y = (1 - clientPos.Y / clientPos.X) * ClientSize.Height / 2; + } + } + else + { + if (clientPos.Y > 0) + { + ptArrow.X = (1 + clientPos.X / clientPos.Y) * ClientSize.Width / 2; + ptArrow.Y = ClientSize.Height - 1; + } + else + { + ptArrow.X = (1 - clientPos.X / clientPos.Y) * ClientSize.Width / 2; + ptArrow.Y = 0; + } + } + } + + CompositingMode oldCM = g.CompositingMode; + + g.CompositingMode = CompositingMode.SourceCopy; + g.Clear(this.BackColor); + g.CompositingMode = CompositingMode.SourceOver; + + if (this.staticImageUnderlay != null) + { + Size dstSize; + + if (this.cachedUnderlay != null) + { + dstSize = new Size(this.cachedUnderlay.Width, this.cachedUnderlay.Height); + } + else + { + Image image = this.staticImageUnderlay.Reference; + Rectangle srcRect = new Rectangle(0, 0, image.Width, image.Height); + + Size maxThumbSize = new Size( + Math.Max(1, Math.Min(ClientSize.Width - 4, srcRect.Width)), + Math.Max(1, Math.Min(ClientSize.Height - 4, srcRect.Height))); + + dstSize = Utility.ComputeThumbnailSize(image.Size, Math.Min(maxThumbSize.Width, maxThumbSize.Height)); + + this.cachedUnderlay = new Bitmap(dstSize.Width, dstSize.Height, PixelFormat.Format24bppRgb); + + Surface checkers = new Surface(dstSize); + checkers.ClearWithCheckboardPattern(); + Bitmap checkersBmp = checkers.CreateAliasedBitmap(); + + Rectangle gcuRect = new Rectangle(0, 0, this.cachedUnderlay.Width, this.cachedUnderlay.Height); + + using (Graphics gcu = Graphics.FromImage(this.cachedUnderlay)) + { + gcu.CompositingMode = CompositingMode.SourceOver; + gcu.DrawImage(checkersBmp, gcuRect, new Rectangle(0, 0, checkersBmp.Width, checkersBmp.Height), GraphicsUnit.Pixel); + + gcu.InterpolationMode = InterpolationMode.HighQualityBicubic; + RectangleF gcuRect2 = RectangleF.Inflate(gcuRect, 0.5f, 0.5f); + gcu.DrawImage(image, gcuRect2, srcRect, GraphicsUnit.Pixel); + } + + checkersBmp.Dispose(); + checkersBmp = null; + + checkers.Dispose(); + checkers = null; + } + + Rectangle dstRect = new Rectangle((ClientSize.Width - dstSize.Width) / 2, (ClientSize.Height - dstSize.Height) / 2, dstSize.Width, dstSize.Height); + g.DrawImage(this.cachedUnderlay, dstRect, new Rectangle(0, 0, this.cachedUnderlay.Width, this.cachedUnderlay.Height), GraphicsUnit.Pixel); + g.DrawRectangle(Pens.Black, new Rectangle(dstRect.Left - 1, dstRect.Top - 1, dstRect.Width + 1, dstRect.Height + 1)); + Utility.DrawDropShadow1px(g, new Rectangle(dstRect.Left - 2, dstRect.Top - 2, dstRect.Width + 4, dstRect.Height + 4)); + } + + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.Half; + + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.HighQuality; + + // Draw the center -> end point arrow + using (Pen pen = (Pen)Pens.Black.Clone()) + { + pen.SetLineCap(LineCap.Round, LineCap.DiamondAnchor, DashCap.Flat); + pen.EndCap = LineCap.ArrowAnchor; + pen.Width = 2.0f; + pen.Color = SystemColors.ControlDark; + + g.DrawLine(pen, ptCenter, ptArrow); + } + + // Draw the compass + using (Pen pen = new Pen(Color.White)) + { + pen.SetLineCap(LineCap.DiamondAnchor, LineCap.DiamondAnchor, DashCap.Flat); + + // Draw white outline + pen.Width = 3f; + pen.Color = Color.White; + + g.DrawLine(pen, ptDot.X - 5.0f, ptDot.Y, ptDot.X + 5.0f, ptDot.Y); + g.DrawLine(pen, ptDot.X, ptDot.Y - 5.0f, ptDot.X, ptDot.Y + 5.0f); + + // Draw black inset + pen.Width = 2f; + pen.Color = Color.Black; + + g.DrawLine(pen, ptDot.X - 5.0f, ptDot.Y, ptDot.X + 5.0f, ptDot.Y); + g.DrawLine(pen, ptDot.X, ptDot.Y - 5.0f, ptDot.X, ptDot.Y + 5.0f); + } + + g.SmoothingMode = oldSM; + g.PixelOffsetMode = oldPOM; + g.CompositingMode = oldCM; + } + } +} \ No newline at end of file diff --git a/src/Core/PanelEx.cs b/src/Core/PanelEx.cs new file mode 100644 index 0000000..81faaa2 --- /dev/null +++ b/src/Core/PanelEx.cs @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public class PanelEx : + PaintDotNet.SystemLayer.ScrollPanel + { + private bool hideHScroll = false; + + public bool HideHScroll + { + get + { + return this.hideHScroll; + } + + set + { + this.hideHScroll = value; + } + } + + protected override void OnSizeChanged(EventArgs e) + { + if (this.hideHScroll) + { + SystemLayer.UI.SuspendControlPainting(this); + } + + base.OnSizeChanged(e); + + if (this.hideHScroll) + { + SystemLayer.UI.HideHorizontalScrollBar(this); + SystemLayer.UI.ResumeControlPainting(this); + Invalidate(true); + } + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + //base.OnMouseWheel(e); + } + } +} diff --git a/src/Core/PanelEx.resx b/src/Core/PanelEx.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/PdnBanner.cs b/src/Core/PdnBanner.cs new file mode 100644 index 0000000..55449ee --- /dev/null +++ b/src/Core/PdnBanner.cs @@ -0,0 +1,361 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class PdnBanner + : Control + { + private System.Windows.Forms.PictureBox bannerImage; + private System.Windows.Forms.Label bannerText; + + // for awhile there, this control was designed to fade between several different + // images. for 3.0 this did not end up being the case, but the functionality is + // still here + private System.Windows.Forms.Timer bannerTimer; + + private static readonly Size defaultSize = new Size(495, 71); + + protected override Size DefaultSize + { + get + { + return defaultSize; + } + } + + public string BannerText + { + get + { + return this.bannerText.Text; + } + + set + { + this.bannerText.Text = value; + } + } + + public Font BannerFont + { + get + { + return (Font)this.bannerText.Font.Clone(); + } + + set + { + this.bannerText.Font = (Font)value.Clone(); + } + } + + private void BannerTimer_Tick(object sender, EventArgs e) + { + Form findForm = FindForm(); + + if (findForm != null && + findForm.WindowState != FormWindowState.Minimized) + { + const int bannerUpDuration = 4000; + const int bannerFadeDuration = 2000; + const int bannerPeriod = bannerUpDuration + bannerFadeDuration; + int ticks = unchecked(Environment.TickCount - this.firstTick + bannerUpDuration / 2); + int localTick = ticks % bannerPeriod; + + double a; + if (localTick < bannerUpDuration) + { + a = 1.0; + } + else + { + int fadeTick = localTick - bannerUpDuration; + a = (double)(bannerFadeDuration - fadeTick) / (double)bannerFadeDuration; + a = 1.0 - a; + a = a * a; + a = 1.0 - a; + } + + int newBannerIndex = ticks / bannerPeriod; + float newBannerAlpha = (float)a; + + if (banners.Length < 2 || SystemLayer.UserSessions.IsRemote) + { + newBannerAlpha = 1.0f; + } + + if (newBannerAlpha != this.bannerAlpha || + newBannerIndex != this.bannerIndex) + { + this.bannerAlpha = newBannerAlpha; + this.bannerIndex = newBannerIndex; + SetUpBannerImage(); + } + } + } + + private int indexOffset; + private int firstTick = Environment.TickCount; + private Image pdnLogo = PdnResources.GetImageResource("Images.TransparentLogo.png").GetCopy(); + private int bannerIndex = 0; + private float bannerAlpha = 1.0f; + private Bitmap logoAndGradient = new Bitmap(495, 71, PixelFormat.Format24bppRgb); + private Bitmap highQualityBmp = null; + private Image[] banners = new Image[] + { + PdnResources.GetImageResource("Images.Banner.png").GetCopy(), + }; + + private void SetUpBannerImage() + { + Image banner1 = this.banners[(this.bannerIndex + this.indexOffset) % this.banners.Length]; + Image banner2 = this.banners[(this.bannerIndex + 1 + this.indexOffset) % this.banners.Length]; + + using (Graphics g = Graphics.FromImage(this.logoAndGradient)) + { + g.Clear(Color.White); + + Rectangle gradientSrcBounds = new Rectangle( + new Point(0, 0), + banner2.Size); + + Rectangle gradientDstBounds = new Rectangle( + new Point(logoAndGradient.Width - banner2.Width, 0), + banner2.Size); + + float alpha1 = this.bannerAlpha; + float alpha2 = 1.0f - alpha1; + + ColorMatrix cm1 = new ColorMatrix( + new float[][] + { + new float[] { 1, 0, 0, 0, 0 }, + new float[] { 0, 1, 0, 0, 0 }, + new float[] { 0, 0, 1, 0, 0 }, + new float[] { 0, 0, 0, alpha1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + + ImageAttributes ia1 = new ImageAttributes(); + ia1.SetColorMatrix(cm1); + + ColorMatrix cm2 = new ColorMatrix( + new float[][] + { + new float[] { 1, 0, 0, 0, 0 }, + new float[] { 0, 1, 0, 0, 0 }, + new float[] { 0, 0, 1, 0, 0 }, + new float[] { 0, 0, 0, alpha2, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + + ImageAttributes ia2 = new ImageAttributes(); + ia2.SetColorMatrix(cm2); + + if (banner1 != null) + { + float inflateAmt1X = 0; // 1500.0f - (alpha1 * 1500.0f); + float inflateAmt1Y = 0; // (inflateAmt1X * (float)banner1.Height) / (float)banner2.Width; + + RectangleF dstRect1 = new RectangleF( + gradientDstBounds.X - inflateAmt1X * 2 + (inflateAmt1X / 150.0f), + gradientDstBounds.Y - (inflateAmt1Y * 3) / 2, + gradientDstBounds.Width + (2 * inflateAmt1X), + gradientDstBounds.Height + (2 * inflateAmt1Y)); + + g.DrawImage( + banner1, + new PointF[] + { + dstRect1.Location, + new PointF(dstRect1.Right, dstRect1.Top), + new PointF(dstRect1.Left, dstRect1.Bottom) + }, + gradientSrcBounds, + GraphicsUnit.Pixel, + ia1); + } + + float inflateAmt2X = 0; // 1500.0f - (alpha2 * 1500.0f); + float inflateAmt2Y = 0; // (inflateAmt2X * (float)banner2.Height) / (float)banner2.Width; + + RectangleF dstRect2 = new RectangleF( + gradientDstBounds.X - inflateAmt2X * 2 + (inflateAmt2X / 150.0f), + gradientDstBounds.Y - (inflateAmt2Y * 3) / 2, + gradientDstBounds.Width + (2 * inflateAmt2X), + gradientDstBounds.Height + (2 * inflateAmt2Y)); + + g.DrawImage( + banner2, + new PointF[] + { + dstRect2.Location, + new PointF(dstRect2.Right, dstRect2.Top), + new PointF(dstRect2.Left, dstRect2.Bottom) + }, + (RectangleF)gradientSrcBounds, + GraphicsUnit.Pixel, + ia2); + + Rectangle pdnLogoBounds = new Rectangle(new Point(0, 0), pdnLogo.Size); + g.DrawImage(pdnLogo, pdnLogoBounds, pdnLogoBounds, GraphicsUnit.Pixel); + + ia1.Dispose(); + ia1 = null; + + ia2.Dispose(); + ia2 = null; + } + + Bitmap useThis; + + if (this.bannerImage.Size == logoAndGradient.Size) + { + useThis = logoAndGradient; + } + else + { + if (this.highQualityBmp == null) + { + this.highQualityBmp = new Bitmap( + this.bannerImage.Width, + this.bannerImage.Height, + PixelFormat.Format24bppRgb); + } + + using (Graphics g = Graphics.FromImage(this.highQualityBmp)) + { + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + + g.DrawImage( + logoAndGradient, + new Rectangle(0, 0, this.highQualityBmp.Width, this.highQualityBmp.Height), + new Rectangle(0, 0, logoAndGradient.Width, logoAndGradient.Height), + GraphicsUnit.Pixel); + } + + useThis = this.highQualityBmp; + } + + this.bannerImage.Image = useThis; + } + + public PdnBanner() + { + InitializeComponent(); + this.indexOffset = new Random().Next(this.banners.Length); + SetUpBannerImage(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.bannerTimer != null) + { + this.bannerTimer.Dispose(); + this.bannerTimer = null; + } + + if (this.pdnLogo != null) + { + this.pdnLogo.Dispose(); + this.pdnLogo = null; + } + + if (this.logoAndGradient != null) + { + this.logoAndGradient.Dispose(); + this.logoAndGradient = null; + } + + if (this.highQualityBmp != null) + { + this.highQualityBmp.Dispose(); + this.highQualityBmp = null; + } + + if (this.banners != null) + { + foreach (Image image in this.banners) + { + if (image != null) + { + image.Dispose(); + } + } + + this.banners = null; + } + } + + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.bannerImage = new System.Windows.Forms.PictureBox(); + this.bannerText = new System.Windows.Forms.Label(); + this.bannerTimer = new System.Windows.Forms.Timer(); + ((System.ComponentModel.ISupportInitialize)(this.bannerImage)).BeginInit(); + this.SuspendLayout(); + // + // bannerImage + // + this.bannerImage.BackColor = System.Drawing.Color.White; + this.bannerImage.Dock = System.Windows.Forms.DockStyle.Top; + this.bannerImage.Location = new System.Drawing.Point(0, 0); + this.bannerImage.Name = "headerImage"; + this.bannerImage.Size = defaultSize; + this.bannerImage.SizeChanged += new EventHandler(BannerImage_SizeChanged); + this.bannerImage.SizeMode = PictureBoxSizeMode.CenterImage; + this.bannerImage.TabIndex = 0; + this.bannerImage.TabStop = false; + this.bannerImage.Controls.Add(this.bannerText); + // + // bannerText + // + this.bannerText.BackColor = System.Drawing.Color.Transparent; + this.bannerText.ForeColor = System.Drawing.Color.Black; + this.bannerText.Font = Utility.CreateFont("Tahoma", 10.0f, FontStyle.Regular); + this.bannerText.Location = new System.Drawing.Point(70, 47); + this.bannerText.Name = "headingText"; + this.bannerText.Size = new System.Drawing.Size(441, 25); + this.bannerText.TabIndex = 4; + this.bannerText.Text = "headingText"; + this.bannerText.Visible = false; + // + // bannerTimer + // + this.bannerTimer.Interval = 30; + this.bannerTimer.Tick += new EventHandler(BannerTimer_Tick); + this.bannerTimer.Enabled = true; + // + // PdnBanner + // + this.Controls.Add(this.bannerImage); + this.Name = "PdnBanner"; + ((System.ComponentModel.ISupportInitialize)(this.bannerImage)).EndInit(); + ResumeLayout(); + PerformLayout(); + } + + private void BannerImage_SizeChanged(object sender, EventArgs e) + { + SetUpBannerImage(); + } + } +} diff --git a/src/Core/PdnBaseDialog.cs b/src/Core/PdnBaseDialog.cs new file mode 100644 index 0000000..9c70c2d --- /dev/null +++ b/src/Core/PdnBaseDialog.cs @@ -0,0 +1,121 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public class PdnBaseDialog + : PdnBaseForm + { + protected System.Windows.Forms.Button baseOkButton; + protected System.Windows.Forms.Button baseCancelButton; + private System.ComponentModel.IContainer components = null; + + public PdnBaseDialog() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + if (!this.DesignMode) + { + this.baseOkButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.baseCancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + public DialogResult ShowDialog(Control owner) + { + return Utility.ShowDialog(this, owner); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.baseOkButton = new System.Windows.Forms.Button(); + this.baseCancelButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // baseOkButton + // + this.baseOkButton.Location = new System.Drawing.Point(77, 128); + this.baseOkButton.Name = "baseOkButton"; + this.baseOkButton.TabIndex = 1; + this.baseOkButton.FlatStyle = FlatStyle.System; + this.baseOkButton.Click += new System.EventHandler(this.baseOkButton_Click); + // + // baseCancelButton + // + this.baseCancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.baseCancelButton.Location = new System.Drawing.Point(165, 128); + this.baseCancelButton.Name = "baseCancelButton"; + this.baseCancelButton.TabIndex = 2; + this.baseCancelButton.FlatStyle = FlatStyle.System; + this.baseCancelButton.Click += new System.EventHandler(this.baseCancelButton_Click); + // + // PdnBaseDialog + // + this.AcceptButton = this.baseOkButton; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.baseCancelButton; + this.ClientSize = new System.Drawing.Size(248, 158); + this.Controls.Add(this.baseCancelButton); + this.Controls.Add(this.baseOkButton); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MinimizeBox = false; + this.Name = "PdnBaseDialog"; + this.ShowInTaskbar = false; + this.Text = "PdnBaseDialog"; + this.Controls.SetChildIndex(this.baseOkButton, 0); + this.Controls.SetChildIndex(this.baseCancelButton, 0); + this.ResumeLayout(false); + + } + #endregion + + private void baseOkButton_Click(object sender, System.EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void baseCancelButton_Click(object sender, System.EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } + } +} + diff --git a/src/Core/PdnBaseDialog.resx b/src/Core/PdnBaseDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/PdnBaseForm.cs b/src/Core/PdnBaseForm.cs new file mode 100644 index 0000000..72329a6 --- /dev/null +++ b/src/Core/PdnBaseForm.cs @@ -0,0 +1,1096 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; +using System.Security; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// This Form class is used to fix a few bugs in Windows Forms, and to add a few performance + /// enhancements, such as disabling opacity != 1.0 when running in a remote TS/RD session. + /// We derive from this class instead of Windows.Forms.Form directly. + /// + public class PdnBaseForm + : Form, + ISnapManagerHost + { + static PdnBaseForm() + { + Application.EnterThreadModal += new EventHandler(Application_EnterThreadModal); + Application.LeaveThreadModal += new EventHandler(Application_LeaveThreadModal); + Application_EnterThreadModal(null, EventArgs.Empty); + } + + // This set keeps track of forms which cannot be the current modal form. + private static Stack> parentModalForms = new Stack>(); + + private static bool IsInParentModalForms(Form form) + { + foreach (List
formList in parentModalForms) + { + foreach (Form parentModalForm in formList) + { + if (parentModalForm == form) + { + return true; + } + } + } + + return false; + } + + private static List GetAllPeerForms(Form form) + { + if (form == null) + { + return new List(); + } + + if (form.Owner != null) + { + return GetAllPeerForms(form.Owner); + } + + List forms = new List(); + forms.Add(form); + forms.AddRange(form.OwnedForms); + + return forms; + } + + private static void Application_EnterThreadModal(object sender, EventArgs e) + { + Form activeForm = Form.ActiveForm; + List allPeerForms = GetAllPeerForms(activeForm); + parentModalForms.Push(allPeerForms); + } + + private static void Application_LeaveThreadModal(object sender, EventArgs e) + { + parentModalForms.Pop(); + } + + protected override void OnShown(EventArgs e) + { + isShown = true; + Tracing.LogFeature("ShowDialog(" + GetType().FullName + ")"); + base.OnShown(e); + } + + public bool IsShown + { + get + { + return this.isShown; + } + } + + private bool isShown = false; + private bool enableOpacity = true; + private double ourOpacity = 1.0; // store opacity setting so that when we go from disabled->enabled opacity we can set the correct value + private SnapManager snapManager = null; + private System.ComponentModel.IContainer components; + private bool instanceEnableOpacity = true; + private static bool globalEnableOpacity = true; + private FormEx formEx; + private bool processFormHotKeyMutex = false; // if we're already processing a form hotkey, don't let other hotkeys execute. + + private static Dictionary> hotkeyRegistrar = null; + + /// + /// Registers a form-wide hot key, and a callback for when the key is pressed. + /// The callback must be an instance method on a Control. Whatever Form the Control + /// is on will process the hotkey, as long as the Form is derived from PdnBaseForm. + /// + public static void RegisterFormHotKey(Keys keys, Function callback) + { + IComponent targetAsComponent = callback.Target as IComponent; + IHotKeyTarget targetAsHotKeyTarget = callback.Target as IHotKeyTarget; + + if (targetAsComponent == null && targetAsHotKeyTarget == null) + { + throw new ArgumentException("target instance must implement IComponent or IHotKeyTarget", "callback"); + } + + if (hotkeyRegistrar == null) + { + hotkeyRegistrar = new Dictionary>(); + } + + Function theDelegate = null; + + if (hotkeyRegistrar.ContainsKey(keys)) + { + theDelegate = hotkeyRegistrar[keys]; + theDelegate += callback; + hotkeyRegistrar[keys] = theDelegate; + } + else + { + theDelegate = new Function(callback); + hotkeyRegistrar.Add(keys, theDelegate); + } + + if (targetAsComponent != null) + { + targetAsComponent.Disposed += TargetAsComponent_Disposed; + } + else + { + targetAsHotKeyTarget.Disposed += TargetAsHotKeyTarget_Disposed; + } + } + + private bool ShouldProcessHotKey(Keys keys) + { + Keys keyOnly = keys & ~Keys.Modifiers; + + if (keyOnly == Keys.Back || + keyOnly == Keys.Delete || + keyOnly == Keys.Left || + keyOnly == Keys.Right || + keyOnly == Keys.Up || + keyOnly == Keys.Down || + keys == (Keys.Control | Keys.A) || // select all + keys == (Keys.Control | Keys.Z) || // undo + keys == (Keys.Control | Keys.Y) || // redo + keys == (Keys.Control | Keys.X) || // cut + keys == (Keys.Control | Keys.C) || // copy + keys == (Keys.Control | Keys.V) || // paste + keys == (Keys.Shift | Keys.Delete) || // cut (left-handed) + keys == (Keys.Control | Keys.Insert) || // copy (left-handed) + keys == (Keys.Shift | Keys.Insert) // paste (left-handed) + ) + { + Control focused = Utility.FindFocus(); + + if (focused is TextBox || focused is ComboBox || focused is UpDownBase) + { + return false; + } + } + + return true; + } + + private static void TargetAsComponent_Disposed(object sender, EventArgs e) + { + ((IComponent)sender).Disposed -= TargetAsComponent_Disposed; + RemoveDisposedTarget(sender); + } + + private static void TargetAsHotKeyTarget_Disposed(object sender, EventArgs e) + { + ((IHotKeyTarget)sender).Disposed -= TargetAsHotKeyTarget_Disposed; + RemoveDisposedTarget(sender); + } + + static void RemoveDisposedTarget(object sender) + { + // Control was disposed, but it never unregistered for its hotkeys! + List keysList = new List(hotkeyRegistrar.Keys); + + foreach (Keys keys in keysList) + { + Function theMultiDelegate = hotkeyRegistrar[keys]; + + foreach (Delegate theDelegate in theMultiDelegate.GetInvocationList()) + { + if (object.ReferenceEquals(theDelegate.Target, sender)) + { + UnregisterFormHotKey(keys, (Function)theDelegate); + } + } + } + } + + public static void UnregisterFormHotKey(Keys keys, Function callback) + { + if (hotkeyRegistrar != null) + { + Function theDelegate = hotkeyRegistrar[keys]; + theDelegate -= callback; + hotkeyRegistrar[keys] = theDelegate; + + IComponent targetAsComponent = callback.Target as IComponent; + if (targetAsComponent != null) + { + targetAsComponent.Disposed -= TargetAsComponent_Disposed; + } + + IHotKeyTarget targetAsHotKeyTarget = callback.Target as IHotKeyTarget; + if (targetAsHotKeyTarget != null) + { + targetAsHotKeyTarget.Disposed -= TargetAsHotKeyTarget_Disposed; + } + + if (theDelegate.GetInvocationList().Length == 0) + { + hotkeyRegistrar.Remove(keys); + } + + if (hotkeyRegistrar.Count == 0) + { + hotkeyRegistrar = null; + } + } + } + + public void Flash() + { + UI.FlashForm(this); + } + + public void RestoreWindow() + { + if (WindowState == FormWindowState.Minimized) + { + UI.RestoreWindow(this); + } + } + + /// + /// Returns the currently active modal form if the process is in the foreground and is active. + /// + /// + /// If Form.ActiveForm is modeless, we search up the chain of owner forms + /// to find its modeless owner form. + /// + public static Form CurrentModalForm + { + get + { + Form theForm = Form.ActiveForm; + + while (theForm != null && !theForm.Modal && theForm.Owner != null) + { + theForm = theForm.Owner; + } + + return theForm; + } + } + + /// + /// Gets whether the current form is the processes' top level modal form. + /// + public bool IsCurrentModalForm + { + get + { + if (IsInParentModalForms(this)) + { + return false; + } + + if (this.ContainsFocus) + { + return true; + } + + foreach (Form ownedForm in this.OwnedForms) + { + if (ownedForm.ContainsFocus) + { + return true; + } + } + + return (this == CurrentModalForm); + } + } + + private bool IsTargetFormActive(object target) + { + Control targetControl = null; + + if (targetControl == null) + { + Control asControl = target as Control; + + if (asControl != null) + { + targetControl = asControl; + } + } + + if (targetControl == null) + { + IFormAssociate asIFormAssociate = target as IFormAssociate; + + if (asIFormAssociate != null) + { + targetControl = asIFormAssociate.AssociatedForm; + } + } + + // target is not a control, or a type of non-control that we recognize as hosted by a control + if (targetControl == null) + { + return false; + } + + Form targetForm = targetControl.FindForm(); + + // target is not on a form + if (targetForm == null) + { + return false; + } + + // is the target on the currently active form? + Form activeModalForm = CurrentModalForm; + + if (targetForm == activeModalForm) + { + return true; + } + + // Nope. + return false; + } + + private static object GetConcreteTarget(object target) + { + Delegate asDelegate = target as Delegate; + + if (asDelegate == null) + { + return target; + } + else + { + return GetConcreteTarget(asDelegate.Target); + } + } + + private bool ProcessFormHotKey(Keys keyData) + { + bool processed = false; + + if (this.processFormHotKeyMutex) + { + processed = true; + } + else + { + this.processFormHotKeyMutex = true; + + try + { + if (hotkeyRegistrar != null && hotkeyRegistrar.ContainsKey(keyData)) + { + Function theDelegate = hotkeyRegistrar[keyData]; + Delegate[] invokeList = theDelegate.GetInvocationList(); + + for (int i = invokeList.Length - 1; i >= 0; --i) + { + Function invokeMe = (Function)invokeList[i]; + object concreteTarget = GetConcreteTarget(invokeMe.Target); + + if (IsTargetFormActive(concreteTarget)) + { + bool result = invokeMe(keyData); + + if (result) + { + // The callback handled the key. + processed = true; + break; + } + } + } + } + } + + finally + { + this.processFormHotKeyMutex = false; + } + } + + return processed; + } + + private void OnProcessCmdKeyRelay(object sender, FormEx.ProcessCmdKeyEventArgs e) + { + bool handled = e.Handled; + + if (!handled) + { + handled = ProcessCmdKeyData(e.KeyData); + e.Handled = handled; + } + } + + public bool RelayProcessCmdKey(ref Message msg, Keys keyData) + { + return ProcessCmdKeyData(keyData); + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + bool processed = ProcessCmdKeyData(keyData); + + if (!processed) + { + processed = base.ProcessCmdKey(ref msg, keyData); + } + + return processed; + } + + private bool ProcessCmdKeyData(Keys keyData) + { + bool shouldHandle = ShouldProcessHotKey(keyData); + + if (shouldHandle) + { + bool processed = ProcessFormHotKey(keyData); + return processed; + } + else + { + return false; + } + } + + public static void UpdateAllForms() + { + try + { + foreach (Form form in Application.OpenForms) + { + try + { + form.Update(); + } + + catch (Exception) + { + } + } + } + + catch (InvalidOperationException) + { + } + } + + protected override void OnHelpRequested(HelpEventArgs hevent) + { + if (!hevent.Handled) + { + Utility.ShowHelp(this); + hevent.Handled = true; + } + + base.OnHelpRequested(hevent); + } + + public static EventHandler EnableOpacityChanged; + private static void OnEnableOpacityChanged() + { + if (EnableOpacityChanged != null) + { + EnableOpacityChanged(null, EventArgs.Empty); + } + } + + public bool EnableInstanceOpacity + { + get + { + return instanceEnableOpacity; + } + + set + { + instanceEnableOpacity = value; + this.DecideOpacitySetting(); + } + } + + /// + /// Gets or sets a flag that enables or disables opacity for all PdnBaseForm instances. + /// If a particular form's EnableInstanceOpacity property is false, that will override + /// this property being 'true'. + /// + public static bool EnableOpacity + { + get + { + return globalEnableOpacity; + } + + set + { + globalEnableOpacity = value; + OnEnableOpacityChanged(); + } + } + + /// + /// Gets or sets the titlebar rendering behavior for when the form is deactivated. + /// + /// + /// If this property is false, the titlebar will be rendered in a different color when the form + /// is inactive as opposed to active. If this property is true, it will always render with the + /// active style. If the whole application is deactivated, the title bar will still be drawn in + /// an inactive state. + /// + public bool ForceActiveTitleBar + { + get + { + return this.formEx.ForceActiveTitleBar; + } + + set + { + this.formEx.ForceActiveTitleBar = value; + } + } + + private ThreadPriority originalPriority; + + protected override void OnScroll(ScrollEventArgs se) + { + Thread.CurrentThread.Priority = this.originalPriority; + base.OnScroll(se); + } + + public PdnBaseForm() + { + this.originalPriority = Thread.CurrentThread.Priority; + Thread.CurrentThread.Priority = ThreadPriority.AboveNormal; + + UI.InitScaling(this); + + this.SuspendLayout(); + InitializeComponent(); + + this.formEx = new PaintDotNet.SystemLayer.FormEx(this, new RealParentWndProcDelegate(this.RealWndProc)); + this.Controls.Add(this.formEx); + this.formEx.Visible = false; + DecideOpacitySetting(); + this.ResumeLayout(false); + + this.formEx.ProcessCmdKeyRelay += OnProcessCmdKeyRelay; + } + + protected override void OnLoad(EventArgs e) + { + if (!this.DesignMode) + { + LoadResources(); + } + + base.OnLoad(e); + } + + public virtual void LoadResources() + { + if (!this.DesignMode) + { + string stringName = this.Name + ".Localized"; + string stringValue = StringsResourceManager.GetString(stringName); + + if (stringValue != null) + { + try + { + bool boolValue = bool.Parse(stringValue); + + if (boolValue) + { + LoadLocalizedResources(); + } + } + + catch (Exception) + { + } + } + } + } + + protected virtual ResourceManager StringsResourceManager + { + get + { + return PdnResources.Strings; + } + } + + private void LoadLocalizedResources() + { + LoadLocalizedResources(this.Name, this); + } + + private void ParsePair(string theString, out int x, out int y) + { + string[] split = theString.Split(','); + x = int.Parse(split[0]); + y = int.Parse(split[1]); + } + + private void LoadLocalizedResources(string baseName, Control control) + { + // Text + string textStringName = baseName + ".Text"; + string textString = this.StringsResourceManager.GetString(textStringName); + + if (textString != null) + { + control.Text = textString; + } + + // Location + string locationStringName = baseName + ".Location"; + string locationString = this.StringsResourceManager.GetString(locationStringName); + + if (locationString != null) + { + try + { + int x; + int y; + + ParsePair(locationString, out x, out y); + control.Location = new Point(x, y); + } + + catch (Exception ex) + { + Tracing.Ping(locationStringName + " is invalid: " + locationString + ", exception: " + ex.ToString()); + } + } + + // Size + string sizeStringName = baseName + ".Size"; + string sizeString = this.StringsResourceManager.GetString(sizeStringName); + + if (sizeString != null) + { + try + { + int width; + int height; + + ParsePair(sizeString, out width, out height); + control.Size = new Size(width, height); + } + + catch (Exception ex) + { + Tracing.Ping(sizeStringName + " is invalid: " + sizeString + ", exception: " + ex.ToString()); + } + } + + // Recurse + foreach (Control child in control.Controls) + { + if (child.Name == null || child.Name.Length > 0) + { + string newBaseName = baseName + "." + child.Name; + LoadLocalizedResources(newBaseName, child); + } + else + { + Tracing.Ping("Name property not set for an instance of " + child.GetType().Name + " within " + baseName); + } + } + } + + protected override void OnClosing(CancelEventArgs e) + { + base.OnClosing(e); + + if (!e.Cancel) + { + this.ForceActiveTitleBar = false; + } + } + + private void EnableOpacityChangedHandler(object sender, EventArgs e) + { + DecideOpacitySetting(); + } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated (e); + + PdnBaseForm.EnableOpacityChanged += new EventHandler(EnableOpacityChangedHandler); + UserSessions.SessionChanged += new EventHandler(UserSessions_SessionChanged); + DecideOpacitySetting(); + } + + protected override void OnHandleDestroyed(EventArgs e) + { + base.OnHandleDestroyed(e); + + PdnBaseForm.EnableOpacityChanged -= new EventHandler(EnableOpacityChangedHandler); + UserSessions.SessionChanged -= new EventHandler(UserSessions_SessionChanged); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Sets the opacity of the form. + /// + /// The new opacity value. + /// + /// Depending on the system configuration, this request may be ignored. For example, + /// when running within a Terminal Service (or Remote Desktop) session, opacity will + /// always be set to 1.0 for performance reasons. + /// + public new double Opacity + { + get + { + return this.ourOpacity; + } + + set + { + if (enableOpacity) + { + // Bypassing Form.Opacity eliminates a "black flickering" that occurs when + // the form transitions from Opacity=1.0 to Opacity != 1.0, or vice versa. + // It appears to be a result of toggling the WS_EX_LAYERED style, or the + // fact that Form.Opacity re-applies visual styles when this value transition + // takes place. + SystemLayer.UI.SetFormOpacity(this, value); + } + + this.ourOpacity = value; + } + } + + /// + /// Decides whether or not to have opacity be enabled. + /// + private void DecideOpacitySetting() + { + if (UserSessions.IsRemote || !PdnBaseForm.globalEnableOpacity || !this.EnableInstanceOpacity) + { + if (this.enableOpacity) + { + try + { + UI.SetFormOpacity(this, 1.0); + } + + // This fails in certain odd situations (bug #746), so we just eat the exception. + catch (System.ComponentModel.Win32Exception) + { + } + } + + this.enableOpacity = false; + } + else + { + if (!this.enableOpacity) + { + // This fails in certain odd situations (bug #746), so we just eat the exception. + try + { + UI.SetFormOpacity(this, this.ourOpacity); + } + + catch (System.ComponentModel.Win32Exception) + { + } + } + + this.enableOpacity = true; + } + } + + public double ScreenAspect + { + get + { + Rectangle bounds = System.Windows.Forms.Screen.FromControl(this).Bounds; + double aspect = (double)bounds.Width / (double)bounds.Height; + return aspect; + } + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.SuspendLayout(); + // + // PdnBaseForm + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(291, 270); + this.Name = "PdnBaseForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; + this.Text = "PdnBaseForm"; + this.ResumeLayout(false); + + } + #endregion + + public event MovingEventHandler Moving; + protected virtual void OnMoving(MovingEventArgs mea) + { + if (Moving != null) + { + Moving(this, mea); + } + } + + public event CancelEventHandler QueryEndSession; + protected virtual void OnQueryEndSession(CancelEventArgs e) + { + if (QueryEndSession != null) + { + QueryEndSession(this, e); + } + } + + private void UserSessions_SessionChanged(object sender, EventArgs e) + { + this.DecideOpacitySetting(); + } + + void RealWndProc(ref Message m) + { + OurWndProc(ref m); + } + + protected override void WndProc(ref Message m) + { + if (this.formEx == null) + { + base.WndProc(ref m); + } + else if (!this.formEx.HandleParentWndProc(ref m)) + { + OurWndProc(ref m); + } + } + + private void OurWndProc(ref Message m) + { + switch (m.Msg) + { + case 0x0216: // WM_MOVING + unsafe + { + int *p = (int *)m.LParam; + Rectangle rect = Rectangle.FromLTRB(p[0], p[1], p[2], p[3]); + + MovingEventArgs mea = new MovingEventArgs(rect); + OnMoving(mea); + + p[0] = mea.Rectangle.Left; + p[1] = mea.Rectangle.Top; + p[2] = mea.Rectangle.Right; + p[3] = mea.Rectangle.Bottom; + + m.Result = new IntPtr(1); + } + break; + + // WinForms doesn't handle this message correctly and wrongly returns 0 instead of 1. + case 0x0011: // WM_QUERYENDSESSION + CancelEventArgs e = new CancelEventArgs(); + OnQueryEndSession(e); + m.Result = e.Cancel ? IntPtr.Zero : new IntPtr(1); + break; + + default: + base.WndProc(ref m); + break; + } + } + + public SnapManager SnapManager + { + get + { + if (this.snapManager == null) + { + this.snapManager = new SnapManager(); + } + + return snapManager; + } + } + + public Size ClientSizeToWindowSize(Size clientSize) + { + Size baseClientSize = ClientSize; + Size baseWindowSize = Size; + + int extraWidth = baseWindowSize.Width - baseClientSize.Width; + int extraHeight = baseWindowSize.Height - baseClientSize.Height; + + Size windowSize = new Size(clientSize.Width + extraWidth, clientSize.Height + extraHeight); + return windowSize; + } + + public Size WindowSizeToClientSize(Size windowSize) + { + Size baseClientSize = ClientSize; + Size baseWindowSize = Size; + + int extraWidth = baseWindowSize.Width - baseClientSize.Width; + int extraHeight = baseWindowSize.Height - baseClientSize.Height; + Size clientSize = new Size(windowSize.Width - extraWidth, windowSize.Height - extraHeight); + + return clientSize; + } + + public Rectangle ClientBoundsToWindowBounds(Rectangle clientBounds) + { + Rectangle currentBounds = this.Bounds; + Rectangle currentClientBounds = this.RectangleToScreen(ClientRectangle); + + Rectangle newWindowBounds = new Rectangle( + clientBounds.Left - (currentClientBounds.Left - currentBounds.Left), + clientBounds.Top - (currentClientBounds.Top - currentBounds.Top), + clientBounds.Width + (currentBounds.Width - currentClientBounds.Width), + clientBounds.Height + (currentBounds.Height - currentClientBounds.Height)); + + return newWindowBounds; + } + + public Rectangle WindowBoundsToClientBounds(Rectangle windowBounds) + { + Rectangle currentBounds = this.Bounds; + Rectangle currentClientBounds = this.RectangleToScreen(ClientRectangle); + + Rectangle newClientBounds = new Rectangle( + windowBounds.Left + (currentClientBounds.Left - currentBounds.Left), + windowBounds.Top + (currentClientBounds.Top - currentBounds.Top), + windowBounds.Width - (currentBounds.Width - currentClientBounds.Width), + windowBounds.Height - (currentBounds.Height - currentClientBounds.Height)); + + return newClientBounds; + } + + public void EnsureFormIsOnScreen() + { + if (this.WindowState == FormWindowState.Maximized) + { + return; + } + + if (this.WindowState == FormWindowState.Minimized) + { + return; + } + + Screen ourScreen; + + try + { + ourScreen = Screen.FromControl(this); + } + + catch (Exception) + { + ourScreen = null; + } + + if (ourScreen == null) + { + ourScreen = Screen.PrimaryScreen; + } + + Rectangle currentBounds = Bounds; + Rectangle newBounds = EnsureRectIsOnScreen(ourScreen, currentBounds); + Bounds = newBounds; + } + + public static Rectangle EnsureRectIsOnScreen(Screen screen, Rectangle bounds) + { + Rectangle newBounds = bounds; + Rectangle screenBounds = screen.WorkingArea; + + // Make sure the bottom and right do not fall off the edge, by moving the bounds + if (newBounds.Right > screenBounds.Right) + { + newBounds.X -= (newBounds.Right - screenBounds.Right); + } + + if (newBounds.Bottom > screenBounds.Bottom) + { + newBounds.Y -= (newBounds.Bottom - screenBounds.Bottom); + } + + // Make sure the top and left haven't fallen off, by moving + if (newBounds.Left < screenBounds.Left) + { + newBounds.X = screenBounds.Left; + } + + if (newBounds.Top < screenBounds.Top) + { + newBounds.Y = screenBounds.Top; + } + + // Make sure that we are not too wide / tall, by resizing + if (newBounds.Right > screenBounds.Right) + { + newBounds.Width -= (newBounds.Right - screenBounds.Right); + } + + if (newBounds.Bottom > screenBounds.Bottom) + { + newBounds.Height -= (newBounds.Bottom - screenBounds.Bottom); + } + + // All done. + return newBounds; + } + } +} diff --git a/src/Core/PdnBaseForm.resx b/src/Core/PdnBaseForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/PdnException.cs b/src/Core/PdnException.cs new file mode 100644 index 0000000..f04460c --- /dev/null +++ b/src/Core/PdnException.cs @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + /// + /// This is the base exception for all Paint.NET exceptions. + /// + public class PdnException + : ApplicationException + { + public PdnException() + { + } + + public PdnException(string message) + : base(message) + { + } + + public PdnException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected PdnException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Core/PdnGraphicsPath.cs b/src/Core/PdnGraphicsPath.cs new file mode 100644 index 0000000..a048f10 --- /dev/null +++ b/src/Core/PdnGraphicsPath.cs @@ -0,0 +1,1233 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + [Serializable] + public sealed class PdnGraphicsPath + : MarshalByRefObject, + ICloneable, + IDisposable, + ISerializable + { + private GraphicsPath gdiPath; + private bool tooComplex = false; + internal PdnRegion regionCache = null; + + public static implicit operator GraphicsPath(PdnGraphicsPath convert) + { + return convert.gdiPath; + } + + internal PdnRegion GetRegionCache() + { + if (regionCache == null) + { + regionCache = new PdnRegion(this.gdiPath); + } + + return regionCache; + } + + private GraphicsPath GdiPath + { + get + { + return gdiPath; + } + } + + private void Changed() + { + if (regionCache != null) + { + lock (regionCache.SyncRoot) + { + regionCache.Dispose(); + regionCache = null; + } + } + } + + public PdnGraphicsPath() + { + Changed(); + gdiPath = new GraphicsPath(); + } + + public PdnGraphicsPath(GraphicsPath wrapMe) + { + Changed(); + gdiPath = wrapMe; + } + + public PdnGraphicsPath(FillMode fillMode) + { + Changed(); + gdiPath = new GraphicsPath(fillMode); + } + + public PdnGraphicsPath(Point[] pts, byte[] types) + { + Changed(); + gdiPath = new GraphicsPath(pts, types); + } + + public PdnGraphicsPath(PointF[] pts, byte[] types) + { + Changed(); + gdiPath = new GraphicsPath(pts, types); + } + + public PdnGraphicsPath(Point[] pts, byte[] types, FillMode fillMode) + { + Changed(); + gdiPath = new GraphicsPath(pts, types, fillMode); + } + + public PdnGraphicsPath(PointF[] pts, byte[] types, FillMode fillMode) + { + Changed(); + gdiPath = new GraphicsPath(pts, types, fillMode); + } + + public PdnGraphicsPath(SerializationInfo info, StreamingContext context) + { + int ptCount = info.GetInt32("ptCount"); + + PointF[] pts; + byte[] types; + + if (ptCount == 0) + { + pts = new PointF[0]; + types = new byte[0]; + } + else + { + pts = (PointF[])info.GetValue("pts", typeof(PointF[])); + types = (byte[])info.GetValue("types", typeof(byte[])); + } + + FillMode fillMode = (FillMode)info.GetValue("fillMode", typeof(FillMode)); + Changed(); + + if (ptCount == 0) + { + gdiPath = new GraphicsPath(); + } + else + { + gdiPath = new GraphicsPath(pts, types, fillMode); + } + + this.tooComplex = false; + this.regionCache = null; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + lock (this) // avoid race condition with Dispose() + { + info.AddValue("ptCount", this.gdiPath.PointCount); + + if (this.gdiPath.PointCount > 0) + { + info.AddValue("pts", this.gdiPath.PathPoints); + info.AddValue("types", this.gdiPath.PathTypes); + } + + info.AddValue("fillMode", this.gdiPath.FillMode); + } + } + + public static PdnGraphicsPath FromRegion(PdnRegion region) + { + Rectangle[] scans = region.GetRegionScansReadOnlyInt(); + + if (scans.Length == 1) + { + PdnGraphicsPath path = new PdnGraphicsPath(); + path.AddRectangle(scans[0]); + path.CloseFigure(); + return path; + } + else + { + Rectangle bounds = region.GetBoundsInt(); + BitVector2D stencil = new BitVector2D(bounds.Width, bounds.Height); + + for (int i = 0; i < scans.Length; ++i) + { + Rectangle rect = scans[i]; + rect.X -= bounds.X; + rect.Y -= bounds.Y; + + stencil.SetUnchecked(rect, true); + } + + PdnGraphicsPath path = PathFromStencil(stencil, new Rectangle(0, 0, stencil.Width, stencil.Height)); + + using (Matrix matrix = new Matrix()) + { + matrix.Reset(); + matrix.Translate(bounds.X, bounds.Y); + path.Transform(matrix); + } + + return path; + } + } + + public static PdnGraphicsPath FromRegions(PdnRegion lhs, CombineMode combineMode, PdnRegion rhs) + { + Rectangle lhsBounds = lhs.GetBoundsInt(); + Rectangle rhsBounds = rhs.GetBoundsInt(); + int left = Math.Min(lhsBounds.Left, rhsBounds.Left); + int top = Math.Min(lhsBounds.Top, rhsBounds.Top); + int right = Math.Max(lhsBounds.Right, rhsBounds.Right); + int bottom = Math.Max(lhsBounds.Bottom, rhsBounds.Bottom); + Rectangle bounds = Rectangle.FromLTRB(left, top, right, bottom); + BitVector2D stencil = new BitVector2D(bounds.Width, bounds.Height); + Rectangle[] lhsScans = lhs.GetRegionScansReadOnlyInt(); + Rectangle[] rhsScans = rhs.GetRegionScansReadOnlyInt(); + + switch (combineMode) + { + case CombineMode.Complement: + case CombineMode.Intersect: + case CombineMode.Replace: + throw new ArgumentException("combineMode can't be Complement, Intersect, or Replace"); + + default: + break; + } + + for (int i = 0; i < lhsScans.Length; ++i) + { + Rectangle rect = lhsScans[i]; + rect.X -= bounds.X; + rect.Y -= bounds.Y; + + stencil.SetUnchecked(rect, true); + } + + for (int i = 0; i < rhsScans.Length; ++i) + { + Rectangle rect = rhsScans[i]; + rect.X -= bounds.X; + rect.Y -= bounds.Y; + + switch (combineMode) + { + case CombineMode.Xor: + stencil.InvertUnchecked(rect); + break; + + case CombineMode.Union: + stencil.SetUnchecked(rect, true); + break; + + case CombineMode.Exclude: + stencil.SetUnchecked(rect, false); + break; + } + } + + PdnGraphicsPath path = PathFromStencil(stencil, new Rectangle(0, 0, stencil.Width, stencil.Height)); + + using (Matrix matrix = new Matrix()) + { + matrix.Reset(); + matrix.Translate(bounds.X, bounds.Y); + path.Transform(matrix); + } + + return path; + } + + public unsafe static Point[][] PolygonSetFromStencil(IBitVector2D stencil, Rectangle bounds, int translateX, int translateY) + { + List polygons = new List(); + + if (!stencil.IsEmpty) + { + Point start = bounds.Location; + List pts = new List(); + int count = 0; + + // find all islands + while (true) + { + bool startFound = false; + + while (true) + { + if (stencil[start]) + { + startFound = true; + break; + } + + ++start.X; + + if (start.X >= bounds.Right) + { + ++start.Y; + start.X = bounds.Left; + + if (start.Y >= bounds.Bottom) + { + break; + } + } + } + + if (!startFound) + { + break; + } + + pts.Clear(); + Point last = new Point(start.X, start.Y + 1); + Point curr = new Point(start.X, start.Y); + Point next = curr; + Point left = Point.Empty; + Point right = Point.Empty; + + // trace island outline + while (true) + { + left.X = ((curr.X - last.X) + (curr.Y - last.Y) + 2) / 2 + curr.X - 1; + left.Y = ((curr.Y - last.Y) - (curr.X - last.X) + 2) / 2 + curr.Y - 1; + + right.X = ((curr.X - last.X) - (curr.Y - last.Y) + 2) / 2 + curr.X - 1; + right.Y = ((curr.Y - last.Y) + (curr.X - last.X) + 2) / 2 + curr.Y - 1; + + if (bounds.Contains(left) && stencil[left]) + { + // go left + next.X += curr.Y - last.Y; + next.Y -= curr.X - last.X; + } + else if (bounds.Contains(right) && stencil[right]) + { + // go straight + next.X += curr.X - last.X; + next.Y += curr.Y - last.Y; + } + else + { + // turn right + next.X -= curr.Y - last.Y; + next.Y += curr.X - last.X; + } + + if (Math.Sign(next.X - curr.X) != Math.Sign(curr.X - last.X) || + Math.Sign(next.Y - curr.Y) != Math.Sign(curr.Y - last.Y)) + { + pts.Add(curr); + ++count; + } + + last = curr; + curr = next; + + if (next.X == start.X && next.Y == start.Y) + { + break; + } + } + + Point[] points = pts.ToArray(); + Scanline[] scans = Utility.GetScans(points); + + foreach (Scanline scan in scans) + { + stencil.Invert(scan); + } + + Utility.TranslatePointsInPlace(points, translateX, translateY); + polygons.Add(points); + } + } + + Point[][] returnVal = polygons.ToArray(); + return returnVal; + } + + /// + /// Creates a graphics path from the given stencil buffer. It should be filled with 'true' values + /// to indicate the areas that should be outlined. + /// + /// The stencil buffer to read from. NOTE: The contents of this will be destroyed when this method returns. + /// The bounding box within the stencil buffer to limit discovery to. + /// A PdnGraphicsPath with traces that outline the various areas from the given stencil buffer. + public unsafe static PdnGraphicsPath PathFromStencil(IBitVector2D stencil, Rectangle bounds) + { + if (stencil.IsEmpty) + { + return new PdnGraphicsPath(); + } + + PdnGraphicsPath ret = new PdnGraphicsPath(); + Point start = bounds.Location; + Vector pts = new Vector(); + int count = 0; + + // find all islands + while (true) + { + bool startFound = false; + + while (true) + { + if (stencil[start]) + { + startFound = true; + break; + } + + ++start.X; + + if (start.X >= bounds.Right) + { + ++start.Y; + start.X = bounds.Left; + + if (start.Y >= bounds.Bottom) + { + break; + } + } + } + + if (!startFound) + { + break; + } + + pts.Clear(); + Point last = new Point(start.X, start.Y + 1); + Point curr = new Point(start.X, start.Y); + Point next = curr; + Point left = Point.Empty; + Point right = Point.Empty; + + // trace island outline + while (true) + { + left.X = ((curr.X - last.X) + (curr.Y - last.Y) + 2) / 2 + curr.X - 1; + left.Y = ((curr.Y - last.Y) - (curr.X - last.X) + 2) / 2 + curr.Y - 1; + + right.X = ((curr.X - last.X) - (curr.Y - last.Y) + 2) / 2 + curr.X - 1; + right.Y = ((curr.Y - last.Y) + (curr.X - last.X) + 2) / 2 + curr.Y - 1; + + if (bounds.Contains(left) && stencil[left]) + { + // go left + next.X += curr.Y - last.Y; + next.Y -= curr.X - last.X; + } + else if (bounds.Contains(right) && stencil[right]) + { + // go straight + next.X += curr.X - last.X; + next.Y += curr.Y - last.Y; + } + else + { + // turn right + next.X -= curr.Y - last.Y; + next.Y += curr.X - last.X; + } + + if (Math.Sign(next.X - curr.X) != Math.Sign(curr.X - last.X) || + Math.Sign(next.Y - curr.Y) != Math.Sign(curr.Y - last.Y)) + { + pts.Add(curr); + ++count; + } + + last = curr; + curr = next; + + if (next.X == start.X && next.Y == start.Y) + { + break; + } + } + + Point[] points = pts.ToArray(); + Scanline[] scans = Utility.GetScans(points); + + foreach (Scanline scan in scans) + { + stencil.Invert(scan); + } + + ret.AddLines(points); + ret.CloseFigure(); + } + + return ret; + } + + public static PdnGraphicsPath Combine(PdnGraphicsPath subjectPath, CombineMode combineMode, PdnGraphicsPath clipPath) + { + switch (combineMode) + { + case CombineMode.Complement: + return Combine(clipPath, CombineMode.Exclude, subjectPath); + + case CombineMode.Replace: + return clipPath.Clone(); + + case CombineMode.Xor: + case CombineMode.Intersect: + case CombineMode.Union: + case CombineMode.Exclude: + if (subjectPath.IsEmpty && clipPath.IsEmpty) + { + return new PdnGraphicsPath(); // empty path + } + else if (subjectPath.IsEmpty) + { + switch (combineMode) + { + case CombineMode.Xor: + case CombineMode.Union: + return clipPath.Clone(); + + case CombineMode.Intersect: + case CombineMode.Exclude: + return new PdnGraphicsPath(); + + default: + throw new InvalidEnumArgumentException(); + } + } + else if (clipPath.IsEmpty) + { + switch (combineMode) + { + case CombineMode.Exclude: + case CombineMode.Xor: + case CombineMode.Union: + return subjectPath.Clone(); + + case CombineMode.Intersect: + return new PdnGraphicsPath(); + + default: + throw new InvalidEnumArgumentException(); + } + } + else + { + GraphicsPath resultPath = PdnGraphics.ClipPath(subjectPath, combineMode, clipPath); + return new PdnGraphicsPath(resultPath); + } + + default: + throw new InvalidEnumArgumentException(); + } + } + + + ~PdnGraphicsPath() + { + Changed(); + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + lock (this) // avoid race condition with GetObjectData() + { + if (gdiPath != null) + { + gdiPath.Dispose(); + gdiPath = null; + } + + if (regionCache != null) + { + regionCache.Dispose(); + regionCache = null; + } + } + } + } + + public FillMode FillMode + { + get + { + return gdiPath.FillMode; + } + + set + { + Changed(); + gdiPath.FillMode = value; + } + } + + public PathData PathData + { + get + { + return gdiPath.PathData; + } + } + + public PointF[] PathPoints + { + get + { + return gdiPath.PathPoints; + } + } + + public byte[] PathTypes + { + get + { + return gdiPath.PathTypes; + } + } + + public int PointCount + { + get + { + return gdiPath.PointCount; + } + } + + public void AddArc(Rectangle rect, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddArc(rect, startAngle, sweepAngle); + } + + public void AddArc(RectangleF rectF, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddArc(rectF, startAngle, sweepAngle); + } + + public void AddArc(int x, int y, int width, int height, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddArc(x, y, width, height, startAngle, sweepAngle); + } + + public void AddArc(float x, float y, float width, float height, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddArc(x, y, width, height, startAngle, sweepAngle); + } + + public void AddBezier(Point pt1, Point pt2, Point pt3, Point pt4) + { + Changed(); + gdiPath.AddBezier(pt1, pt2, pt3, pt4); + } + + public void AddBezier(PointF pt1, PointF pt2, PointF pt3, PointF pt4) + { + Changed(); + gdiPath.AddBezier(pt1, pt2, pt3, pt4); + } + + public void AddBezier(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) + { + Changed(); + gdiPath.AddBezier(x1, y1, x2, y2, x3, y3, x4, y4); + } + + public void AddBezier(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) + { + Changed(); + gdiPath.AddBezier(x1, y1, x2, y2, x3, y3, x4, y4); + } + + public void AddBeziers(Point[] points) + { + Changed(); + gdiPath.AddBeziers(points); + } + + public void AddBeziers(PointF[] points) + { + Changed(); + gdiPath.AddBeziers(points); + } + + public void AddClosedCurve(Point[] points) + { + Changed(); + gdiPath.AddClosedCurve(points); + } + + public void AddClosedCurve(PointF[] points) + { + Changed(); + gdiPath.AddClosedCurve(points); + } + + public void AddClosedCurve(Point[] points, float tension) + { + Changed(); + gdiPath.AddClosedCurve(points, tension); + } + + public void AddClosedCurve(PointF[] points, float tension) + { + Changed(); + gdiPath.AddClosedCurve(points, tension); + } + + public void AddCurve(Point[] points) + { + Changed(); + gdiPath.AddCurve(points); + } + + public void AddCurve(PointF[] points) + { + Changed(); + gdiPath.AddCurve(points); + } + + public void AddCurve(Point[] points, float tension) + { + Changed(); + gdiPath.AddCurve(points, tension); + } + + public void AddCurve(PointF[] points, float tension) + { + Changed(); + gdiPath.AddCurve(points, tension); + } + + public void AddCurve(Point[] points, int offset, int numberOfSegments, float tension) + { + Changed(); + gdiPath.AddCurve(points, offset, numberOfSegments, tension); + } + + public void AddCurve(PointF[] points, int offset, int numberOfSegments, float tension) + { + Changed(); + gdiPath.AddCurve(points, offset, numberOfSegments, tension); + } + + public void AddEllipse(Rectangle rect) + { + Changed(); + gdiPath.AddEllipse(rect); + } + + public void AddEllipse(RectangleF rectF) + { + Changed(); + gdiPath.AddEllipse(rectF); + } + + public void AddEllipse(int x, int y, int width, int height) + { + Changed(); + gdiPath.AddEllipse(x, y, width, height); + } + + public void AddEllipse(float x, float y, float width, float height) + { + Changed(); + gdiPath.AddEllipse(x, y, width, height); + } + + public void AddLine(Point pt1, Point pt2) + { + Changed(); + gdiPath.AddLine(pt1, pt2); + } + + public void AddLine(PointF pt1, PointF pt2) + { + Changed(); + gdiPath.AddLine(pt1, pt2); + } + + public void AddLine(int x1, int y1, int x2, int y2) + { + Changed(); + gdiPath.AddLine(x1, y1, x2, y2); + } + + public void AddLine(float x1, float y1, float x2, float y2) + { + Changed(); + gdiPath.AddLine(x1, y1, x2, y2); + } + + public void AddLines(Point[] points) + { + Changed(); + gdiPath.AddLines(points); + } + + public void AddLines(PointF[] points) + { + Changed(); + gdiPath.AddLines(points); + } + + public void AddPath(GraphicsPath addingPath, bool connect) + { + if (addingPath.PointCount != 0) + { + Changed(); + gdiPath.AddPath(addingPath, connect); + } + } + + public void AddPie(Rectangle rect, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddPie(rect, startAngle, sweepAngle); + } + + public void AddPie(int x, int y, int width, int height, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddPie(x, y, width, height, startAngle, sweepAngle); + } + + public void AddPie(float x, float y, float width, float height, float startAngle, float sweepAngle) + { + Changed(); + gdiPath.AddPie(x, y, width, height, startAngle, sweepAngle); + } + + public void AddPolygon(Point[] points) + { + Changed(); + gdiPath.AddPolygon(points); + } + + public void AddPolygon(PointF[] points) + { + Changed(); + gdiPath.AddPolygon(points); + } + + public void AddPolygons(PointF[][] polygons) + { + foreach (PointF[] polygon in polygons) + { + AddPolygon(polygon); + CloseFigure(); + } + } + + public void AddPolygons(Point[][] polygons) + { + foreach (Point[] polygon in polygons) + { + AddPolygon(polygon); + CloseFigure(); + } + } + + public void AddRectangle(Rectangle rect) + { + Changed(); + gdiPath.AddRectangle(rect); + } + + public void AddRectangle(RectangleF rectF) + { + Changed(); + gdiPath.AddRectangle(rectF); + } + + public void AddRectangles(Rectangle[] rects) + { + Changed(); + gdiPath.AddRectangles(rects); + } + + public void AddRectangles(RectangleF[] rectsF) + { + Changed(); + gdiPath.AddRectangles(rectsF); + } + + public void AddString(string s, FontFamily family, int style, float emSize, Point origin, StringFormat format) + { + Changed(); + gdiPath.AddString(s, family, style, emSize, origin, format); + } + + public void AddString(string s, FontFamily family, int style, float emSize, PointF origin, StringFormat format) + { + Changed(); + gdiPath.AddString(s, family, style, emSize, origin, format); + } + + public void AddString(string s, FontFamily family, int style, float emSize, Rectangle layoutRect, StringFormat format) + { + Changed(); + gdiPath.AddString(s, family, style, emSize, layoutRect, format); + } + + public void AddString(string s, FontFamily family, int style, float emSize, RectangleF layoutRect, StringFormat format) + { + Changed(); + gdiPath.AddString(s, family, style, emSize, layoutRect, format); + } + + public void ClearMarkers() + { + Changed(); + gdiPath.ClearMarkers(); + } + + object ICloneable.Clone() + { + return Clone(); + } + + public PdnGraphicsPath Clone() + { + PdnGraphicsPath path = new PdnGraphicsPath((GraphicsPath)gdiPath.Clone()); + path.tooComplex = this.tooComplex; + return path; + } + + public void CloseAllFigures() + { + Changed(); + gdiPath.CloseAllFigures(); + } + + public void CloseFigure() + { + Changed(); + gdiPath.CloseFigure(); + } + + public void Draw(Graphics g, Pen pen) + { + Draw(g, pen, false); + } + + /// + /// Draws the path to the given Graphics context using the given Pen. + /// + /// The Graphics context to draw to. + /// The Pen to draw with. + /// + /// If true, gives a hint that the path is being drawn to be presented to the user. + /// + /// + /// If the path is "too complex," and if presentationIntent is true, then the path will + /// not be drawn. To force the path to be drawn, set presentationIntent to false. + /// + public void Draw(Graphics g, Pen pen, bool presentationIntent) + { + try + { + if (!tooComplex || !presentationIntent) + { + int start = Environment.TickCount; + g.DrawPath(pen, this.gdiPath); + int end = Environment.TickCount; + + if ((end - start) > 1000) + { + tooComplex = true; + } + } + } + + catch (OutOfMemoryException ex) + { + tooComplex = true; + Tracing.Ping("DrawPath exception: " + ex); + } + } + + public void Flatten() + { + Changed(); + gdiPath.Flatten(); + } + + public void Flatten(Matrix matrix) + { + Changed(); + gdiPath.Flatten(matrix); + } + + public void Flatten(Matrix matrix, float flatness) + { + Changed(); + gdiPath.Flatten(matrix, flatness); + } + + public RectangleF GetBounds2() + { + if (this.PointCount == 0) + { + return RectangleF.Empty; + } + + PointF[] points = this.PathPoints; + + if (points.Length == 0) + { + return RectangleF.Empty; + } + + float left = points[0].X; + float right = points[0].X; + float top = points[0].Y; + float bottom = points[0].Y; + + for (int i = 1; i < points.Length; ++i) + { + if (points[i].X < left) + { + left = points[i].X; + } + + if (points[i].Y < top) + { + top = points[i].Y; + } + + if (points[i].X > right) + { + right = points[i].X; + } + + if (points[i].Y > bottom) + { + bottom = points[i].Y; + } + } + + return RectangleF.FromLTRB(left, top, right, bottom); + } + + public RectangleF GetBounds() + { + return gdiPath.GetBounds(); + } + + public RectangleF GetBounds(Matrix matrix) + { + return gdiPath.GetBounds(matrix); + } + + public RectangleF GetBounds(Matrix matrix, Pen pen) + { + return gdiPath.GetBounds(matrix, pen); + } + + public PointF GetLastPoint() + { + return gdiPath.GetLastPoint(); + } + + public bool IsEmpty + { + get + { + return this.PointCount == 0; + } + } + + public bool IsOutlineVisible(Point point, Pen pen) + { + return gdiPath.IsOutlineVisible(point, pen); + } + + public bool IsOutlineVisible(PointF point, Pen pen) + { + return gdiPath.IsOutlineVisible(point, pen); + } + + public bool IsOutlineVisible(int x, int y, Pen pen) + { + return gdiPath.IsOutlineVisible(x, y, pen); + } + + public bool IsOutlineVisible(Point point, Pen pen, Graphics g) + { + return gdiPath.IsOutlineVisible(point, pen, g); + } + + public bool IsOutlineVisible(PointF point, Pen pen, Graphics g) + { + return gdiPath.IsOutlineVisible(point, pen, g); + } + + public bool IsOutlineVisible(float x, float y, Pen pen) + { + return gdiPath.IsOutlineVisible(x, y, pen); + } + + public bool IsOutlineVisible(int x, int y, Pen pen, Graphics g) + { + return gdiPath.IsOutlineVisible(x, y, pen, g); + } + + public bool IsOutlineVisible(float x, float y, Pen pen, Graphics g) + { + return gdiPath.IsOutlineVisible(x, y, pen, g); + } + + public bool IsVisible(Point point) + { + return gdiPath.IsVisible(point); + } + + public bool IsVisible(PointF point) + { + return gdiPath.IsVisible(point); + } + + public bool IsVisible(int x, int y) + { + return gdiPath.IsVisible(x, y); + } + + public bool IsVisible(Point point, Graphics g) + { + return gdiPath.IsVisible(point, g); + } + + public bool IsVisible(PointF point, Graphics g) + { + return gdiPath.IsVisible(point, g); + } + + public bool IsVisible(float x, float y) + { + return gdiPath.IsVisible(x, y); + } + + public bool IsVisible(int x, int y, Graphics g) + { + return gdiPath.IsVisible(x, y, g); + } + + public bool IsVisible(float x, float y, Graphics g) + { + return gdiPath.IsVisible(x, y, g); + } + + public void Reset() + { + Changed(); + this.tooComplex = false; + gdiPath.Reset(); + } + + public void Reverse() + { + Changed(); + gdiPath.Reverse(); + } + + public void SetMarkers() + { + Changed(); + gdiPath.SetMarkers(); + } + + public void StartFigure() + { + Changed(); + gdiPath.StartFigure(); + } + + public void Transform(Matrix matrix) + { + Changed(); + gdiPath.Transform(matrix); + } + + public void Warp(PointF[] destPoints, RectangleF srcRect) + { + Changed(); + gdiPath.Warp(destPoints, srcRect); + } + + public void Warp(PointF[] destPoints, RectangleF srcRect, Matrix matrix) + { + Changed(); + gdiPath.Warp(destPoints, srcRect, matrix); + } + + public void Warp(PointF[] destPoints, RectangleF srcRect, Matrix matrix, WarpMode warpMode) + { + Changed(); + gdiPath.Warp(destPoints, srcRect, matrix, warpMode); + } + + public void Warp(PointF[] destPoints, RectangleF srcRect, Matrix matrix, WarpMode warpMode, float flatness) + { + Changed(); + gdiPath.Warp(destPoints, srcRect, matrix, warpMode, flatness); + } + + public void Widen(Pen pen) + { + Changed(); + gdiPath.Widen(pen); + } + + public void Widen(Pen pen, Matrix matrix) + { + Changed(); + gdiPath.Widen(pen, matrix); + } + + public void Widen(Pen pen, Matrix matrix, float flatness) + { + Changed(); + gdiPath.Widen(pen, matrix, flatness); + } + } +} diff --git a/src/Core/PdnNumericUpDown.cs b/src/Core/PdnNumericUpDown.cs new file mode 100644 index 0000000..b57bcd7 --- /dev/null +++ b/src/Core/PdnNumericUpDown.cs @@ -0,0 +1,55 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// This class implements some common things on top of the regular NumericUpDown class. + /// + public class PdnNumericUpDown + : NumericUpDown + { + public PdnNumericUpDown() + { + TextAlign = HorizontalAlignment.Right; + } + + protected override void OnEnter(EventArgs e) + { + Select(0, Text.Length); + base.OnEnter(e); + } + + protected override void OnLeave(EventArgs e) + { + if (Value < Minimum) + { + Value = Minimum; + } + else if (Value > Maximum) + { + Value = Maximum; + } + + decimal parsedValue; + + if (decimal.TryParse(Text, out parsedValue)) + { + Value = parsedValue; + } + + base.OnLeave(e); + } + } +} diff --git a/src/Core/PdnRegion.cs b/src/Core/PdnRegion.cs new file mode 100644 index 0000000..9effe19 --- /dev/null +++ b/src/Core/PdnRegion.cs @@ -0,0 +1,771 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; + +namespace PaintDotNet +{ + /// + /// Designed as a proxy to the GDI+ Region class, while allowing for a + /// replacement that won't break code. The main reason for having this + /// right now is to work around some bugs in System.Drawing.Region, + /// especially the memory leak in GetRegionScans(). + /// + [Serializable] + public sealed class PdnRegion + : ISerializable, + IDisposable + { + private object lockObject = new object(); + private Region gdiRegion; + private bool changed = true; + private int cachedArea = -1; + private Rectangle cachedBounds = Rectangle.Empty; + private RectangleF[] cachedRectsF = null; + private Rectangle[] cachedRects = null; + + public object SyncRoot + { + get + { + return lockObject; + } + } + + public int GetArea() + { + lock (SyncRoot) + { + int theCachedArea = cachedArea; + + if (theCachedArea == -1) + { + int ourCachedArea = 0; + + foreach (Rectangle rect in GetRegionScansReadOnlyInt()) + { + try + { + ourCachedArea += rect.Width * rect.Height; + } + + catch (System.OverflowException) + { + ourCachedArea = int.MaxValue; + break; + } + } + + cachedArea = ourCachedArea; + return ourCachedArea; + } + else + { + return theCachedArea; + } + } + } + + private bool IsChanged() + { + return this.changed; + } + + private void Changed() + { + lock (SyncRoot) + { + this.changed = true; + this.cachedArea = -1; + this.cachedBounds = Rectangle.Empty; + } + } + + private void ResetChanged() + { + lock (SyncRoot) + { + this.changed = false; + } + } + + public PdnRegion() + { + this.gdiRegion = new Region(); + } + + public PdnRegion(GraphicsPath path) + { + this.gdiRegion = new Region(path); + } + + public PdnRegion(PdnGraphicsPath pdnPath) + : this(pdnPath.GetRegionCache()) + { + } + + public PdnRegion(Rectangle rect) + { + this.gdiRegion = new Region(rect); + } + + public PdnRegion(RectangleF rectF) + { + this.gdiRegion = new Region(rectF); + } + + public PdnRegion(RegionData regionData) + { + this.gdiRegion = new Region(regionData); + } + + public PdnRegion(Region region, bool takeOwnership) + { + if (takeOwnership) + { + this.gdiRegion = region; + } + else + { + this.gdiRegion = region.Clone(); + } + } + + public PdnRegion(Region region) + : this(region, false) + { + } + + private PdnRegion(PdnRegion pdnRegion) + { + lock (pdnRegion.SyncRoot) + { + this.gdiRegion = pdnRegion.gdiRegion.Clone(); + this.changed = pdnRegion.changed; + this.cachedArea = pdnRegion.cachedArea; + this.cachedRectsF = pdnRegion.cachedRectsF; + this.cachedRects = pdnRegion.cachedRects; + } + } + + // This constructor is used by WrapRegion. The boolean parameter is just + // there because we already have a parameterless contructor + private PdnRegion(bool sentinel) + { + } + + public static PdnRegion CreateEmpty() + { + PdnRegion region = new PdnRegion(); + region.MakeEmpty(); + return region; + } + + public static PdnRegion WrapRegion(Region region) + { + PdnRegion pdnRegion = new PdnRegion(false); + pdnRegion.gdiRegion = region; + return pdnRegion; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (this.disposed) + { + throw new ObjectDisposedException("PdnRegion"); + } + + RegionData regionData; + + lock (SyncRoot) + { + regionData = this.gdiRegion.GetRegionData(); + } + + byte[] data = regionData.Data; + info.AddValue("data", data); + } + + public PdnRegion(SerializationInfo info, StreamingContext context) + { + byte[] data = (byte[])info.GetValue("data", typeof(byte[])); + + using (Region region = new Region()) + { + RegionData regionData = region.GetRegionData(); + regionData.Data = data; + this.gdiRegion = new Region(regionData); + } + + this.lockObject = new object(); + this.cachedArea = -1; + this.cachedBounds = Rectangle.Empty; + this.changed = true; + this.cachedRects = null; + this.cachedRectsF = null; + } + + public PdnRegion Clone() + { + return new PdnRegion(this); + } + + ~PdnRegion() + { + Dispose(false); + } + + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + lock (SyncRoot) + { + gdiRegion.Dispose(); + gdiRegion = null; + } + } + + disposed = true; + } + } + + public Region GetRegionReadOnly() + { + return this.gdiRegion; + } + + public void Complement(GraphicsPath path) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Complement(path); + } + } + + public void Complement(Rectangle rect) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Complement(rect); + } + } + + public void Complement(RectangleF rectF) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Complement(rectF); + } + } + + public void Complement(Region region) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Complement(region); + } + } + + public void Complement(PdnRegion region2) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Complement(region2.gdiRegion); + } + } + + public void Exclude(GraphicsPath path) + { + lock (SyncRoot) + { + gdiRegion.Exclude(path); + } + } + + public void Exclude(Rectangle rect) + { + lock (SyncRoot) + { + gdiRegion.Exclude(rect); + } + } + + public void Exclude(RectangleF rectF) + { + lock (SyncRoot) + { + gdiRegion.Exclude(rectF); + } + } + + public void Exclude(Region region) + { + lock (SyncRoot) + { + gdiRegion.Exclude(region); + } + } + + public void Exclude(PdnRegion region2) + { + lock (SyncRoot) + { + gdiRegion.Exclude(region2.gdiRegion); + } + } + + public RectangleF GetBounds(Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.GetBounds(g); + } + } + + public RectangleF GetBounds() + { + lock (SyncRoot) + { + using (SystemLayer.NullGraphics nullGraphics = new SystemLayer.NullGraphics()) + { + return gdiRegion.GetBounds(nullGraphics.Graphics); + } + } + } + + public Rectangle GetBoundsInt() + { + Rectangle bounds; + + lock (SyncRoot) + { + bounds = this.cachedBounds; + + if (bounds == Rectangle.Empty) + { + Rectangle[] rects = GetRegionScansReadOnlyInt(); + + if (rects.Length == 0) + { + return Rectangle.Empty; + } + + bounds = rects[0]; + + for (int i = 1; i < rects.Length; ++i) + { + bounds = Rectangle.Union(bounds, rects[i]); + } + + this.cachedBounds = bounds; + } + } + + return bounds; + } + + public RegionData GetRegionData() + { + lock (SyncRoot) + { + return gdiRegion.GetRegionData(); + } + } + + public RectangleF[] GetRegionScans() + { + return (RectangleF[])GetRegionScansReadOnly().Clone(); + } + + /// + /// This is an optimized version of GetRegionScans that returns a reference to the array + /// that is used to cache the region scans. This mitigates performance when this array + /// is requested many times on an unmodified PdnRegion. + /// Thus, by using this method you are promising to not modify the array that is returned. + /// + /// + public RectangleF[] GetRegionScansReadOnly() + { + lock (this.SyncRoot) + { + if (this.changed) + { + UpdateCachedRegionScans(); + } + + if (this.cachedRectsF == null) + { + this.cachedRectsF = new RectangleF[cachedRects.Length]; + + for (int i = 0; i < this.cachedRectsF.Length; ++i) + { + this.cachedRectsF[i] = (RectangleF)this.cachedRects[i]; + } + } + + return this.cachedRectsF; + } + } + + public Rectangle[] GetRegionScansInt() + { + return (Rectangle[])GetRegionScansReadOnlyInt().Clone(); + } + + public Rectangle[] GetRegionScansReadOnlyInt() + { + lock (this.SyncRoot) + { + if (this.changed) + { + UpdateCachedRegionScans(); + } + + return this.cachedRects; + } + } + + private unsafe void UpdateCachedRegionScans() + { + // Assumes we are in a lock(SyncRoot){} block + SystemLayer.PdnGraphics.GetRegionScans(this.gdiRegion, out cachedRects, out cachedArea); + this.cachedRectsF = null; // only update this when specifically asked for it + } + + public void Intersect(GraphicsPath path) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Intersect(path); + } + } + + public void Intersect(Rectangle rect) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Intersect(rect); + } + } + + public void Intersect(RectangleF rectF) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Intersect(rectF); + } + } + + public void Intersect(Region region) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Intersect(region); + } + } + + public void Intersect(PdnRegion region2) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Intersect(region2.gdiRegion); + } + } + + public bool IsEmpty(Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsEmpty(g); + } + } + + public bool IsEmpty() + { + return GetArea() == 0; + } + + public bool IsInfinite(Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsInfinite(g); + } + } + + public bool IsVisible(Point point) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(point); + } + } + + public bool IsVisible(PointF pointF) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(pointF); + } + } + + public bool IsVisible(Rectangle rect) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(rect); + } + } + + public bool IsVisible(RectangleF rectF) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(rectF); + } + } + + public bool IsVisible(Point point, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(point, g); + } + } + + public bool IsVisible(PointF pointF, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(pointF, g); + } + } + + public bool IsVisible(Rectangle rect, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(rect, g); + } + } + + public bool IsVisible(RectangleF rectF, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(rectF, g); + } + } + + public bool IsVisible(float x, float y) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y); + } + } + + public bool IsVisible(int x, int y, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y, g); + } + } + + public bool IsVisible(float x, float y, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y, g); + } + } + + public bool IsVisible(int x, int y, int width, int height) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y, width, height); + } + } + + public bool IsVisible(float x, float y, float width, float height) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y, width, height); + } + } + + public bool IsVisible(int x, int y, int width, int height, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y, width, height, g); + } + } + + public bool IsVisible(float x, float y, float width, float height, Graphics g) + { + lock (SyncRoot) + { + return gdiRegion.IsVisible(x, y, width, height, g); + } + } + + public void MakeEmpty() + { + lock (SyncRoot) + { + Changed(); + gdiRegion.MakeEmpty(); + } + } + + public void MakeInfinite() + { + lock (SyncRoot) + { + Changed(); + gdiRegion.MakeInfinite(); + } + } + + public void Transform(Matrix matrix) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Transform(matrix); + } + } + + public void Union(GraphicsPath path) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Union(path); + } + } + + public void Union(Rectangle rect) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Union(rect); + } + } + + public void Union(RectangleF rectF) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Union(rectF); + } + } + + public void Union(RectangleF[] rectsF) + { + lock (SyncRoot) + { + Changed(); + + using (PdnRegion tempRegion = Utility.RectanglesToRegion(rectsF)) + { + this.Union(tempRegion); + } + } + } + + public void Union(Region region) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Union(region); + } + } + + public void Union(PdnRegion region2) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Union(region2.gdiRegion); + } + } + + public void Xor(Rectangle rect) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Xor(rect); + } + } + + public void Xor(RectangleF rectF) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Xor(rectF); + } + } + + public void Xor(Region region) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Xor(region); + } + } + + public void Xor(PdnRegion region2) + { + lock (SyncRoot) + { + Changed(); + gdiRegion.Xor(region2.gdiRegion); + } + } + } +} diff --git a/src/Core/PersistedObjectLocker.cs b/src/Core/PersistedObjectLocker.cs new file mode 100644 index 0000000..cd0e4d8 --- /dev/null +++ b/src/Core/PersistedObjectLocker.cs @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PaintDotNet +{ + /// + /// Provides a Guid->WeakReference mapping for PersistedObject instances. + /// This is used by the Move Selected Pixels tool so that it can have many + /// instances of its history and context data that refer to the same + /// MaskedSurface, but only have it serializing and deserializing exactly + /// once (to and from the same file). + /// + public static class PersistedObjectLocker + { + private static Dictionary guidToPO = new Dictionary(); + + public static Guid Add(PersistedObject po) + { + Guid retGuid = Guid.NewGuid(); + WeakReference wr = new WeakReference(po); + guidToPO.Add(retGuid, wr); + return retGuid; + } + + public static PersistedObject Get(Guid guid) + { + WeakReference wr; + guidToPO.TryGetValue(guid, out wr); + PersistedObject po; + + if (wr == null) + { + po = null; + } + else + { + object weakObj = wr.Target; + + if (weakObj == null) + { + po = null; + guidToPO.Remove(guid); + } + else + { + po = (PersistedObject)weakObj; + } + } + + return po; + } + + public static void Remove(Guid guid) + { + guidToPO.Remove(guid); + } + } +} diff --git a/src/Core/PersistedObject`1.cs b/src/Core/PersistedObject`1.cs new file mode 100644 index 0000000..fad5ad8 --- /dev/null +++ b/src/Core/PersistedObject`1.cs @@ -0,0 +1,312 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class PersistedObject + : IDisposable + { + private static ArrayList fileNames = ArrayList.Synchronized(new ArrayList()); + + public static string[] FileNames + { + get + { + return (string[])fileNames.ToArray(typeof(string)); + } + } + + // NOTE: We use a BSTR to hold the filename because we still need to be able to + // delete the file in our finalizer. However, the rules of finalizers say + // that you may not reference another object. Hence, we can not use a + // normal .NET System.String. + private IntPtr bstrTempFileName = IntPtr.Zero; + + private string tempFileName; + private WeakReference objectRef; + private bool disposed = false; + private ManualResetEvent theObjectSaved = new ManualResetEvent(false); + + private void WaitForObjectSaved() + { + ManualResetEvent theEvent = this.theObjectSaved; + + if (theEvent != null) + { + theEvent.WaitOne(); + } + } + + private void WaitForObjectSaved(int timeoutMs) + { + ManualResetEvent theEvent = this.theObjectSaved; + + if (theEvent != null) + { + theEvent.WaitOne(timeoutMs, false); + } + } + + /// + /// Gets the data stored in this instance of PersistedObject. + /// + /// + /// If the object has already been finalized and freed from memory, then this + /// property will deserialize the object from disk before returning a new + /// reference to it. + /// + public T Object + { + get + { + if (disposed) + { + throw new ObjectDisposedException("PersistedObject"); + } + + T o; + + if (objectRef == null) + { + o = default(T); + } + else + { + o = (T)objectRef.Target; + } + + if (o == null) + { + string strTempFileName = Marshal.PtrToStringBSTR(this.bstrTempFileName); + FileStream stream = new FileStream(strTempFileName, FileMode.Open, FileAccess.Read, FileShare.Read); + BinaryFormatter formatter = new BinaryFormatter(); + DeferredFormatter deferred = new DeferredFormatter(); + StreamingContext context = new StreamingContext(formatter.Context.State, deferred); + formatter.Context = context; + T localObject = (T)formatter.Deserialize(stream); + deferred.FinishDeserialization(stream); + this.objectRef = new WeakReference(localObject); + stream.Close(); + + return localObject; + } + else + { + return o; + } + } + } + + /// + /// Gets the data stored in this instance of PersistedObject. + /// + /// + /// If the object has already been finalized and freed from memory, then + /// this property will return null. + /// + public T WeakObject + { + get + { + if (disposed) + { + throw new ObjectDisposedException("PersistedObject"); + } + + if (objectRef == null) + { + return default(T); + } + else + { + return (T)objectRef.Target; + } + } + } + + /// + /// Ensures that the object held by this instance of PersistedObject is flushed to disk + /// and freed from memory. + /// + public void Flush() + { + WaitForObjectSaved(); + + // At this point we can now assume the object has been serialized to disk. + object obj = this.WeakObject; + IDisposable disposable = obj as IDisposable; + + if (disposable != null) + { + disposable.Dispose(); + disposable = null; + } + + this.objectRef = null; + } + + /// + /// Creates a new instance of the PersistedObject class. + /// + /// + /// The object to persist. It must be serializable. + /// + /// + /// Whether to serialize to disk in the background. If you specify true, then you must make + /// sure not to mutate or dispose theObject. + /// + /// Deferred serialization via IDeferredSerializable and DeferredFormatter are supported, + /// and the compression level will be set to none (zero) if background is false. The + /// compression level will be one if background is true. + /// + public PersistedObject(T theObject, bool background) + { + this.objectRef = new WeakReference(theObject); + this.tempFileName = FileSystem.GetTempFileName(); + fileNames.Add(tempFileName); + this.bstrTempFileName = Marshal.StringToBSTR(tempFileName); + + if (background) + { + Thread thread = new Thread(new ParameterizedThreadStart(PersistToDiskThread)); + thread.Start(theObject); + } + else + { + PersistToDisk(theObject); + } + } + + private void PersistToDiskThread(object theObject) + { + using (new ThreadBackground(ThreadBackgroundFlags.Cpu)) + { + PersistToDisk(theObject); + } + } + + private void PersistToDisk(object theObject) + { + try + { + FileStream stream = new FileStream(this.tempFileName, FileMode.Create, FileAccess.Write, FileShare.Read); + BinaryFormatter formatter = new BinaryFormatter(); + DeferredFormatter deferred = new DeferredFormatter(false, null); + StreamingContext context = new StreamingContext(formatter.Context.State, deferred); + + formatter.Context = context; + formatter.Serialize(stream, theObject); + deferred.FinishSerialization(stream); + stream.Flush(); + stream.Close(); + } + + finally + { + this.theObjectSaved.Set(); + this.theObjectSaved = null; + } + } + + ~PersistedObject() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!this.disposed) + { + this.disposed = true; + + if (disposing) + { + WaitForObjectSaved(1000); + } + + string strTempFileName = Marshal.PtrToStringBSTR(this.bstrTempFileName); + + FileInfo fi = new FileInfo(strTempFileName); + + if (fi.Exists) + { + bool result = FileSystem.TryDeleteFile(fi.FullName); + + try + { + fileNames.Remove(strTempFileName); + } + + catch + { + } + } + + Marshal.FreeBSTR(this.bstrTempFileName); + this.bstrTempFileName = IntPtr.Zero; + + if (disposing) + { + ManualResetEvent theEvent = this.theObjectSaved; + this.theObjectSaved = null; + + if (theEvent != null) + { + theEvent.Close(); + theEvent = null; + } + } + } + } + + static PersistedObject() + { + Application.ApplicationExit += new EventHandler(Application_ApplicationExit); + } + + private static void Application_ApplicationExit(object sender, EventArgs e) + { + // Clean-up leftover persisted objects + string[] fileNames = PersistedObject.FileNames; + + if (fileNames.Length != 0) + { + foreach (string fileName in fileNames) + { + FileInfo fi = new FileInfo(fileName); + + if (fi.Exists) + { + bool result = FileSystem.TryDeleteFile(fi.FullName); + } + } + } + } + } +} diff --git a/src/Core/PixelOp.cs b/src/Core/PixelOp.cs new file mode 100644 index 0000000..2c534bd --- /dev/null +++ b/src/Core/PixelOp.cs @@ -0,0 +1,127 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Threading; + +namespace PaintDotNet +{ + [Serializable] + public unsafe abstract class PixelOp + : IPixelOp + { + /// + /// Computes alpha for r OVER l operation. + /// + public static byte ComputeAlpha(byte la, byte ra) + { + return (byte)(((la * (256 - (ra + (ra >> 7)))) >> 8) + ra); + } + + public void Apply(Surface dst, Surface src, Rectangle[] rois, int startIndex, int length) + { + for (int i = startIndex; i < startIndex + length; ++i) + { + ApplyBase(dst, rois[i].Location, src, rois[i].Location, rois[i].Size); + } + } + + public void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, Size roiSize) + { + ApplyBase(dst, dstOffset, src, srcOffset, roiSize); + } + + /// + /// Provides a default implementation for performing dst = F(dst, src) or F(src) over some rectangle + /// of interest. May be slightly faster than calling the other multi-parameter Apply method, as less + /// variables are used in the implementation, thus inducing less register pressure. + /// + /// The Surface to write pixels to, and from which pixels are read and used as the lhs parameter for calling the method ColorBgra Apply(ColorBgra, ColorBgra). + /// The pixel offset that defines the upper-left of the rectangle-of-interest for the dst Surface. + /// The Surface to read pixels from for the rhs parameter given to the method ColorBgra Apply(ColorBgra, ColorBgra)b>. + /// The pixel offset that defines the upper-left of the rectangle-of-interest for the src Surface. + /// The size of the rectangles-of-interest for all Surfaces. + public void ApplyBase(Surface dst, Point dstOffset, Surface src, Point srcOffset, Size roiSize) + { + // Create bounding rectangles for each Surface + Rectangle dstRect = new Rectangle(dstOffset, roiSize); + + if (dstRect.Width == 0 || dstRect.Height == 0) + { + return; + } + + Rectangle srcRect = new Rectangle(srcOffset, roiSize); + + if (srcRect.Width == 0 || srcRect.Height == 0) + { + return; + } + + // Clip those rectangles to those Surface's bounding rectangles + Rectangle dstClip = Rectangle.Intersect(dstRect, dst.Bounds); + Rectangle srcClip = Rectangle.Intersect(srcRect, src.Bounds); + + // If any of those Rectangles actually got clipped, then throw an exception + if (dstRect != dstClip) + { + throw new ArgumentOutOfRangeException + ( + "roiSize", + "Destination roi out of bounds" + + ", dst.Size=" + dst.Size.ToString() + + ", dst.Bounds=" + dst.Bounds.ToString() + + ", dstOffset=" + dstOffset.ToString() + + ", src.Size=" + src.Size.ToString() + + ", srcOffset=" + srcOffset.ToString() + + ", roiSize=" + roiSize.ToString() + + ", dstRect=" + dstRect.ToString() + + ", dstClip=" + dstClip.ToString() + + ", srcRect=" + srcRect.ToString() + + ", srcClip=" + srcClip.ToString() + ); + } + + if (srcRect != srcClip) + { + throw new ArgumentOutOfRangeException("roiSize", "Source roi out of bounds"); + } + + // Cache the width and height properties + int width = roiSize.Width; + int height = roiSize.Height; + + // Do the work. + unsafe + { + for (int row = 0; row < roiSize.Height; ++row) + { + ColorBgra *dstPtr = dst.GetPointAddress(dstOffset.X, dstOffset.Y + row); + ColorBgra *srcPtr = src.GetPointAddress(srcOffset.X, srcOffset.Y + row); + Apply(dstPtr, srcPtr, width); + } + } + } + + public virtual void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int scanLength) + { + Apply(dst.GetPointAddress(dstOffset), src.GetPointAddress(srcOffset), scanLength); + } + + public virtual void Apply(ColorBgra *dst, ColorBgra *src, int length) + { + throw new System.NotImplementedException("Derived class must implement Apply(ColorBgra*,ColorBgra*,int)"); + } + + public PixelOp() + { + } + } +} diff --git a/src/Core/PlacedSurface.cs b/src/Core/PlacedSurface.cs new file mode 100644 index 0000000..6539764 --- /dev/null +++ b/src/Core/PlacedSurface.cs @@ -0,0 +1,220 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + +namespace PaintDotNet +{ + /// + /// Encapsulates a surface ("what") along with a pixel offset ("where") which + /// defines where the surface would be drawn on to another surface. + /// Instances of this object are immutable -- once you create it, you can not + /// change it. + /// + [Serializable] + public sealed class PlacedSurface + : ISurfaceDraw, + IDisposable, + ICloneable + { + Point where; + Surface what; + + public Point Where + { + get + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + return where; + } + } + + public Surface What + { + get + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + return what; + } + } + + public Size Size + { + get + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + return What.Size; + } + } + + public Rectangle Bounds + { + get + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + return new Rectangle(Where, What.Size); + } + } + + public void Draw(Surface dst) + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + dst.CopySurface(what, where); + } + + public void Draw(Surface dst, IPixelOp pixelOp) + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + Rectangle dstRect = Bounds; + Rectangle dstClip = Rectangle.Intersect(dstRect, dst.Bounds); + + if (dstClip.Width > 0 && dstClip.Height > 0) + { + int dtX = dstClip.X - where.X; + int dtY = dstClip.Y - where.Y; + + pixelOp.Apply(dst, dstClip.Location, what, new Point(dtX, dtY), dstClip.Size); + } + } + + public void Draw(Surface dst, int tX, int tY) + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + Point oldWhere = where; + + try + { + where.X += tX; + where.Y += tY; + Draw(dst); + } + + finally + { + where = oldWhere; + } + } + + public void Draw(Surface dst, int tX, int tY, IPixelOp pixelOp) + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + Point oldWhere = where; + + try + { + where.X += tX; + where.Y += tY; + Draw(dst, pixelOp); + } + + finally + { + where = oldWhere; + } + } + + public PlacedSurface (Surface source, Rectangle roi) + { + where = roi.Location; + + Surface window = source.CreateWindow(roi); + what = new Surface(window.Size); + what.CopySurface(window); + window.Dispose(); + } + + private PlacedSurface (PlacedSurface ps) + { + where = ps.Where; + what = ps.What.Clone(); + } + + private PlacedSurface() + { + } + + ~PlacedSurface() + { + Dispose(false); + } + + #region IDisposable Members + private bool disposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + + if (disposing) + { + what.Dispose(); + what = null; + } + } + } + #endregion + + #region ICloneable Members + + public object Clone() + { + if (disposed) + { + throw new ObjectDisposedException("PlacedSurface"); + } + + return new PlacedSurface(this); + } + + #endregion + } +} diff --git a/src/Core/PluginSupportInfo.cs b/src/Core/PluginSupportInfo.cs new file mode 100644 index 0000000..74cc6f7 --- /dev/null +++ b/src/Core/PluginSupportInfo.cs @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace PaintDotNet +{ + public sealed class PluginSupportInfo + { + private PluginSupportInfo() + { + } + + private static IPluginSupportInfo GetPluginSupportInfo(ICustomAttributeProvider icap) + { + object[] attributes; + + try + { + attributes = icap.GetCustomAttributes(typeof(PluginSupportInfoAttribute), false); + } + + catch (Exception) + { + attributes = new object[0]; + } + + if (attributes.Length == 1) + { + PluginSupportInfoAttribute psiAttr = (PluginSupportInfoAttribute)attributes[0]; + return (IPluginSupportInfo)psiAttr; + } + else + { + return null; + } + } + + public static IPluginSupportInfo GetPluginSupportInfo(Assembly assembly) + { + return GetPluginSupportInfo((ICustomAttributeProvider)assembly); + } + + public static IPluginSupportInfo GetPluginSupportInfo(Type type) + { + return GetPluginSupportInfo((ICustomAttributeProvider)type) ?? GetPluginSupportInfo(type.Assembly); + } + + public static IPluginSupportInfo GetPluginSupportInfo(object theObject) + { + if (theObject is IPluginSupportInfo) + { + return (IPluginSupportInfo)theObject; + } + else + { + return GetPluginSupportInfo(theObject.GetType()); + } + } + } +} diff --git a/src/Core/PluginSupportInfoAttribute.cs b/src/Core/PluginSupportInfoAttribute.cs new file mode 100644 index 0000000..51a34c6 --- /dev/null +++ b/src/Core/PluginSupportInfoAttribute.cs @@ -0,0 +1,96 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + [AttributeUsage( + AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface, + AllowMultiple = false, + Inherited = false)] + public class PluginSupportInfoAttribute + : Attribute, + IPluginSupportInfo + { + private string displayName; + private string author; + private string copyright; + private Version version = new Version(); + private Uri websiteUri; + + public string DisplayName + { + get + { + return this.displayName; + } + + set + { + this.displayName = value; + } + } + + public string Author + { + get + { + return this.author; + } + } + + public string Copyright + { + get + { + return this.copyright; + } + } + + public Version Version + { + get + { + return this.version; + } + } + + public Uri WebsiteUri + { + get + { + return this.websiteUri; + } + } + + public PluginSupportInfoAttribute() + { + } + + public PluginSupportInfoAttribute(Type pluginSupportInfoProvider) + { + IPluginSupportInfo ipsi = (IPluginSupportInfo)Activator.CreateInstance(pluginSupportInfoProvider); + this.displayName = ipsi.DisplayName; + this.author = ipsi.Author; + this.copyright = ipsi.Copyright; + this.version = ipsi.Version; + this.websiteUri = ipsi.WebsiteUri; + } + + public PluginSupportInfoAttribute(string displayName, string author, string copyright, Version version, Uri websiteUri) + { + this.displayName = displayName; + this.author = author; + this.copyright = copyright; + this.version = version; + this.websiteUri = websiteUri; + } + } +} diff --git a/src/Core/ProgressEventArgs.cs b/src/Core/ProgressEventArgs.cs new file mode 100644 index 0000000..7342851 --- /dev/null +++ b/src/Core/ProgressEventArgs.cs @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public class ProgressEventArgs + : System.EventArgs + { + private double percent; + public double Percent + { + get + { + return percent; + } + } + + public ProgressEventArgs(double percent) + { + this.percent = percent; + } + } +} diff --git a/src/Core/ProgressEventHandler.cs b/src/Core/ProgressEventHandler.cs new file mode 100644 index 0000000..729e44a --- /dev/null +++ b/src/Core/ProgressEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate void ProgressEventHandler(object sender, ProgressEventArgs e); +} diff --git a/src/Core/PropertyEventArgs.cs b/src/Core/PropertyEventArgs.cs new file mode 100644 index 0000000..8fe63bf --- /dev/null +++ b/src/Core/PropertyEventArgs.cs @@ -0,0 +1,31 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public class PropertyEventArgs + : System.EventArgs + { + private string propertyName; + public string PropertyName + { + get + { + return propertyName; + } + } + + public PropertyEventArgs(string propertyName) + { + this.propertyName = propertyName; + } + } +} diff --git a/src/Core/PropertyEventHandler.cs b/src/Core/PropertyEventHandler.cs new file mode 100644 index 0000000..867be03 --- /dev/null +++ b/src/Core/PropertyEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate void PropertyEventHandler(object sender, PropertyEventArgs e); +} diff --git a/src/Core/RenderArgs.cs b/src/Core/RenderArgs.cs new file mode 100644 index 0000000..72e3878 --- /dev/null +++ b/src/Core/RenderArgs.cs @@ -0,0 +1,232 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Encapsulates the arguments passed to a Render function. + /// This way we can do on-demand and once-only creation of Bitmap and Graphics + /// objects from a given Surface object. + /// + /// + /// Use of the Bitmap and Graphics objects is not thread safe because of how GDI+ works. + /// You must wrap use of these objects with a critical section, like so: + /// object lockObject = new object(); + /// lock (lockObject) + /// { + /// Graphics g = ra.Graphics; + /// g.DrawRectangle(...); + /// // etc. + /// } + /// + public sealed class RenderArgs + : IDisposable + { + private Surface surface; + private Bitmap bitmap; + private Graphics graphics; + private bool disposed = false; + + /// + /// Gets the Surface that has been associated with this instance of RenderArgs. + /// + public Surface Surface + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + return this.surface; + } + } + + /// + /// Gets a Bitmap reference that aliases the Surface. + /// + public Bitmap Bitmap + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + if (this.bitmap == null) + { + this.bitmap = surface.CreateAliasedBitmap(); + } + + return this.bitmap; + } + } + + /// + /// Retrieves a Graphics instance that can be used to draw on to the Surface. + /// + /// + /// Use of this object is not thread-safe. You must wrap retrieval and consumption of this + /// property with a critical section. + /// + public Graphics Graphics + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + if (this.graphics == null) + { + this.graphics = Graphics.FromImage(Bitmap); + } + + return this.graphics; + } + } + + /// + /// Gets the size of the associated Surface object. + /// + /// + /// This is a convenience method equivalent to using RenderArgs.Surface.Bounds. + /// + public Rectangle Bounds + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + return this.Surface.Bounds; + } + } + + /// + /// Gets the size of the associated Surface object. + /// + /// + /// This is a convenient method equivalent to using RenderArgs.Surface.Size. + /// + public Size Size + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + return this.Surface.Size; + } + } + + /// + /// Gets the width of the associated Surface object. + /// + /// + /// This is a convenience method equivalent to using RenderArgs.Surface.Width. + /// + public int Width + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + return this.surface.Width; + } + } + + /// + /// Gets the height of the associated Surface object. + /// + /// + /// This is a convenience method equivalent to using RenderArgs.Surface.Height. + /// + public int Height + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("RenderArgs"); + } + + return this.surface.Height; + } + } + + /// + /// Creates an instance of the RenderArgs class. + /// + /// + /// The Surface to associate with this instance. This instance of RenderArgs does not + /// take ownership of this Surface. + /// + public RenderArgs(Surface surface) + { + this.surface = surface; + this.bitmap = null; + this.graphics = null; + } + + ~RenderArgs() + { + Dispose(false); + } + + /// + /// Disposes of the contained Bitmap and Graphics instances, if necessary. + /// + /// + /// Note that since this class does not take ownership of the Surface, it + /// is not disposed. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!this.disposed) + { + this.disposed = true; + + if (disposing) + { + if (this.graphics != null) + { + this.graphics.Dispose(); + this.graphics = null; + } + + if (this.bitmap != null) + { + this.bitmap.Dispose(); + this.bitmap = null; + } + } + } + } + } +} diff --git a/src/Core/RenderedTileEventArgs.cs b/src/Core/RenderedTileEventArgs.cs new file mode 100644 index 0000000..5d5e62e --- /dev/null +++ b/src/Core/RenderedTileEventArgs.cs @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class RenderedTileEventArgs + : EventArgs + { + private PdnRegion renderedRegion; + public PdnRegion RenderedRegion + { + get + { + return renderedRegion; + } + } + + private int tileNumber; + public int TileNumber + { + get + { + return tileNumber; + } + } + + private int tileCount; + public int TileCount + { + get + { + return tileCount; + } + } + + public RenderedTileEventArgs(PdnRegion renderedRegion, int tileCount, int tileNumber) + { + this.renderedRegion = renderedRegion; + this.tileCount = tileCount; + this.tileNumber = tileNumber; + } + } +} diff --git a/src/Core/RenderedTileEventHandler.cs b/src/Core/RenderedTileEventHandler.cs new file mode 100644 index 0000000..dd87628 --- /dev/null +++ b/src/Core/RenderedTileEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public delegate void RenderedTileEventHandler (object sender, RenderedTileEventArgs e); +} diff --git a/src/Core/ResamplingAlgorithm.cs b/src/Core/ResamplingAlgorithm.cs new file mode 100644 index 0000000..e333b4c --- /dev/null +++ b/src/Core/ResamplingAlgorithm.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum ResamplingAlgorithm + { + NearestNeighbor, + Bilinear, + Bicubic, + SuperSampling + } +} diff --git a/src/Core/RgbColor.cs b/src/Core/RgbColor.cs new file mode 100644 index 0000000..b0c1d74 --- /dev/null +++ b/src/Core/RgbColor.cs @@ -0,0 +1,135 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Adapted from: + /// "A Primer on Building a Color Picker User Control with GDI+ in Visual Basic .NET or C#" + /// http://www.msdnaa.net/Resources/display.aspx?ResID=2460 + /// + /// This class is only used by the ColorsForm and ColorWheel. Nothing else in this program + /// should be using it! + /// + [Serializable] + public struct RgbColor + { + // All values are between 0 and 255. + public int Red; + public int Green; + public int Blue; + + public RgbColor(int R, int G, int B) + { +#if DEBUG + if (R < 0 || R > 255) + { + throw new ArgumentOutOfRangeException("R", R, "R must corrospond to a byte value"); + } + if (G < 0 || G > 255) + { + throw new ArgumentOutOfRangeException("G", G, "G must corrospond to a byte value"); + } + if (B < 0 || B > 255) + { + throw new ArgumentOutOfRangeException("B", B, "B must corrospond to a byte value"); + } +#endif + Red = R; + Green = G; + Blue = B; + } + + public static RgbColor FromHsv(HsvColor hsv) + { + return hsv.ToRgb(); + } + + public Color ToColor() + { + return Color.FromArgb(Red, Green, Blue); + } + + public HsvColor ToHsv() + { + // In this function, R, G, and B values must be scaled + // to be between 0 and 1. + // HsvColor.Hue will be a value between 0 and 360, and + // HsvColor.Saturation and value are between 0 and 1. + + double min; + double max; + double delta; + + double r = (double) Red / 255; + double g = (double) Green / 255; + double b = (double) Blue / 255; + + double h; + double s; + double v; + + min = Math.Min(Math.Min(r, g), b); + max = Math.Max(Math.Max(r, g), b); + v = max; + delta = max - min; + + if (max == 0 || delta == 0) + { + // R, G, and B must be 0, or all the same. + // In this case, S is 0, and H is undefined. + // Using H = 0 is as good as any... + s = 0; + h = 0; + } + else + { + s = delta / max; + if (r == max) + { + // Between Yellow and Magenta + h = (g - b) / delta; + } + else if (g == max) + { + // Between Cyan and Yellow + h = 2 + (b - r) / delta; + } + else + { + // Between Magenta and Cyan + h = 4 + (r - g) / delta; + } + + } + // Scale h to be between 0 and 360. + // This may require adding 360, if the value + // is negative. + h *= 60; + + if (h < 0) + { + h += 360; + } + + // Scale to the requirements of this + // application. All values are between 0 and 255. + return new HsvColor((int)h, (int)(s * 100), (int)(v * 100)); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2})", Red, Green, Blue); + } + } + +} diff --git a/src/Core/Ruler.cs b/src/Core/Ruler.cs new file mode 100644 index 0000000..a89a710 --- /dev/null +++ b/src/Core/Ruler.cs @@ -0,0 +1,558 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class Ruler + : UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + private MeasurementUnit measurementUnit = MeasurementUnit.Inch; + public MeasurementUnit MeasurementUnit + { + get + { + return measurementUnit; + } + + set + { + if (value != measurementUnit) + { + measurementUnit = value; + Invalidate(); + } + } + } + + private Orientation orientation = Orientation.Horizontal; + + [DefaultValue(Orientation.Horizontal)] + public Orientation Orientation + { + get + { + return orientation; + } + + set + { + if (orientation != value) + { + orientation = value; + Invalidate(); + } + } + } + + private double dpu = 96; + + [DefaultValue(96.0)] + public double Dpu + { + get + { + return dpu; + } + + set + { + if (value != dpu) + { + dpu = value; + Invalidate(); + } + } + } + + private ScaleFactor scaleFactor = ScaleFactor.OneToOne; + + [Browsable(false)] + public ScaleFactor ScaleFactor + { + get + { + return scaleFactor; + } + + set + { + if (scaleFactor != value) + { + scaleFactor = value; + Invalidate(); + } + } + } + + private float offset = 0; + + [DefaultValue(0)] + public float Offset + { + get + { + return offset; + } + + set + { + if (offset != value) + { + offset = value; + Invalidate(); + } + } + } + + private float rulerValue = 0.0f; + + [DefaultValue(0)] + public float Value + { + get + { + return rulerValue; + } + + set + { + if (this.rulerValue != value) + { + float oldStart = this.scaleFactor.ScaleScalar(this.rulerValue - offset) - 1; + float oldEnd = this.scaleFactor.ScaleScalar(this.rulerValue + 1 - offset) + 1; + RectangleF oldRect; + + if (this.orientation == Orientation.Horizontal) + { + oldRect = new RectangleF(oldStart, this.ClientRectangle.Top, oldEnd - oldStart, this.ClientRectangle.Height); + } + else // if (this.orientation == Orientation.Vertical) + { + oldRect = new RectangleF(this.ClientRectangle.Left, oldStart, this.ClientRectangle.Width, oldEnd - oldStart); + } + + float newStart = this.scaleFactor.ScaleScalar(value - offset); + float newEnd = this.scaleFactor.ScaleScalar(value + 1 - offset); + RectangleF newRect; + + if (this.orientation == Orientation.Horizontal) + { + newRect = new RectangleF(newStart, this.ClientRectangle.Top, newEnd - newStart, this.ClientRectangle.Height); + } + else // if (this.orientation == Orientation.Vertical) + { + newRect = new RectangleF(this.ClientRectangle.Left, newStart, this.ClientRectangle.Width, newEnd - newStart); + } + + this.rulerValue = value; + + Invalidate(Utility.RoundRectangle(oldRect)); + Invalidate(Utility.RoundRectangle(newRect)); + } + } + } + + private float highlightStart = 0.0f; + public float HighlightStart + { + get + { + return this.highlightStart; + } + + set + { + if (this.highlightStart != value) + { + this.highlightStart = value; + Invalidate(); + } + } + } + + private float highlightLength = 0.0f; + public float HighlightLength + { + get + { + return this.highlightLength; + } + + set + { + if (this.highlightLength != value) + { + this.highlightLength = value; + Invalidate(); + } + } + } + + private bool highlightEnabled = false; + public bool HighlightEnabled + { + get + { + return this.highlightEnabled; + } + + set + { + if (this.highlightEnabled != value) + { + this.highlightEnabled = value; + Invalidate(); + } + } + } + + public Ruler() + { + SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true); + + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + } + + protected override void OnPaint(PaintEventArgs e) + { + float valueStart = this.scaleFactor.ScaleScalar(this.rulerValue - offset); + float valueEnd = this.scaleFactor.ScaleScalar(this.rulerValue + 1.0f - offset); + float highlightStartPx = this.scaleFactor.ScaleScalar(this.highlightStart - offset); + float highlightEndPx = this.scaleFactor.ScaleScalar(this.highlightStart + this.highlightLength - offset); + + RectangleF highlightRect; + RectangleF valueRect; + + if (this.orientation == Orientation.Horizontal) + { + valueRect = new RectangleF(valueStart, this.ClientRectangle.Top, valueEnd - valueStart, this.ClientRectangle.Height); + highlightRect = new RectangleF(highlightStartPx, this.ClientRectangle.Top, highlightEndPx - highlightStartPx, this.ClientRectangle.Height); + } + else // if (this.orientation == Orientation.Vertical) + { + valueRect = new RectangleF(this.ClientRectangle.Left, valueStart, this.ClientRectangle.Width, valueEnd - valueStart); + highlightRect = new RectangleF(this.ClientRectangle.Left, highlightStartPx, this.ClientRectangle.Width, highlightEndPx - highlightStartPx); + } + + if (!this.highlightEnabled) + { + highlightRect = RectangleF.Empty; + } + + if (this.orientation == Orientation.Horizontal) + { + e.Graphics.DrawLine( + SystemPens.WindowText, + UI.ScaleWidth(15), + ClientRectangle.Top, + UI.ScaleWidth(15), + ClientRectangle.Bottom); + + string abbStringName = "MeasurementUnit." + this.MeasurementUnit.ToString() + ".Abbreviation"; + string abbString = PdnResources.GetString(abbStringName); + e.Graphics.DrawString(abbString, Font, SystemBrushes.WindowText, UI.ScaleWidth(-2), 0); + } + + Region clipRegion = new Region(highlightRect); + clipRegion.Xor(valueRect); + + if (this.orientation == Orientation.Horizontal) + { + clipRegion.Exclude(new Rectangle(0, 0, UI.ScaleWidth(16), ClientRectangle.Height)); + } + + e.Graphics.SetClip(clipRegion, CombineMode.Replace); + DrawRuler(e, true); + + clipRegion.Xor(this.ClientRectangle); + + if (this.orientation == Orientation.Horizontal) + { + clipRegion.Exclude(new Rectangle(0, 0, UI.ScaleWidth(16), ClientRectangle.Height - 1)); + } + + e.Graphics.SetClip(clipRegion, CombineMode.Replace); + DrawRuler(e, false); + clipRegion.Dispose(); + } + + private static readonly float[] majorDivisors = + new float[] + { + 2.0f, + 2.5f, + 2.0f + }; + + private int[] GetSubdivs(MeasurementUnit unit) + { + switch (unit) + { + case MeasurementUnit.Centimeter: + { + return new int[] { 2, 5 }; + } + + case MeasurementUnit.Inch: + { + return new int[] { 2 }; + } + + default: + { + return null; + } + } + } + + private void SubdivideX( + Graphics g, + Pen pen, + float x, + float delta, + int index, + float y, + float height, + int[] subdivs) + { + g.DrawLine(pen, x, y, x, y + height); + + if (index > 10) + { + return; + } + + float div; + + if (subdivs != null && index >= 0) + { + div = subdivs[index % subdivs.Length]; + } + else if (index < 0) + { + div = majorDivisors[(-index - 1) % majorDivisors.Length]; + } + else + { + return; + } + + for (int i = 0; i < div; i++) + { + if ((delta / div) > 3.5) + { + SubdivideX(g, pen, x + delta * i / div, delta / div, index + 1, y, height / div + 0.5f, subdivs); + } + } + } + + private void SubdivideY( + Graphics g, + Pen pen, + float y, + float delta, + int index, + float x, + float width, + int[] subdivs) + { + g.DrawLine(pen, x, y, x + width, y); + + if (index > 10) + { + return; + } + + float div; + + if (subdivs != null && index >= 0) + { + div = subdivs[index % subdivs.Length]; + } + else if (index < 0) + { + div = majorDivisors[(-index - 1) % majorDivisors.Length]; + } + else + { + return; + } + + for (int i = 0; i < div; i++) + { + if ((delta / div) > 3.5) + { + SubdivideY(g, pen, y + delta * i / div, delta / div, index + 1, x, width / div + 0.5f, subdivs); + } + } + } + + private void DrawRuler(PaintEventArgs e, bool highlighted) + { + Pen pen; + Brush cursorBrush; + Brush textBrush; + StringFormat textFormat = new StringFormat(); + int maxPixel; + Color cursorColor; + + if (highlighted) + { + e.Graphics.Clear(SystemColors.Highlight); + pen = SystemPens.HighlightText; + textBrush = SystemBrushes.HighlightText; + cursorColor = SystemColors.Window; + } + else + { + e.Graphics.Clear(SystemColors.Window); + pen = SystemPens.WindowText; + textBrush = SystemBrushes.WindowText; + cursorColor = SystemColors.Highlight; + } + + cursorColor = Color.FromArgb(128, cursorColor); + cursorBrush = new SolidBrush(cursorColor); + + if (orientation == Orientation.Horizontal) + { + maxPixel = ScaleFactor.UnscaleScalar(ClientRectangle.Width); + textFormat.Alignment = StringAlignment.Near; + textFormat.LineAlignment = StringAlignment.Far; + } + else // if (orientation == Orientation.Vertical) + { + maxPixel = ScaleFactor.UnscaleScalar(ClientRectangle.Height); + textFormat.Alignment = StringAlignment.Near; + textFormat.LineAlignment = StringAlignment.Near; + textFormat.FormatFlags |= StringFormatFlags.DirectionVertical; + } + + float majorSkip = 1; + int majorSkipPower = 0; + float majorDivisionLength = (float)dpu; + float majorDivisionPixels = (float)ScaleFactor.ScaleScalar(majorDivisionLength); + int[] subdivs = GetSubdivs(measurementUnit); + float offsetPixels = ScaleFactor.ScaleScalar((float)offset); + int startMajor = (int)(offset / majorDivisionLength) - 1; + int endMajor = (int)((offset + maxPixel) / majorDivisionLength) + 1; + + if (orientation == Orientation.Horizontal) + { + // draw Value + if (!highlighted) + { + PointF pt = scaleFactor.ScalePointJustX(new PointF(ClientRectangle.Left + Value - Offset, ClientRectangle.Top)); + SizeF size = new SizeF(Math.Max(1, scaleFactor.ScaleScalar(1.0f)), ClientRectangle.Height); + + pt.X -= 0.5f; + + CompositingMode oldCM = e.Graphics.CompositingMode; + e.Graphics.CompositingMode = CompositingMode.SourceOver; + e.Graphics.FillRectangle(cursorBrush, new RectangleF(pt, size)); + e.Graphics.CompositingMode = oldCM; + } + + // draw border + e.Graphics.DrawLine(SystemPens.WindowText, new Point(ClientRectangle.Left, ClientRectangle.Bottom - 1), + new Point(ClientRectangle.Right - 1, ClientRectangle.Bottom - 1)); + } + else if (orientation == Orientation.Vertical) + { + // draw Value + if (!highlighted) + { + PointF pt = scaleFactor.ScalePointJustY(new PointF(ClientRectangle.Left, ClientRectangle.Top + Value - Offset)); + SizeF size = new SizeF(ClientRectangle.Width, Math.Max(1, scaleFactor.ScaleScalar(1.0f))); + + pt.Y -= 0.5f; + + CompositingMode oldCM = e.Graphics.CompositingMode; + e.Graphics.CompositingMode = CompositingMode.SourceOver; + e.Graphics.FillRectangle(cursorBrush, new RectangleF(pt, size)); + e.Graphics.CompositingMode = oldCM; + } + + // draw border + e.Graphics.DrawLine(SystemPens.WindowText, new Point(ClientRectangle.Right - 1, ClientRectangle.Top), + new Point(ClientRectangle.Right - 1, ClientRectangle.Bottom - 1)); + } + + while (majorDivisionPixels * majorSkip < 60) + { + majorSkip *= majorDivisors[majorSkipPower % majorDivisors.Length]; + ++majorSkipPower; + } + + startMajor = (int)(majorSkip * Math.Floor(startMajor / (double)majorSkip)); + + for (int major = startMajor; major <= endMajor; major += (int)majorSkip) + { + float majorMarkPos = (major * majorDivisionPixels) - offsetPixels; + string majorText = (major).ToString(); + + if (orientation == Orientation.Horizontal) + { + SubdivideX(e.Graphics, pen, ClientRectangle.Left + majorMarkPos, majorDivisionPixels * majorSkip, -majorSkipPower, ClientRectangle.Top, ClientRectangle.Height, subdivs); + e.Graphics.DrawString(majorText, Font, textBrush, new PointF(ClientRectangle.Left + majorMarkPos, ClientRectangle.Bottom), textFormat); + } + else // if (orientation == Orientation.Vertical) + { + SubdivideY(e.Graphics, pen, ClientRectangle.Top + majorMarkPos, majorDivisionPixels * majorSkip, -majorSkipPower, ClientRectangle.Left, ClientRectangle.Width, subdivs); + e.Graphics.DrawString(majorText, Font, textBrush, new PointF(ClientRectangle.Left, ClientRectangle.Top + majorMarkPos), textFormat); + } + } + + textFormat.Dispose(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + #endregion + } +} diff --git a/src/Core/Ruler.resx b/src/Core/Ruler.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/ScaleFactor.cs b/src/Core/ScaleFactor.cs new file mode 100644 index 0000000..ed7e145 --- /dev/null +++ b/src/Core/ScaleFactor.cs @@ -0,0 +1,422 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Encapsulates functionality for zooming/scaling coordinates. + /// Includes methods for Size[F]'s, Point[F]'s, Rectangle[F]'s, + /// and various scalars + /// + public struct ScaleFactor + { + private int denominator; + private int numerator; + + public int Denominator + { + get + { + return denominator; + } + } + + public int Numerator + { + get + { + return numerator; + } + } + + public double Ratio + { + get + { + return (double)numerator / (double)denominator; + } + } + + public static readonly ScaleFactor OneToOne = new ScaleFactor(1, 1); + public static readonly ScaleFactor MinValue = new ScaleFactor(1, 100); + public static readonly ScaleFactor MaxValue = new ScaleFactor(32, 1); + + private void Clamp() + { + if (this < MinValue) + { + this = MinValue; + } + else if (this > MaxValue) + { + this = MaxValue; + } + } + + public static ScaleFactor UseIfValid(int numerator, int denominator, ScaleFactor lastResort) + { + if (numerator <= 0 || denominator <= 0) + { + return lastResort; + } + else + { + return new ScaleFactor(numerator, denominator); + } + } + + public static ScaleFactor Min(int n1, int d1, int n2, int d2, ScaleFactor lastResort) + { + ScaleFactor a = UseIfValid(n1, d1, lastResort); + ScaleFactor b = UseIfValid(n2, d2, lastResort); + return ScaleFactor.Min(a, b); + } + + public static ScaleFactor Max(int n1, int d1, int n2, int d2, ScaleFactor lastResort) + { + ScaleFactor a = UseIfValid(n1, d1, lastResort); + ScaleFactor b = UseIfValid(n2, d2, lastResort); + return ScaleFactor.Max(a, b); + } + + public static ScaleFactor Min(ScaleFactor lhs, ScaleFactor rhs) + { + if (lhs < rhs) + { + return lhs; + } + else + { + return rhs; + } + } + + public static ScaleFactor Max(ScaleFactor lhs, ScaleFactor rhs) + { + if (lhs > rhs) + { + return lhs; + } + else + { + return lhs; + } + } + + public static bool operator==(ScaleFactor lhs, ScaleFactor rhs) + { + return (lhs.numerator * rhs.denominator) == (rhs.numerator * lhs.denominator); + } + + public static bool operator!=(ScaleFactor lhs, ScaleFactor rhs) + { + return !(lhs == rhs); + } + + public static bool operator<(ScaleFactor lhs, ScaleFactor rhs) + { + return (lhs.numerator * rhs.denominator) < (rhs.numerator * lhs.denominator); + } + + public static bool operator<=(ScaleFactor lhs, ScaleFactor rhs) + { + return (lhs.numerator * rhs.denominator) <= (rhs.numerator * lhs.denominator); + } + + public static bool operator>(ScaleFactor lhs, ScaleFactor rhs) + { + return (lhs.numerator * rhs.denominator) > (rhs.numerator * lhs.denominator); + } + + public static bool operator>=(ScaleFactor lhs, ScaleFactor rhs) + { + return (lhs.numerator * rhs.denominator) >= (rhs.numerator * lhs.denominator); + } + + public override bool Equals(object obj) + { + if (obj is ScaleFactor) + { + ScaleFactor rhs = (ScaleFactor)obj; + return this == rhs; + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return numerator.GetHashCode() ^ denominator.GetHashCode(); + } + + private static string percentageFormat = PdnResources.GetString("ScaleFactor.Percentage.Format"); + public override string ToString() + { + try + { + return string.Format(percentageFormat, unchecked(Math.Round(unchecked(100 * Ratio)))); + } + + catch (ArithmeticException) + { + return "--"; + } + } + + public int ScaleScalar(int x) + { + return (int)(((long)x * numerator) / denominator); + } + + public int UnscaleScalar(int x) + { + return (int)(((long)x * denominator) / numerator); + } + + public float ScaleScalar(float x) + { + return (x * (float)numerator) / (float)denominator; + } + + public float UnscaleScalar(float x) + { + return (x * (float)denominator) / (float)numerator; + } + + public double ScaleScalar(double x) + { + return (x * (double)numerator) / (double)denominator; + } + + public double UnscaleScalar(double x) + { + return (x * (double)denominator) / (double)numerator; + } + + public Point ScalePoint(Point p) + { + return new Point(ScaleScalar(p.X), ScaleScalar(p.Y)); + } + + public PointF ScalePoint(PointF p) + { + return new PointF(ScaleScalar(p.X), ScaleScalar(p.Y)); + } + + public PointF ScalePointJustX(PointF p) + { + return new PointF(ScaleScalar(p.X), p.Y); + } + + public PointF ScalePointJustY(PointF p) + { + return new PointF(p.X, ScaleScalar(p.Y)); + } + + public PointF UnscalePoint(PointF p) + { + return new PointF(UnscaleScalar(p.X), UnscaleScalar(p.Y)); + } + + public PointF UnscalePointJustX(PointF p) + { + return new PointF(UnscaleScalar(p.X), p.Y); + } + + public PointF UnscalePointJustY(PointF p) + { + return new PointF(p.X, UnscaleScalar(p.Y)); + } + + public Point ScalePointJustX(Point p) + { + return new Point(ScaleScalar(p.X), p.Y); + } + + public Point ScalePointJustY(Point p) + { + return new Point(p.X, ScaleScalar(p.Y)); + } + + public Point UnscalePoint(Point p) + { + return new Point(UnscaleScalar(p.X), UnscaleScalar(p.Y)); + } + + public Point UnscalePointJustX(Point p) + { + return new Point(UnscaleScalar(p.X), p.Y); + } + + public Point UnscalePointJustY(Point p) + { + return new Point(p.X, UnscaleScalar(p.Y)); + } + + public SizeF ScaleSize(SizeF s) + { + return new SizeF(ScaleScalar(s.Width), ScaleScalar(s.Height)); + } + + public SizeF UnscaleSize(SizeF s) + { + return new SizeF(UnscaleScalar(s.Width), UnscaleScalar(s.Height)); + } + + public Size ScaleSize(Size s) + { + return new Size(ScaleScalar(s.Width), ScaleScalar(s.Height)); + } + + public Size UnscaleSize(Size s) + { + return new Size(UnscaleScalar(s.Width), UnscaleScalar(s.Height)); + } + + public RectangleF ScaleRectangle(RectangleF rectF) + { + return new RectangleF(ScalePoint(rectF.Location), ScaleSize(rectF.Size)); + } + + public RectangleF UnscaleRectangle(RectangleF rectF) + { + return new RectangleF(UnscalePoint(rectF.Location), UnscaleSize(rectF.Size)); + } + + public Rectangle ScaleRectangle(Rectangle rect) + { + return new Rectangle(ScalePoint(rect.Location), ScaleSize(rect.Size)); + } + + public Rectangle UnscaleRectangle(Rectangle rect) + { + return new Rectangle(UnscalePoint(rect.Location), UnscaleSize(rect.Size)); + } + + private static readonly double[] scales = + { + 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.08, 0.12, 0.16, 0.25, 0.33, 0.50, 0.66, 1, + 2, 3, 4, 5, 6, 7, 8, 12, 16, 24, 32 + }; + + /// + /// Gets a list of values that GetNextLarger() and GetNextSmaller() will cycle through. + /// + /// + /// 1.0 is guaranteed to be in the array returned by this property. This list is also + /// sorted in ascending order. + /// + public static double[] PresetValues + { + get + { + double[] returnValue = new double[scales.Length]; + scales.CopyTo(returnValue, 0); + return returnValue; + } + } + + /// + /// Rounds the current scaling factor up to the next power of two. + /// + /// The new ScaleFactor value. + public ScaleFactor GetNextLarger() + { + double ratio = Ratio + 0.005; + + int index = Array.FindIndex( + scales, + delegate(double scale) + { + return ratio <= scale; + }); + + if (index == -1) + { + index = scales.Length; + } + + index = Math.Min(index, scales.Length - 1); + + return ScaleFactor.FromDouble(scales[index]); + } + + public ScaleFactor GetNextSmaller() + { + double ratio = Ratio - 0.005; + + int index = Array.FindIndex( + scales, + delegate(double scale) + { + return ratio <= scale; + }); + + --index; + + if (index == -1) + { + index = 0; + } + + index = Math.Max(index, 0); + + return ScaleFactor.FromDouble(scales[index]); + } + + private static ScaleFactor Reduce(int numerator, int denominator) + { + int factor = 2; + + while (factor < denominator && factor < numerator) + { + if ((numerator % factor) == 0 && (denominator % factor) == 0) + { + numerator /= factor; + denominator /= factor; + } + else + { + ++factor; + } + } + + return new ScaleFactor(numerator, denominator); + } + + public static ScaleFactor FromDouble(double scalar) + { + int numerator = (int)(Math.Floor(scalar * 1000.0)); + int denominator = 1000; + return Reduce(numerator, denominator); + } + + public ScaleFactor(int numerator, int denominator) + { + if (denominator <= 0) + { + throw new ArgumentOutOfRangeException("denominator", "must be greater than 0"); + } + + if (numerator < 0) + { + throw new ArgumentOutOfRangeException("numerator", "must be greater than 0"); + } + + this.numerator = numerator; + this.denominator = denominator; + this.Clamp(); + } + } +} diff --git a/src/Core/Scanline.cs b/src/Core/Scanline.cs new file mode 100644 index 0000000..4d5b17a --- /dev/null +++ b/src/Core/Scanline.cs @@ -0,0 +1,88 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public struct Scanline + { + private int x; + private int y; + private int length; + + public int X + { + get + { + return x; + } + } + + public int Y + { + get + { + return y; + } + } + + public int Length + { + get + { + return length; + } + } + + public override int GetHashCode() + { + unchecked + { + return length.GetHashCode() + x.GetHashCode() + y.GetHashCode(); + } + } + + public override bool Equals(object obj) + { + if (obj is Scanline) + { + Scanline rhs = (Scanline)obj; + return x == rhs.x && y == rhs.y && length == rhs.length; + } + else + { + return false; + } + } + + public static bool operator== (Scanline lhs, Scanline rhs) + { + return lhs.x == rhs.x && lhs.y == rhs.y && lhs.length == rhs.length; + } + + public static bool operator!= (Scanline lhs, Scanline rhs) + { + return !(lhs == rhs); + } + + public override string ToString() + { + return "(" + x + "," + y + "):[" + length.ToString() + "]"; + } + + public Scanline(int x, int y, int length) + { + this.x = x; + this.y = y; + this.length = length; + } + } +} diff --git a/src/Core/Selection.cs b/src/Core/Selection.cs new file mode 100644 index 0000000..bf6fba2 --- /dev/null +++ b/src/Core/Selection.cs @@ -0,0 +1,610 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + /// + /// Manages a selection for Paint.NET. + /// There are five major components of a selection: + /// * Base path + /// * Continuation path + /// * Continuation combination mode + /// * Cumulative transform + /// * Interim transform + /// The selection itself, as returned by e.g. CreatePath(), is (base COMBINE continuation) x interimTransform. + /// Whenever the interim transform is set, the continuation path is first committed, and vice versa. + /// Because of this, you may think of the selection as operating in two editing 'modes': editing + /// the path, and editing the transformation. + /// When the continuation path is committed, it is appended to the base path using the given combination mode. + /// The continuation is then reset to empty. + /// When the interim transform is committed, both the base path and the cumulative transform + /// are multiplied by it. The interim transform is then reset to the identity matrix. + /// If the selection is empty, then its "clip region" is the entire canvas as set by the ClipRectangle + /// property. + /// + public sealed class Selection + { + private object syncRoot = new object(); + + public object SyncRoot + { + get + { + return this.syncRoot; + } + } + + private class Data + : ICloneable, + IDisposable + { + public PdnGraphicsPath basePath; + public PdnGraphicsPath continuation; + public CombineMode continuationCombineMode; + public Matrix cumulativeTransform; // resets whenever SetContinuation is called + public Matrix interimTransform; + + public Data() + { + this.basePath = new PdnGraphicsPath(); + this.continuation = new PdnGraphicsPath(); + this.continuationCombineMode = CombineMode.Xor; + this.cumulativeTransform = new Matrix(); + this.cumulativeTransform.Reset(); + this.interimTransform = new Matrix(); + this.interimTransform.Reset(); + } + + ~Data() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + if (disposing) + { + if (this.basePath != null) + { + this.basePath.Dispose(); + this.basePath = null; + } + + if (this.continuation != null) + { + this.continuation.Dispose(); + this.continuation = null; + } + + if (this.cumulativeTransform != null) + { + this.cumulativeTransform.Dispose(); + this.cumulativeTransform = null; + } + + if (this.interimTransform != null) + { + this.interimTransform.Dispose(); + this.interimTransform = null; + } + } + } + + public Data Clone() + { + Data newData = new Data(); + newData.basePath = this.basePath.Clone(); + newData.continuation = this.continuation.Clone(); + newData.continuationCombineMode = this.continuationCombineMode; + newData.cumulativeTransform = this.cumulativeTransform.Clone(); + newData.interimTransform = this.interimTransform.Clone(); + return newData; + } + + object ICloneable.Clone() + { + return (object)Clone(); + } + } + + private Data data; + private int alreadyChanging; // we don't want to nest Changing events -- consolidate them with this + private Rectangle clipRectangle; + + //private PdnGraphicsPath cachedPathTrue; + //private PdnGraphicsPath cachedPathFalse; + + public Selection() + { + this.data = new Data(); + this.alreadyChanging = 0; + this.clipRectangle = new Rectangle(0, 0, 65535, 65535); + } + + public Rectangle ClipRectangle + { + get + { + return this.clipRectangle; + } + + set + { + this.clipRectangle = value; + } + } + + public bool IsEmpty + { + get + { + return this.data.basePath.IsEmpty && this.data.continuation.IsEmpty; + } + } + + public bool IsVisible(Point pt) + { + using (PdnGraphicsPath path = CreatePath()) + //PdnGraphicsPath path = GetPathReadOnly(); + { + return path.IsVisible(pt); + } + } + + public object Save() + { + lock (this.syncRoot) + { + return this.data.Clone(); + } + } + + public void Restore(object state) + { + lock (this.syncRoot) + { + OnChanging(); + this.data.Dispose(); + this.data = ((Data)state).Clone(); + OnChanged(); + } + } + + public PdnGraphicsPath CreatePixelatedPath() + { + using (PdnGraphicsPath path = CreatePath()) + //PdnGraphicsPath path = GetPathReadOnly(); + { + using (PdnRegion region = new PdnRegion(path)) + { + PdnGraphicsPath pixellatedPath = PdnGraphicsPath.FromRegion(region); + return pixellatedPath; + } + } + } + + /* + public PdnGraphicsPath GetPathReadOnly() + { + return GetPathReadOnly(true); + } + * */ + + /* + public PdnGraphicsPath GetPathReadOnly(bool applyInterimTransform) + { + lock (this.syncRoot) + { + if (applyInterimTransform) + { + if (this.cachedPathTrue == null) + { + this.cachedPathTrue = CreatePath(true); + } + + return this.cachedPathTrue; + } + else + { + if (this.cachedPathFalse == null) + { + this.cachedPathFalse = CreatePath(false); + } + + return this.cachedPathFalse; + } + } + } + * */ + + public PdnGraphicsPath CreatePath() + { + return CreatePath(true); + } + + public PdnGraphicsPath CreatePath(bool applyInterimTransform) + { + lock (this.syncRoot) + { + PdnGraphicsPath returnPath = PdnGraphicsPath.Combine(this.data.basePath, this.data.continuationCombineMode, this.data.continuation); + + if (applyInterimTransform) + { + returnPath.Transform(this.data.interimTransform); + } + + return returnPath; + } + } + + public void Reset() + { + lock (this.syncRoot) + { + OnChanging(); + this.data.basePath.Dispose(); + this.data.basePath = new PdnGraphicsPath(); + this.data.continuation.Dispose(); + this.data.continuation = new PdnGraphicsPath(); + this.data.cumulativeTransform.Reset(); + this.data.interimTransform.Reset(); + OnChanged(); + } + } + + public void ResetContinuation() + { + lock (this.syncRoot) + { + OnChanging(); + CommitInterimTransform(); + ResetCumulativeTransform(); + this.data.continuation.Reset(); + OnChanged(); + } + } + + public Rectangle GetBounds() + { + return GetBounds(true); + } + + public Rectangle GetBounds(bool applyInterimTransformation) + { + return Utility.RoundRectangle(GetBoundsF(applyInterimTransformation)); + } + + public RectangleF GetBoundsF() + { + return GetBoundsF(true); + } + + public RectangleF GetBoundsF(bool applyInterimTransformation) + { + using (PdnGraphicsPath path = this.CreatePath(applyInterimTransformation)) + //PdnGraphicsPath path = GetPathReadOnly(applyInterimTransformation); + { + RectangleF bounds2 = path.GetBounds2(); + return bounds2; + } + } + + public void SetContinuation(Rectangle rect, CombineMode combineMode) + { + lock (this.syncRoot) + { + OnChanging(); + CommitInterimTransform(); + ResetCumulativeTransform(); + this.data.continuationCombineMode = combineMode; + this.data.continuation.Reset(); + this.data.continuation.AddRectangle(rect); + OnChanged(); + } + } + + public void SetContinuation(Point[] linePoints, CombineMode combineMode) + { + lock (this.syncRoot) + { + OnChanging(); + CommitInterimTransform(); + ResetCumulativeTransform(); + this.data.continuationCombineMode = combineMode; + this.data.continuation.Reset(); + this.data.continuation.AddLines(linePoints); + OnChanged(); + } + } + + public void SetContinuation(PointF[] linePointsF, CombineMode combineMode) + { + lock (this.syncRoot) + { + OnChanging(); + CommitInterimTransform(); + ResetCumulativeTransform(); + this.data.continuationCombineMode = combineMode; + this.data.continuation.Reset(); + this.data.continuation.AddLines(linePointsF); + OnChanged(); + } + } + + public void SetContinuation(PointF[][] polygonSet, CombineMode combineMode) + { + lock (this.syncRoot) + { + OnChanging(); + CommitInterimTransform(); + ResetCumulativeTransform(); + this.data.continuationCombineMode = combineMode; + this.data.continuation.Reset(); + this.data.continuation.AddPolygons(polygonSet); + OnChanged(); + } + } + + public void SetContinuation(Point[][] polygonSet, CombineMode combineMode) + { + lock (this.syncRoot) + { + OnChanging(); + CommitInterimTransform(); + ResetCumulativeTransform(); + this.data.continuationCombineMode = combineMode; + this.data.continuation.Reset(); + this.data.continuation.AddPolygons(polygonSet); + OnChanged(); + } + } + + // only works if base is empty + public void SetContinuation(PdnGraphicsPath path, CombineMode combineMode, bool takeOwnership) + { + lock (this.syncRoot) + { + if (!this.data.basePath.IsEmpty) + { + throw new InvalidOperationException("base path must be empty to use this overload of SetContinuation"); + } + + OnChanging(); + + CommitInterimTransform(); + ResetCumulativeTransform(); + + this.data.continuationCombineMode = combineMode; + + if (takeOwnership) + { + this.data.continuation.Dispose(); + this.data.continuation = path; + } + else + { + this.data.continuation.Reset(); + this.data.continuation.AddPath(path, false); + } + + OnChanged(); + } + } + + public void CommitContinuation() + { + lock (this.syncRoot) + { + OnChanging(); + this.data.continuation.CloseAllFigures(); + PdnGraphicsPath newBasePath = CreatePath(); + this.data.basePath.Dispose(); + this.data.basePath = newBasePath; + this.data.continuation.Reset(); + this.data.continuationCombineMode = CombineMode.Xor; + OnChanged(); + } + } + + public Matrix GetCumulativeTransformCopy() + { + lock (this.syncRoot) + { + if (this.data.cumulativeTransform == null) + { + Matrix m = new Matrix(); + m.Reset(); + return m; + } + else + { + return this.data.cumulativeTransform.Clone(); + } + } + } + + public Matrix GetCumulativeTransformReadOnly() + { + lock (this.syncRoot) + { + return this.data.cumulativeTransform; + } + } + + private void ResetCumulativeTransform() + { + lock (this.syncRoot) + { + if (this.data.cumulativeTransform == null) + { + this.data.cumulativeTransform = new Matrix(); + } + + this.data.cumulativeTransform.Reset(); + } + } + + public Matrix GetInterimTransformCopy() + { + lock (this.syncRoot) + { + if (this.data.interimTransform == null) + { + Matrix m = new Matrix(); + m.Reset(); + return m; + } + else + { + return this.data.interimTransform.Clone(); + } + } + } + + public Matrix GetInterimTransformReadOnly() + { + lock (this.syncRoot) + { + return this.data.interimTransform; + } + } + + public void SetInterimTransform(Matrix m) + { + lock (this.syncRoot) + { + OnChanging(); + this.data.interimTransform.Dispose(); + this.data.interimTransform = m.Clone(); + OnChanged(); + } + } + + public void CommitInterimTransform() + { + lock (this.syncRoot) + { + if (!this.data.interimTransform.IsIdentity) + { + OnChanging(); + this.data.basePath.Transform(this.data.interimTransform); + this.data.continuation.Transform(this.data.interimTransform); + this.data.cumulativeTransform.Multiply(this.data.interimTransform, MatrixOrder.Append); + this.data.interimTransform.Reset(); + OnChanged(); + } + } + } + + public void ResetInterimTransform() + { + lock (this.syncRoot) + { + OnChanging(); + this.data.interimTransform.Reset(); + OnChanged(); + } + } + + public PdnRegion CreateRegionRaw() + { + using (PdnGraphicsPath path = CreatePath()) + //PdnGraphicsPath path = GetPathReadOnly(); + { + return new PdnRegion(path); + } + } + + public PdnRegion CreateRegion() + { + lock (this.syncRoot) + { + if (IsEmpty) + { + return new PdnRegion(this.clipRectangle); + } + else + { + PdnRegion region = CreateRegionRaw(); + region.Intersect(this.clipRectangle); + return region; + } + } + } + + public event EventHandler Changing; + private void OnChanging() + { + lock (this.syncRoot) + { + if (this.alreadyChanging == 0) + { + if (Changing != null) + { + Changing(this, EventArgs.Empty); + } + } + + //this.cachedPathFalse = null; + //this.cachedPathTrue = null; + } + + ++this.alreadyChanging; + } + + public void PerformChanging() + { + OnChanging(); + } + + public event EventHandler Changed; + private void OnChanged() + { + lock (this.SyncRoot) + { + if (this.alreadyChanging <= 0) + { + throw new InvalidOperationException("Changed event was raised without corresponding Changing event beforehand"); + } + + --this.alreadyChanging; + + //this.cachedPathFalse = null; + //this.cachedPathTrue = null; + } + + if (this.alreadyChanging == 0) + { + if (Changed != null) + { + Changed(this, EventArgs.Empty); + } + } + } + + public void PerformChanged() + { + OnChanged(); + } + } +} diff --git a/src/Core/SnapDescription.cs b/src/Core/SnapDescription.cs new file mode 100644 index 0000000..93b0689 --- /dev/null +++ b/src/Core/SnapDescription.cs @@ -0,0 +1,105 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Specialized; +using System.Drawing; +using System.Globalization; +using System.Text; + +namespace PaintDotNet +{ + public sealed class SnapDescription + { + private SnapObstacle snappedTo; + private HorizontalSnapEdge horizontalEdge; + private VerticalSnapEdge verticalEdge; + private int xOffset; + private int yOffset; + + public SnapObstacle SnappedTo + { + get + { + return this.snappedTo; + } + } + + public HorizontalSnapEdge HorizontalEdge + { + get + { + return this.horizontalEdge; + } + + set + { + this.horizontalEdge = value; + } + } + + public VerticalSnapEdge VerticalEdge + { + get + { + return this.verticalEdge; + } + + set + { + this.verticalEdge = value; + } + } + + public int XOffset + { + get + { + return this.xOffset; + } + + set + { + this.xOffset = value; + } + } + + public int YOffset + { + get + { + return this.yOffset; + } + + set + { + this.yOffset = value; + } + } + + public SnapDescription( + SnapObstacle snappedTo, + HorizontalSnapEdge horizontalEdge, + VerticalSnapEdge verticalEdge, + int xOffset, + int yOffset) + { + if (snappedTo == null) + { + throw new ArgumentNullException("snappedTo"); + } + + this.snappedTo = snappedTo; + this.horizontalEdge = horizontalEdge; + this.verticalEdge = verticalEdge; + this.xOffset = xOffset; + this.yOffset = yOffset; + } + } +} diff --git a/src/Core/SnapManager.cs b/src/Core/SnapManager.cs new file mode 100644 index 0000000..556e0d2 --- /dev/null +++ b/src/Core/SnapManager.cs @@ -0,0 +1,620 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Drawing; +using System.Globalization; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class SnapManager + { + private Dictionary obstacles = + new Dictionary(); + + private const string isSnappedValueName = "IsSnapped"; + private const string leftValueName = "Left"; + private const string topValueName = "Top"; + private const string widthValueName = "Width"; + private const string heightValueName = "Height"; + private const string nullName = ""; + + private const string snappedToValueName = "SnappedTo"; + private const string horizontalEdgeValueName = "HorizontalEdge"; + private const string verticalEdgeValueName = "VerticalEdge"; + private const string xOffsetValueName = "XOffset"; + private const string yOffsetValueName = "YOffset"; + + private void SaveSnapObstacleData(ISimpleCollection saveTo, SnapObstacle so) + { + string prefix = so.Name + "."; + SnapDescription sd = this.obstacles[so]; + + bool isSnappedValue = (sd != null); + saveTo.Set(prefix + isSnappedValueName, isSnappedValue.ToString(CultureInfo.InvariantCulture)); + + if (isSnappedValue) + { + saveTo.Set(prefix + snappedToValueName, sd.SnappedTo.Name); + saveTo.Set(prefix + horizontalEdgeValueName, sd.HorizontalEdge.ToString()); + saveTo.Set(prefix + verticalEdgeValueName, sd.VerticalEdge.ToString()); + saveTo.Set(prefix + xOffsetValueName, sd.XOffset.ToString(CultureInfo.InvariantCulture)); + saveTo.Set(prefix + yOffsetValueName, sd.YOffset.ToString(CultureInfo.InvariantCulture)); + } + + saveTo.Set(prefix + leftValueName, so.Bounds.Left.ToString(CultureInfo.InvariantCulture)); + saveTo.Set(prefix + topValueName, so.Bounds.Top.ToString(CultureInfo.InvariantCulture)); + saveTo.Set(prefix + widthValueName, so.Bounds.Width.ToString(CultureInfo.InvariantCulture)); + saveTo.Set(prefix + heightValueName, so.Bounds.Height.ToString(CultureInfo.InvariantCulture)); + } + + private void LoadSnapObstacleData(ISimpleCollection loadFrom, SnapObstacle so) + { + string prefix = so.Name + "."; + SnapDescription sd; + + string isSnappedString = loadFrom.Get(prefix + isSnappedValueName); + bool isSnapped = bool.Parse(isSnappedString); + + if (isSnapped) + { + string snappedToString = loadFrom.Get(prefix + snappedToValueName); + SnapObstacle snappedTo = FindObstacle(snappedToString); + + string horizontalEdgeString = loadFrom.Get(prefix + horizontalEdgeValueName); + HorizontalSnapEdge horizontalEdge = (HorizontalSnapEdge)Enum.Parse(typeof(HorizontalSnapEdge), horizontalEdgeString, true); + + string verticalEdgeString = loadFrom.Get(prefix + verticalEdgeValueName); + VerticalSnapEdge verticalEdge = (VerticalSnapEdge)Enum.Parse(typeof(VerticalSnapEdge), verticalEdgeString, true); + + string xOffsetString = loadFrom.Get(prefix + xOffsetValueName); + int xOffset = int.Parse(xOffsetString, CultureInfo.InvariantCulture); + + string yOffsetString = loadFrom.Get(prefix + yOffsetValueName); + int yOffset = int.Parse(yOffsetString, CultureInfo.InvariantCulture); + + sd = new SnapDescription(snappedTo, horizontalEdge, verticalEdge, xOffset, yOffset); + } + else + { + sd = null; + } + + this.obstacles[so] = sd; + + string leftString = loadFrom.Get(prefix + leftValueName); + int left = int.Parse(leftString, CultureInfo.InvariantCulture); + + string topString = loadFrom.Get(prefix + topValueName); + int top = int.Parse(topString, CultureInfo.InvariantCulture); + + string widthString = loadFrom.Get(prefix + widthValueName); + int width = int.Parse(widthString, CultureInfo.InvariantCulture); + + string heightString = loadFrom.Get(prefix + heightValueName); + int height = int.Parse(heightString, CultureInfo.InvariantCulture); + + Rectangle newBounds = new Rectangle(left, top, width, height); + so.RequestBoundsChange(newBounds); + + if (sd != null) + { + ParkObstacle(so, sd); + } + } + + // Requires that all SnapObstacles are already placed in this.obstacles + public void Save(ISimpleCollection saveTo) + { + foreach (SnapObstacle obstacle in this.obstacles.Keys) + { + // TODO: how do we 'erase' something that has this property set to false, for full generality? + if (obstacle.EnableSave) + { + SaveSnapObstacleData(saveTo, obstacle); + } + } + } + + public void Load(ISimpleCollection loadFrom) + { + SnapObstacle[] newObstacles = new SnapObstacle[this.obstacles.Count]; + this.obstacles.Keys.CopyTo(newObstacles, 0); + + foreach (SnapObstacle obstacle in newObstacles) + { + if (obstacle.EnableSave) + { + LoadSnapObstacleData(loadFrom, obstacle); + } + } + } + + public void ParkObstacle(ISnapObstacleHost obstacle, ISnapObstacleHost snappedTo, HorizontalSnapEdge hEdge, VerticalSnapEdge vEdge) + { + ParkObstacle(obstacle.SnapObstacle, snappedTo.SnapObstacle, hEdge, vEdge); + } + + public void ParkObstacle(SnapObstacle obstacle, SnapObstacle snappedTo, HorizontalSnapEdge hEdge, VerticalSnapEdge vEdge) + { + SnapDescription sd = new SnapDescription(snappedTo, hEdge, vEdge, obstacle.SnapDistance, obstacle.SnapDistance); + this.obstacles[obstacle] = sd; + ParkObstacle(obstacle, sd); + } + + public void ReparkObstacle(ISnapObstacleHost obstacle) + { + ReparkObstacle(obstacle.SnapObstacle); + } + + public void ReparkObstacle(SnapObstacle obstacle) + { + if (this.obstacles.ContainsKey(obstacle)) + { + SnapDescription sd = this.obstacles[obstacle]; + + if (sd != null) + { + ParkObstacle(obstacle, sd); + } + } + } + + public void AddSnapObstacle(ISnapObstacleHost snapObstacleHost) + { + AddSnapObstacle(snapObstacleHost.SnapObstacle); + } + + public void AddSnapObstacle(SnapObstacle snapObstacle) + { + if (!this.obstacles.ContainsKey(snapObstacle)) + { + this.obstacles.Add(snapObstacle, null); + + if (snapObstacle.StickyEdges) + { + snapObstacle.BoundsChanging += SnapObstacle_BoundsChanging; + snapObstacle.BoundsChanged += SnapObstacle_BoundsChanged; + } + } + } + + private void SnapObstacle_BoundsChanging(object sender, EventArgs e) + { + } + + private void SnapObstacle_BoundsChanged(object sender, EventArgs e) + { + SnapObstacle senderSO = (SnapObstacle)sender; + Rectangle fromRect = e.Data; + Rectangle toRect = senderSO.Bounds; + UpdateDependentObstacles(senderSO, fromRect, toRect); + } + + private void UpdateDependentObstacles(SnapObstacle senderSO, Rectangle fromRect, Rectangle toRect) + { + int leftDelta = toRect.Left - fromRect.Left; + int topDelta = toRect.Top - fromRect.Top; + int rightDelta = toRect.Right - fromRect.Right; + int bottomDelta = toRect.Bottom - fromRect.Bottom; + + foreach (SnapObstacle obstacle in this.obstacles.Keys) + { + if (!object.ReferenceEquals(senderSO, obstacle)) + { + SnapDescription sd = this.obstacles[obstacle]; + + if (sd != null && object.ReferenceEquals(sd.SnappedTo, senderSO)) + { + int deltaX; + + if (sd.VerticalEdge == VerticalSnapEdge.Right) + { + deltaX = rightDelta; + } + else + { + deltaX = leftDelta; + } + + int deltaY; + + if (sd.HorizontalEdge == HorizontalSnapEdge.Bottom) + { + deltaY = bottomDelta; + } + else + { + deltaY = topDelta; + } + + Rectangle oldBounds = obstacle.Bounds; + Point newLocation1 = new Point(oldBounds.Left + deltaX, oldBounds.Top + deltaY); + Point newLocation2 = AdjustNewLocation(obstacle, newLocation1, sd); + Rectangle newBounds = new Rectangle(newLocation2, oldBounds.Size); + + obstacle.RequestBoundsChange(newBounds); + + // Recursively update anything snapped to this obstacle + UpdateDependentObstacles(obstacle, oldBounds, newBounds); + } + } + } + } + + public void RemoveSnapObstacle(ISnapObstacleHost snapObstacleHost) + { + RemoveSnapObstacle(snapObstacleHost.SnapObstacle); + } + + public void RemoveSnapObstacle(SnapObstacle snapObstacle) + { + if (this.obstacles.ContainsKey(snapObstacle)) + { + this.obstacles.Remove(snapObstacle); + + if (snapObstacle.StickyEdges) + { + snapObstacle.BoundsChanging -= SnapObstacle_BoundsChanging; + snapObstacle.BoundsChanged -= SnapObstacle_BoundsChanged; + } + } + } + + public bool ContainsSnapObstacle(ISnapObstacleHost snapObstacleHost) + { + return ContainsSnapObstacle(snapObstacleHost.SnapObstacle); + } + + public bool ContainsSnapObstacle(SnapObstacle snapObstacle) + { + return this.obstacles.ContainsKey(snapObstacle); + } + + private static bool AreEdgesClose(int l1, int r1, int l2, int r2) + { + if (r1 < l2) + { + return false; + } + else if (r2 < l1) + { + return false; + } + else if (l1 <= l2 && l2 <= r1 && r1 <= r2) + { + return true; + } + else if (l2 <= l1 && l1 <= r2 && r2 <= r1) + { + return true; + } + else if (l1 <= l2 && r2 <= r1) + { + return true; + } + else if (l2 <= l1 && l1 <= r2) + { + return true; + } + + throw new InvalidOperationException(); + } + + private SnapDescription DetermineNewSnapDescription( + SnapObstacle avoider, + Point newLocation, + SnapObstacle avoidee, + SnapDescription currentSnapDescription) + { + int ourSnapProximity; + + if (currentSnapDescription != null && + (currentSnapDescription.HorizontalEdge != HorizontalSnapEdge.Neither || + currentSnapDescription.VerticalEdge != VerticalSnapEdge.Neither)) + { + // the avoider is already snapped to the avoidee -- make it more difficult to un-snap + ourSnapProximity = avoidee.SnapProximity * 2; + } + else + { + ourSnapProximity = avoidee.SnapProximity; + } + + Rectangle avoiderRect = avoider.Bounds; + avoiderRect.Location = newLocation; + Rectangle avoideeRect = avoidee.Bounds; + + // Are the vertical edges close enough for snapping? + bool vertProximity = AreEdgesClose(avoiderRect.Top, avoiderRect.Bottom, avoideeRect.Top, avoideeRect.Bottom); + + // Are the horizontal edges close enough for snapping? + bool horizProximity = AreEdgesClose(avoiderRect.Left, avoiderRect.Right, avoideeRect.Left, avoideeRect.Right); + + // Compute distances from pertinent edges + // (e.g. if SnapRegion.Interior, figure out distance from avoider's right edge to avoidee's right edge, + // if SnapRegion.Exterior, figure out distance from avoider's right edge to avoidee's left edge) + int leftDistance; + int rightDistance; + int topDistance; + int bottomDistance; + + switch (avoidee.SnapRegion) + { + case SnapRegion.Interior: + leftDistance = Math.Abs(avoiderRect.Left - avoideeRect.Left); + rightDistance = Math.Abs(avoiderRect.Right - avoideeRect.Right); + topDistance = Math.Abs(avoiderRect.Top - avoideeRect.Top); + bottomDistance = Math.Abs(avoiderRect.Bottom - avoideeRect.Bottom); + break; + + case SnapRegion.Exterior: + leftDistance = Math.Abs(avoiderRect.Left - avoideeRect.Right); + rightDistance = Math.Abs(avoiderRect.Right - avoideeRect.Left); + topDistance = Math.Abs(avoiderRect.Top - avoideeRect.Bottom); + bottomDistance = Math.Abs(avoiderRect.Bottom - avoideeRect.Top); + break; + + default: + throw new InvalidEnumArgumentException("avoidee.SnapRegion"); + } + + bool leftClose = (leftDistance < ourSnapProximity); + bool rightClose = (rightDistance < ourSnapProximity); + bool topClose = (topDistance < ourSnapProximity); + bool bottomClose = (bottomDistance < ourSnapProximity); + + VerticalSnapEdge vEdge = VerticalSnapEdge.Neither; + + if (vertProximity) + { + if ((leftClose && avoidee.SnapRegion == SnapRegion.Exterior) || + (rightClose && avoidee.SnapRegion == SnapRegion.Interior)) + { + vEdge = VerticalSnapEdge.Right; + } + else if ((rightClose && avoidee.SnapRegion == SnapRegion.Exterior) || + (leftClose && avoidee.SnapRegion == SnapRegion.Interior)) + { + vEdge = VerticalSnapEdge.Left; + } + } + + HorizontalSnapEdge hEdge = HorizontalSnapEdge.Neither; + + if (horizProximity) + { + if ((topClose && avoidee.SnapRegion == SnapRegion.Exterior) || + (bottomClose && avoidee.SnapRegion == SnapRegion.Interior)) + { + hEdge = HorizontalSnapEdge.Bottom; + } + else if ((bottomClose && avoidee.SnapRegion == SnapRegion.Exterior) || + (topClose && avoidee.SnapRegion == SnapRegion.Interior)) + { + hEdge = HorizontalSnapEdge.Top; + } + } + + SnapDescription sd; + + if (hEdge != HorizontalSnapEdge.Neither || vEdge != VerticalSnapEdge.Neither) + { + int xOffset = avoider.SnapDistance; + int yOffset = avoider.SnapDistance; + + if (hEdge == HorizontalSnapEdge.Neither) + { + if (avoidee.SnapRegion == SnapRegion.Interior) + { + yOffset = avoiderRect.Top - avoideeRect.Top; + hEdge = HorizontalSnapEdge.Top; + } + } + + if (vEdge == VerticalSnapEdge.Neither) + { + if (avoidee.SnapRegion == SnapRegion.Interior) + { + xOffset = avoiderRect.Left - avoideeRect.Left; + vEdge = VerticalSnapEdge.Left; + } + } + + sd = new SnapDescription(avoidee, hEdge, vEdge, xOffset, yOffset); + } + else + { + sd = null; + } + + return sd; + } + + private static void ParkObstacle(SnapObstacle avoider, SnapDescription snapDescription) + { + Point newLocation = avoider.Bounds.Location; + Point adjustedLocation = AdjustNewLocation(avoider, newLocation, snapDescription); + Rectangle newBounds = new Rectangle(adjustedLocation, avoider.Bounds.Size); + avoider.RequestBoundsChange(newBounds); + } + + private static Point AdjustNewLocation(SnapObstacle obstacle, Point newLocation, SnapDescription snapDescription) + { + if (snapDescription == null || + (snapDescription.HorizontalEdge == HorizontalSnapEdge.Neither && + snapDescription.VerticalEdge == VerticalSnapEdge.Neither)) + { + return obstacle.Bounds.Location; + } + + Rectangle obstacleRect = new Rectangle(newLocation, obstacle.Bounds.Size); + Rectangle snappedToRect = snapDescription.SnappedTo.Bounds; + HorizontalSnapEdge hEdge = snapDescription.HorizontalEdge; + VerticalSnapEdge vEdge = snapDescription.VerticalEdge; + SnapRegion region = snapDescription.SnappedTo.SnapRegion; + + int deltaY = 0; + + if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Exterior) + { + int newBottomEdge = snappedToRect.Top - snapDescription.YOffset; + deltaY = obstacleRect.Bottom - newBottomEdge; + } + else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Exterior) + { + int newTopEdge = snappedToRect.Bottom + snapDescription.YOffset; + deltaY = obstacleRect.Top - newTopEdge; + } + else if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Interior) + { + int newTopEdge = Math.Min(snappedToRect.Bottom, snappedToRect.Top + snapDescription.YOffset); + deltaY = obstacleRect.Top - newTopEdge; + } + else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Interior) + { + int newBottomEdge = Math.Max(snappedToRect.Top, snappedToRect.Bottom - snapDescription.YOffset); + deltaY = obstacleRect.Bottom - newBottomEdge; + } + + int deltaX = 0; + + if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Exterior) + { + int newRightEdge = snappedToRect.Left - snapDescription.XOffset; + deltaX = obstacleRect.Right - newRightEdge; + } + else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Exterior) + { + int newLeftEdge = snappedToRect.Right + snapDescription.XOffset; + deltaX = obstacleRect.Left - newLeftEdge; + } + else if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Interior) + { + int newLeftEdge = Math.Min(snappedToRect.Right, snappedToRect.Left + snapDescription.XOffset); + deltaX = obstacleRect.Left - newLeftEdge; + } + else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Interior) + { + int newRightEdge = Math.Max(snappedToRect.Left, snappedToRect.Right - snapDescription.XOffset); + deltaX = obstacleRect.Right - newRightEdge; + } + + Point adjustedLocation = new Point(obstacleRect.Left - deltaX, obstacleRect.Top - deltaY); + return adjustedLocation; + } + + /// + /// Given an obstacle and its attempted destination, determines the correct landing + /// spot for an obstacle. + /// + /// The obstacle that is moving. + /// The upper-left coordinate of the obstacle's original intended destination. + /// + /// A Point that determines where the obstacle should be placed instead. If there are no adjustments + /// required to the obstacle's desintation, then the return value will be equal to newLocation. + /// + /// + /// movingObstacle's SnapDescription will also be updated. The caller of this method is required + /// to update the SnapObstacle with the new, adjusted location. + /// + public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation) + { + Point adjusted1 = AdjustObstacleDestination(movingObstacle, newLocation, false); + Point adjusted2 = AdjustObstacleDestination(movingObstacle, adjusted1, true); + return adjusted2; + } + + public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation, bool considerStickies) + { + Point adjustedLocation = newLocation; + SnapDescription sd = this.obstacles[movingObstacle]; + SnapDescription newSD = null; + + foreach (SnapObstacle avoidee in this.obstacles.Keys) + { + if (avoidee.StickyEdges != considerStickies) + { + continue; + } + + if (avoidee.Enabled && !object.ReferenceEquals(avoidee, movingObstacle)) + { + SnapDescription newSD2 = DetermineNewSnapDescription(movingObstacle, adjustedLocation, avoidee, newSD); + + if (newSD2 != null) + { + Point adjustedLocation2 = AdjustNewLocation(movingObstacle, adjustedLocation, newSD2); + newSD = newSD2; + adjustedLocation = adjustedLocation2; + Rectangle newBounds = new Rectangle(adjustedLocation, movingObstacle.Bounds.Size); + } + } + } + + if (sd == null || !sd.SnappedTo.StickyEdges || newSD == null || newSD.SnappedTo.StickyEdges) + { + this.obstacles[movingObstacle] = newSD; + } + + return adjustedLocation; + } + + public SnapObstacle FindObstacle(string name) + { + foreach (SnapObstacle so in this.obstacles.Keys) + { + if (string.Compare(so.Name, name, true) == 0) + { + return so; + } + } + + return null; + } + + public static SnapManager FindMySnapManager(Control me) + { + if (!(me is ISnapObstacleHost)) + { + throw new ArgumentException("must be called with a Control that implements ISnapObstacleHost"); + } + + ISnapManagerHost ismh; + + ismh = me as ISnapManagerHost; + + if (ismh == null) + { + ismh = me.FindForm() as ISnapManagerHost; + } + + SnapManager sm; + if (ismh != null) + { + sm = ismh.SnapManager; + } + else + { + sm = null; + } + + return sm; + } + + public SnapManager() + { + } + } +} diff --git a/src/Core/SnapObstacle.cs b/src/Core/SnapObstacle.cs new file mode 100644 index 0000000..8a2e015 --- /dev/null +++ b/src/Core/SnapObstacle.cs @@ -0,0 +1,180 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + public abstract class SnapObstacle + { + public const int DefaultSnapProximity = 15; + public const int DefaultSnapDistance = 3; + + private string name; + protected Rectangle previousBounds; // for BoundsChanged event + protected Rectangle bounds; + private SnapRegion snapRegion; + private bool stickyEdges; + private int snapProximity; + private int snapDistance; + private bool enabled; + private bool enableSave; + + public string Name + { + get + { + return this.name; + } + } + + /// + /// Gets the bounds of this snap obstacle, defined in coordinates relative to its container. + /// + public Rectangle Bounds + { + get + { + return this.bounds; + } + } + + protected virtual void OnBoundsChangeRequested(Rectangle newBounds, ref bool handled) + { + } + + public bool RequestBoundsChange(Rectangle newBounds) + { + bool handled = false; + OnBoundsChangeRequested(newBounds, ref handled); + return handled; + } + + public SnapRegion SnapRegion + { + get + { + return this.snapRegion; + } + } + + /// + /// Gets whether or not this obstacle has "sticky" edges. + /// + /// + /// If an obstacle has sticky edges, than any obstacle that is snapped on + /// to it will move with this obstacle. + /// + public bool StickyEdges + { + get + { + return this.stickyEdges; + } + } + + /// + /// Gets how close another obstacle must be to snap to this one, in pixels + /// + public int SnapProximity + { + get + { + return this.snapProximity; + } + } + + /// + /// Gets how close another obstacle will be parked when it snaps to this one, in pixels. + /// + public int SnapDistance + { + get + { + return this.snapDistance; + } + } + + public bool Enabled + { + get + { + return this.enabled; + } + + set + { + this.enabled = value; + } + } + + public bool EnableSave + { + get + { + return this.enableSave; + } + + set + { + this.enableSave = value; + } + } + + /// + /// Raised before the Bounds is changed. + /// + /// + /// The Data property of the event args is the value that Bounds is being set to. + /// + public event EventHandler> BoundsChanging; + protected virtual void OnBoundsChanging() + { + if (BoundsChanging != null) + { + BoundsChanging(this, new EventArgs(this.Bounds)); + } + } + + /// + /// Raised after the Bounds is changed. + /// + /// + /// The Data property of the event args is the value that Bounds was just changed from. + /// + public event EventHandler> BoundsChanged; + protected virtual void OnBoundsChanged() + { + if (BoundsChanged != null) + { + BoundsChanged(this, new EventArgs(this.previousBounds)); + } + } + + internal SnapObstacle(string name, Rectangle bounds, SnapRegion snapRegion, bool stickyEdges) + : this(name, bounds, snapRegion, stickyEdges, DefaultSnapProximity, DefaultSnapDistance) + { + } + + internal SnapObstacle(string name, Rectangle bounds, SnapRegion snapRegion, bool stickyEdges, int snapProximity, int snapDistance) + { + this.name = name; + this.bounds = bounds; + this.previousBounds = bounds; + this.snapRegion = snapRegion; + this.stickyEdges = stickyEdges; + this.snapProximity = snapProximity; + this.snapDistance = snapDistance; + this.enabled = true; + this.enableSave = true; + } + } +} diff --git a/src/Core/SnapObstacleController.cs b/src/Core/SnapObstacleController.cs new file mode 100644 index 0000000..df4ca4d --- /dev/null +++ b/src/Core/SnapObstacleController.cs @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + public sealed class SnapObstacleController + : SnapObstacle + { + /// + /// Used for the obstacle to report changes in the obstacles size and/or location. + /// + public void SetBounds(Rectangle bounds) + { + if (this.bounds != bounds) + { + OnBoundsChanging(); + this.previousBounds = this.bounds; + this.bounds = bounds; + OnBoundsChanged(); + } + } + + /// + /// Raised when the SnapManager is requesting that the obstacle move and/or resize itself. + /// Usually this happens in response to another snap container with "sticky edges" changing + /// its boundary. + /// + public event HandledEventHandler BoundsChangeRequested; + + protected override void OnBoundsChangeRequested(Rectangle newBounds, ref bool handled) + { + if (BoundsChangeRequested != null) + { + HandledEventArgs e = new HandledEventArgs(handled, newBounds); + BoundsChangeRequested(this, e); + handled = e.Handled; + } + + base.OnBoundsChangeRequested(newBounds, ref handled); + } + + public SnapObstacleController(string name, Rectangle bounds, SnapRegion snapRegion, bool stickyEdges) + : base(name, bounds, snapRegion, stickyEdges) + { + } + + public SnapObstacleController(string name, Rectangle bounds, SnapRegion snapRegion, bool stickyEdges, int snapProximity, int snapDistance) + : base(name, bounds, snapRegion, stickyEdges, snapProximity, snapDistance) + { + } + } +} diff --git a/src/Core/SnapRegion.cs b/src/Core/SnapRegion.cs new file mode 100644 index 0000000..486bfc7 --- /dev/null +++ b/src/Core/SnapRegion.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + public enum SnapRegion + { + Interior, + Exterior + } +} diff --git a/src/Core/SplineInterpolator.cs b/src/Core/SplineInterpolator.cs new file mode 100644 index 0000000..dc5df8f --- /dev/null +++ b/src/Core/SplineInterpolator.cs @@ -0,0 +1,121 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class SplineInterpolator + { + private SortedList points = new SortedList(); + private double[] y2; + + public int Count + { + get + { + return this.points.Count; + } + } + + public void Add(double x, double y) + { + points[x] = y; + this.y2 = null; + } + + public void Clear() + { + this.points.Clear(); + } + + // Interpolate() and PreCompute() are adapted from: + // NUMERICAL RECIPES IN C: THE ART OF SCIENTIFIC COMPUTING + // ISBN 0-521-43108-5, page 113, section 3.3. + + public double Interpolate(double x) + { + if (y2 == null) + { + PreCompute(); + } + + IList xa = this.points.Keys; + IList ya = this.points.Values; + + int n = ya.Count; + int klo = 0; // We will find the right place in the table by means of + int khi = n - 1; // bisection. This is optimal if sequential calls to this + + while (khi - klo > 1) + { + // routine are at random values of x. If sequential calls + int k = (khi + klo) >> 1;// are in order, and closely spaced, one would do better + + if (xa[k] > x) + { + khi = k; // to store previous values of klo and khi and test if + } + else + { + klo = k; + } + } + + double h = xa[khi] - xa[klo]; + double a = (xa[khi] - x) / h; + double b = (x - xa[klo]) / h; + + // Cubic spline polynomial is now evaluated. + return a * ya[klo] + b * ya[khi] + + ((a * a * a - a) * y2[klo] + (b * b * b - b) * y2[khi]) * (h * h) / 6.0; + } + + private void PreCompute() + { + int n = points.Count; + double[] u = new double[n]; + IList xa = points.Keys; + IList ya = points.Values; + + this.y2 = new double[n]; + + u[0] = 0; + this.y2[0] = 0; + + for (int i = 1; i < n - 1; ++i) + { + // This is the decomposition loop of the tridiagonal algorithm. + // y2 and u are used for temporary storage of the decomposed factors. + double wx = xa[i + 1] - xa[i - 1]; + double sig = (xa[i] - xa[i - 1]) / wx; + double p = sig * y2[i - 1] + 2.0; + + this.y2[i] = (sig - 1.0) / p; + + double ddydx = + (ya[i + 1] - ya[i]) / (xa[i + 1] - xa[i]) - + (ya[i] - ya[i - 1]) / (xa[i] - xa[i - 1]); + + u[i] = (6.0 * ddydx / wx - sig * u[i - 1]) / p; + } + + this.y2[n - 1] = 0; + + // This is the backsubstitution loop of the tridiagonal algorithm + for (int i = n - 2; i >= 0; --i) + { + this.y2[i] = this.y2[i] * this.y2[i + 1] + u[i]; + } + } + } +} diff --git a/src/Core/State.cs b/src/Core/State.cs new file mode 100644 index 0000000..f3c5b53 --- /dev/null +++ b/src/Core/State.cs @@ -0,0 +1,95 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public abstract class State + { + private StateMachine stateMachine; + private bool isFinalState; + + private bool abortedRequested = false; + + protected bool AbortRequested + { + get + { + return this.abortedRequested; + } + } + + public StateMachine StateMachine + { + get + { + return this.stateMachine; + } + + set + { + this.stateMachine = value; + } + } + + public bool IsFinalState + { + get + { + return this.isFinalState; + } + } + + protected virtual void OnAbort() + { + } + + public virtual bool CanAbort + { + get + { + return false; + } + } + + public void Abort() + { + if (CanAbort) + { + this.abortedRequested = true; + OnAbort(); + } + } + + public virtual void OnEnteredState() + { + } + + public abstract void ProcessInput(object input, out State newState); + + protected void OnProgress(double percent) + { + if (this.StateMachine != null) + { + this.StateMachine.OnStateProgress(percent); + } + } + + protected State() + : this(false) + { + } + + protected State(bool isFinalState) + { + this.isFinalState = isFinalState; + } + } +} diff --git a/src/Core/StateMachine.cs b/src/Core/StateMachine.cs new file mode 100644 index 0000000..012237b --- /dev/null +++ b/src/Core/StateMachine.cs @@ -0,0 +1,144 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; + +namespace PaintDotNet +{ + public class StateMachine + { + private ArrayList inputAlphabet; + private State initialState; + private State currentState; + private bool processingInput = false; + private Queue inputQueue = new Queue(); + + public event EventHandler> NewState; + private void OnNewState(State newState) + { + if (NewState != null) + { + NewState(this, new EventArgs(newState)); + } + } + + public event ProgressEventHandler StateProgress; + public void OnStateProgress(double percent) + { + if (StateProgress != null) + { + StateProgress(this, new ProgressEventArgs(percent)); + } + } + + public State CurrentState + { + get + { + return this.currentState; + } + } + + public bool IsInFinalState + { + get + { + return this.currentState.IsFinalState; + } + } + + private void SetCurrentState(State newState) + { + if (this.currentState != null && this.currentState.IsFinalState) + { + throw new InvalidOperationException("state machine is already in a final state"); + } + + this.currentState = newState; + this.currentState.StateMachine = this; + OnNewState(this.currentState); + this.currentState.OnEnteredState(); + + if (!this.currentState.IsFinalState) + { + ProcessQueuedInput(); + } + } + + public void QueueInput(object input) + { + this.inputQueue.Enqueue(input); + } + + public void ProcessInput(object input) + { + if (this.processingInput) + { + throw new InvalidOperationException("already processing input"); + } + + if (this.currentState.IsFinalState) + { + throw new InvalidOperationException("state machine is already in a final state"); + } + + if (!this.inputAlphabet.Contains(input)) + { + throw new ArgumentOutOfRangeException("must be contained in the input alphabet set", "input"); + } + + this.inputQueue.Enqueue(input); + ProcessQueuedInput(); + } + + private void ProcessQueuedInput() + { + while (this.inputQueue.Count > 0) + { + object processMe = this.inputQueue.Dequeue(); + + State newState; + this.currentState.ProcessInput(processMe, out newState); + + if (newState == currentState) + { + throw new InvalidOperationException("must provide a clean, newly constructed state"); + } + + SetCurrentState(newState); + } + } + + public void Start() + { + if (this.currentState != null) + { + throw new InvalidOperationException("may only call Start() once after construction"); + } + + SetCurrentState(this.initialState); + } + + public StateMachine(State initialState, IEnumerable inputAlphabet) + { + this.initialState = initialState; + + this.inputAlphabet = new ArrayList(); + + foreach (object o in inputAlphabet) + { + this.inputAlphabet.Add(o); + } + } + } +} diff --git a/src/Core/StateMachineExecutor.cs b/src/Core/StateMachineExecutor.cs new file mode 100644 index 0000000..fe7267b --- /dev/null +++ b/src/Core/StateMachineExecutor.cs @@ -0,0 +1,351 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Threading; + +namespace PaintDotNet +{ + public sealed class StateMachineExecutor + : IDisposable + { + private bool disposed = false; + private bool isStarted = false; + private Thread stateMachineThread; + private Exception threadException; + private StateMachine stateMachine; + private ISynchronizeInvoke syncContext; + private ManualResetEvent stateMachineInitialized = new ManualResetEvent(false); + private ManualResetEvent stateMachineNotBusy = new ManualResetEvent(false); // non-signaled when busy, signaled when not busy + private ManualResetEvent inputAvailable = new ManualResetEvent(false); // non-signaled when no input sent from main thread, signaled when there is input or an abort signal + private volatile bool pleaseAbort = false; + private object queuedInput; + private bool lowPriorityExecution = false; + + public event EventHandler StateMachineBegin; + private void OnStateMachineBegin() + { + if (this.syncContext != null && this.syncContext.InvokeRequired) + { + this.syncContext.BeginInvoke(new Procedure(OnStateMachineBegin), null); + } + else + { + if (StateMachineBegin != null) + { + StateMachineBegin(this, EventArgs.Empty); + } + } + } + + public event EventHandler> StateBegin; + private void OnStateBegin(State state) + { + if (this.syncContext != null && this.syncContext.InvokeRequired) + { + this.syncContext.BeginInvoke(new Procedure(OnStateBegin), new object[] { state }); + } + else + { + if (StateBegin != null) + { + StateBegin(this, new EventArgs(state)); + } + } + } + + public event ProgressEventHandler StateProgress; + private void OnStateProgress(double percent) + { + if (this.syncContext != null && this.syncContext.InvokeRequired) + { + this.syncContext.BeginInvoke(new Procedure(OnStateProgress), new object[] { percent }); + } + else + { + if (StateProgress != null) + { + StateProgress(this, new ProgressEventArgs(percent)); + } + } + } + + public event EventHandler> StateWaitingForInput; + private void OnStateWaitingForInput(State state) + { + if (this.syncContext != null && this.syncContext.InvokeRequired) + { + this.syncContext.BeginInvoke(new Procedure(OnStateWaitingForInput), new object[] { state }); + } + else + { + if (StateWaitingForInput != null) + { + StateWaitingForInput(this, new EventArgs(state)); + } + } + } + + public event EventHandler StateMachineFinished; + private void OnStateMachineFinished() + { + if (this.syncContext != null && this.syncContext.InvokeRequired) + { + this.syncContext.BeginInvoke(new Procedure(OnStateMachineFinished), null); + } + else + { + if (StateMachineFinished != null) + { + StateMachineFinished(this, EventArgs.Empty); + } + } + } + + public bool IsStarted + { + get + { + return this.isStarted; + } + } + + public bool LowPriorityExecution + { + get + { + return this.lowPriorityExecution; + } + + set + { + if (IsStarted) + { + throw new InvalidOperationException("Can only enable low priority execution before the state machine begins execution"); + } + + this.lowPriorityExecution = value; + } + } + + public ISynchronizeInvoke SyncContext + { + get + { + return this.syncContext; + } + + set + { + this.syncContext = value; + } + } + + public State CurrentState + { + get + { + return this.stateMachine.CurrentState; + } + } + + public bool IsInFinalState + { + get + { + return this.stateMachine.IsInFinalState; + } + } + + private void StateMachineThread() + { + ThreadBackground tbm = null; + + try + { + if (this.lowPriorityExecution) + { + tbm = new ThreadBackground(ThreadBackgroundFlags.Cpu); + } + + StateMachineThreadImpl(); + } + + finally + { + if (tbm != null) + { + tbm.Dispose(); + tbm = null; + } + } + } + + private void StateMachineThreadImpl() + { + this.threadException = null; + + EventHandler> newStateHandler = + delegate(object sender, EventArgs e) + { + this.stateMachineInitialized.Set(); + OnStateBegin(e.Data); + }; + + ProgressEventHandler stateProgressHandler = + delegate(object sender, ProgressEventArgs e) + { + OnStateProgress(e.Percent); + }; + + try + { + this.stateMachineNotBusy.Set(); + + OnStateMachineBegin(); + + this.stateMachineNotBusy.Reset(); + this.stateMachine.NewState += newStateHandler; + this.stateMachine.StateProgress += stateProgressHandler; + this.stateMachine.Start(); + + while (true) + { + this.stateMachineNotBusy.Set(); + OnStateWaitingForInput(this.stateMachine.CurrentState); + this.inputAvailable.WaitOne(); + this.inputAvailable.Reset(); + // main thread should call Reset() on stateMachineNotBusy + + if (this.pleaseAbort) + { + break; + } + + this.stateMachine.ProcessInput(this.queuedInput); + + if (this.stateMachine.IsInFinalState) + { + break; + } + } + + this.stateMachineNotBusy.Set(); + } + + catch (Exception ex) + { + this.threadException = ex; + } + + finally + { + this.stateMachineNotBusy.Set(); + this.stateMachineInitialized.Set(); + this.stateMachine.NewState -= newStateHandler; + this.stateMachine.StateProgress -= stateProgressHandler; + OnStateMachineFinished(); + } + } + + public void Start() + { + if (this.isStarted) + { + throw new InvalidOperationException("State machine thread is already executing"); + } + + this.isStarted = true; + + this.stateMachineThread = new Thread(new ThreadStart(StateMachineThread)); + this.stateMachineInitialized.Reset(); + this.stateMachineThread.Start(); + this.stateMachineInitialized.WaitOne(); + } + + public void ProcessInput(object input) + { + this.stateMachineNotBusy.WaitOne(); + this.stateMachineNotBusy.Reset(); + this.queuedInput = input; + this.inputAvailable.Set(); + } + + public void Abort() + { + if (this.disposed) + { + return; + } + + this.pleaseAbort = true; + + State currentState2 = this.stateMachine.CurrentState; + if (currentState2 != null && currentState2.CanAbort) + { + this.stateMachine.CurrentState.Abort(); + } + + this.stateMachineNotBusy.WaitOne(); + this.inputAvailable.Set(); + this.stateMachineThread.Join(); + + if (this.threadException != null) + { + throw new WorkerThreadException("State machine thread threw an exception", this.threadException); + } + } + + public StateMachineExecutor(StateMachine stateMachine) + { + this.stateMachine = stateMachine; + } + + ~StateMachineExecutor() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + Abort(); + + if (this.stateMachineInitialized != null) + { + this.stateMachineInitialized.Close(); + this.stateMachineInitialized = null; + } + + if (this.stateMachineNotBusy != null) + { + this.stateMachineNotBusy.Close(); + this.stateMachineNotBusy = null; + } + + if (this.inputAvailable != null) + { + this.inputAvailable.Close(); + this.inputAvailable = null; + } + } + + this.disposed = true; + } + } +} diff --git a/src/Core/StylusEventArgs.cs b/src/Core/StylusEventArgs.cs new file mode 100644 index 0000000..b416b1b --- /dev/null +++ b/src/Core/StylusEventArgs.cs @@ -0,0 +1,93 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// This class contains information about the pointer's position, + /// buttons, wheel rotation, and pressure, if applicable. + /// + public sealed class StylusEventArgs + : MouseEventArgs + { + private PointF position; + public float Fx + { + get + { + return position.X; + } + } + + public float Fy + { + get + { + return position.Y; + } + } + + private float pressure; + public float Pressure + { + get + { + return pressure; + } + } + + /// + /// Constructs a new StylusEventArgs object + /// + /// Which button was pressed + /// The number of times the button was pressed + /// The horizontal position of the pointer + /// The vertical position of the pointer + /// The number of detents the wheel has rotated, signed + public StylusEventArgs(MouseEventArgs e) + : base(e.Button, e.Clicks, e.X, e.Y, e.Delta) + { + this.position = new PointF(e.X, e.Y); + this.pressure = 1.0f; + } + + /// + /// Constructs a new StylusEventArgs object + /// + /// Which button was pressed + /// The number of times the button was pressed + /// The horizontal position of the pointer + /// The vertical position of the pointer + /// The number of detents the wheel has rotated, signed + public StylusEventArgs(MouseButtons button, int clicks, float fx, float fy, int delta) + : this(button, clicks, fx, fy, delta, 1.0f) + { + } + + /// + /// Constructs a new StylusEventArgs object + /// + /// Which button was pressed + /// The number of times the button was pressed + /// The horizontal position of the pointer + /// The vertical position of the pointer + /// The number of detents the wheel has rotated, signed + /// The force applied with the pointer, as a fraction of the maximum + public StylusEventArgs(MouseButtons button, int clicks, float fx, float fy, int delta, float pressure) + : base(button, clicks, (int)Math.Round(fx), (int)Math.Round(fy), delta) + { + this.position = new PointF(fx, fy); + this.pressure = pressure; + } + } +} diff --git a/src/Core/Surface.cs b/src/Core/Surface.cs new file mode 100644 index 0000000..9723503 --- /dev/null +++ b/src/Core/Surface.cs @@ -0,0 +1,2026 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; + +namespace PaintDotNet +{ + /// + /// This is our Surface type. We allocate our own blocks of memory for this, + /// and provide ways to create a GDI+ Bitmap object that aliases our surface. + /// That way we can do everything fast, in memory and have complete control, + /// and still have the ability to use GDI+ for drawing and rendering where + /// appropriate. + /// + [Serializable] + public sealed class Surface + : IDisposable, + ICloneable + { + private MemoryBlock scan0; + private int width; + private int height; + private int stride; + private bool disposed = false; + + public bool IsDisposed + { + get + { + return this.disposed; + } + } + + /// + /// Gets a MemoryBlock which is the buffer holding the pixels associated + /// with this Surface. + /// + public MemoryBlock Scan0 + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("Surface"); + } + + return this.scan0; + } + } + + /// + /// Gets the width, in pixels, of this Surface. + /// + /// + /// This property will never throw an ObjectDisposedException. + /// + public int Width + { + get + { + return this.width; + } + } + + /// + /// Gets the height, in pixels, of this Surface. + /// + /// + /// This property will never throw an ObjectDisposedException. + /// + public int Height + { + get + { + return this.height; + } + } + + /// + /// Gets the stride, in bytes, for this Surface. + /// + /// + /// Stride is defined as the number of bytes between the beginning of a row and + /// the beginning of the next row. Thus, in loose C notation: stride = (byte *)&this[0, 1] - (byte *)&this[0, 0]. + /// Stride will always be equal to or greater than Width * ColorBgra.SizeOf. + /// This property will never throw an ObjectDisposedException. + /// + public int Stride + { + get + { + return this.stride; + } + } + + /// + /// Gets the size, in pixels, of this Surface. + /// + /// + /// This is a convenience function that creates a new Size instance based + /// on the values of the Width and Height properties. + /// This property will never throw an ObjectDisposedException. + /// + public Size Size + { + get + { + return new Size(this.width, this.height); + } + } + + /// + /// Gets the GDI+ PixelFormat of this Surface. + /// + /// + /// This property always returns PixelFormat.Format32bppArgb. + /// This property will never throw an ObjectDisposedException. + /// + public PixelFormat PixelFormat + { + get + { + return PixelFormat.Format32bppArgb; + } + } + + /// + /// Gets the bounds of this Surface, in pixels. + /// + /// + /// This is a convenience function that returns Rectangle(0, 0, Width, Height). + /// This property will never throw an ObjectDisposedException. + /// + public Rectangle Bounds + { + get + { + return new Rectangle(0, 0, width, height); + } + } + + /// + /// Creates a new instance of the Surface class. + /// + /// The size, in pixels, of the new Surface. + public Surface(Size size) + : this(size.Width, size.Height) + { + } + + /// + /// Creates a new instance of the Surface class. + /// + /// The width, in pixels, of the new Surface. + /// The height, in pixels, of the new Surface. + public Surface(int width, int height) + { + int stride; + long bytes; + + try + { + stride = checked(width * ColorBgra.SizeOf); + bytes = (long)height * (long)stride; + } + + catch (OverflowException ex) + { + throw new OutOfMemoryException("Dimensions are too large - not enough memory, width=" + width.ToString() + ", height=" + height.ToString(), ex); + } + + MemoryBlock scan0 = new MemoryBlock(width, height); + Create(width, height, stride, scan0); + } + + /// + /// Creates a new instance of the Surface class that reuses a block of memory that was previously allocated. + /// + /// The width, in pixels, for the Surface. + /// The height, in pixels, for the Surface. + /// The stride, in bytes, for the Surface. + /// The MemoryBlock to use. The beginning of this buffer defines the upper left (0, 0) pixel of the Surface. + private Surface(int width, int height, int stride, MemoryBlock scan0) + { + Create(width, height, stride, scan0); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "width")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "height")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "stride")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "scan0")] + private void Create(int width, int height, int stride, MemoryBlock scan0) + { + this.width = width; + this.height = height; + this.stride = stride; + this.scan0 = scan0; + } + + ~Surface() + { + Dispose(false); + } + + /// + /// Creates a Surface that aliases a portion of this Surface. + /// + /// The portion of this Surface that will be aliased. + /// The upper left corner of the new Surface will correspond to the + /// upper left corner of this rectangle in the original Surface. + /// A Surface that aliases the requested portion of this Surface. + public Surface CreateWindow(Rectangle bounds) + { + return CreateWindow(bounds.X, bounds.Y, bounds.Width, bounds.Height); + } + + public Surface CreateWindow(int x, int y, int windowWidth, int windowHeight) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + if (windowHeight == 0) + { + throw new ArgumentOutOfRangeException("windowHeight", "must be greater than zero"); + } + + Rectangle original = this.Bounds; + Rectangle sub = new Rectangle(x, y, windowWidth, windowHeight); + Rectangle clipped = Rectangle.Intersect(original, sub); + + if (clipped != sub) + { + throw new ArgumentOutOfRangeException("bounds", new Rectangle(x, y, windowWidth, windowHeight), + "bounds parameters must be a subset of this Surface's bounds"); + } + + long offset = ((long)stride * (long)y) + ((long)ColorBgra.SizeOf * (long)x); + long length = ((windowHeight - 1) * (long)stride) + (long)windowWidth * (long)ColorBgra.SizeOf; + MemoryBlock block = new MemoryBlock(this.scan0, offset, length); + return new Surface(windowWidth, windowHeight, this.stride, block); + } + + /// + /// Gets the offset, in bytes, of the requested row from the start of the surface. + /// + /// The row. + /// The number of bytes between (0,0) and (0,y). + public long GetRowByteOffset(int y) + { + if (y < 0 || y >= height) + { + throw new ArgumentOutOfRangeException("y", "Out of bounds: y=" + y.ToString()); + } + + return (long)y * (long)stride; + } + + /// + /// Gets the offset, in bytes, of the requested row from the start of the surface. + /// + /// The row. + /// The number of bytes between (0,0) and (0,y) + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetRowByteOffset(). + /// + public unsafe long GetRowByteOffsetUnchecked(int y) + { +#if DEBUG + if (y < 0 || y >= this.height) + { + Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); + } +#endif + + return (long)y * (long)stride; + } + + /// + /// Gets a pointer to the beginning of the requested row in the surface. + /// + /// The row + /// A pointer that references (0,y) in this surface. + /// Since this returns a pointer, it is potentially unsafe to use. + public unsafe ColorBgra *GetRowAddress(int y) + { + return (ColorBgra *)(((byte *)scan0.VoidStar) + GetRowByteOffset(y)); + } + + /// + /// Gets a pointer to the beginning of the requested row in the surface. + /// + /// The row + /// A pointer that references (0,y) in this surface. + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetRowAddress(). + /// + public unsafe ColorBgra *GetRowAddressUnchecked(int y) + { +#if DEBUG + if (y < 0 || y >= this.height) + { + Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); + } +#endif + + return (ColorBgra *)(((byte *)scan0.VoidStar) + GetRowByteOffsetUnchecked(y)); + } + + /// + /// Gets the number of bytes from the beginning of a row to the requested column. + /// + /// The column. + /// + /// The number of bytes between (0,n) and (x,n) where n is in the range [0, Height). + /// + public long GetColumnByteOffset(int x) + { + if (x < 0 || x >= this.width) + { + throw new ArgumentOutOfRangeException("x", x, "Out of bounds"); + } + + return (long)x * (long)ColorBgra.SizeOf; + } + + /// + /// Gets the number of bytes from the beginning of a row to the requested column. + /// + /// The column. + /// + /// The number of bytes between (0,n) and (x,n) where n is in the range [0, Height). + /// + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetColumnByteOffset(). + /// + public long GetColumnByteOffsetUnchecked(int x) + { +#if DEBUG + if (x < 0 || x >= this.width) + { + Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); + } +#endif + + return (long)x * (long)ColorBgra.SizeOf; + } + + /// + /// Gets the number of bytes from the beginning of the surface's buffer to + /// the requested point. + /// + /// The x offset. + /// The y offset. + /// + /// The number of bytes between (0,0) and (x,y). + /// + public long GetPointByteOffset(int x, int y) + { + return GetRowByteOffset(y) + GetColumnByteOffset(x); + } + + /// + /// Gets the number of bytes from the beginning of the surface's buffer to + /// the requested point. + /// + /// The x offset. + /// The y offset. + /// + /// The number of bytes between (0,0) and (x,y). + /// + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetPointByteOffset(). + /// + public long GetPointByteOffsetUnchecked(int x, int y) + { +#if DEBUG + if (x < 0 || x >= this.width) + { + Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); + } + + if (y < 0 || y >= this.height) + { + Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); + } +#endif + + return GetRowByteOffsetUnchecked(y) + GetColumnByteOffsetUnchecked(x); + } + + /// + /// Gets the color at a specified point in the surface. + /// + /// The x offset. + /// The y offset. + /// The color at the requested location. + public ColorBgra GetPoint(int x, int y) + { + return this[x, y]; + } + + /// + /// Gets the color at a specified point in the surface. + /// + /// The x offset. + /// The y offset. + /// The color at the requested location. + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetPoint(). + /// + public unsafe ColorBgra GetPointUnchecked(int x, int y) + { +#if DEBUG + if (x < 0 || x >= this.width) + { + Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); + } + + if (y < 0 || y >= this.height) + { + Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); + } +#endif + + return *(x + (ColorBgra *)(((byte *)scan0.VoidStar) + (y * stride))); + } + + /// + /// Gets the color at a specified point in the surface. + /// + /// The point to retrieve. + /// The color at the requested location. + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetPoint(). + /// + public unsafe ColorBgra GetPointUnchecked(Point pt) + { + return GetPointUnchecked(pt.X, pt.Y); + } + + /// + /// Gets the address in memory of the requested point. + /// + /// The x offset. + /// The y offset. + /// A pointer to the requested point in the surface. + /// Since this method returns a pointer, it is potentially unsafe to use. + public unsafe ColorBgra *GetPointAddress(int x, int y) + { + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException("x", "Out of bounds: x=" + x.ToString()); + } + + return GetRowAddress(y) + x; + } + + /// + /// Gets the address in memory of the requested point. + /// + /// The point to retrieve. + /// A pointer to the requested point in the surface. + /// Since this method returns a pointer, it is potentially unsafe to use. + public unsafe ColorBgra *GetPointAddress(Point pt) + { + return GetPointAddress(pt.X, pt.Y); + } + + /// + /// Gets the address in memory of the requested point. + /// + /// The x offset. + /// The y offset. + /// A pointer to the requested point in the surface. + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetPointAddress(). + /// + public unsafe ColorBgra *GetPointAddressUnchecked(int x, int y) + { +#if DEBUG + if (x < 0 || x >= this.width) + { + Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); + } + + if (y < 0 || y >= this.height) + { + Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); + } +#endif + + return unchecked(x + (ColorBgra *)(((byte *)scan0.VoidStar) + (y * stride))); + } + + /// + /// Gets the address in memory of the requested point. + /// + /// The point to retrieve. + /// A pointer to the requested point in the surface. + /// + /// This method does not do any bounds checking and is potentially unsafe to use, + /// but faster than GetPointAddress(). + /// + public unsafe ColorBgra *GetPointAddressUnchecked(Point pt) + { + return GetPointAddressUnchecked(pt.X, pt.Y); + } + + /// + /// Gets a MemoryBlock that references the row requested. + /// + /// The row. + /// A MemoryBlock that gives access to the bytes in the specified row. + /// This method is the safest to use for direct memory access to a row's pixel data. + public MemoryBlock GetRow(int y) + { + return new MemoryBlock(scan0, GetRowByteOffset(y), (long)width * (long)ColorBgra.SizeOf); + } + + public bool IsContiguousMemoryRegion(Rectangle bounds) + { + bool oneRow = (bounds.Height == 1); + bool manyRows = (this.Stride == (this.Width * ColorBgra.SizeOf) && + this.Width == bounds.Width); + + return oneRow || manyRows; + } + + /// + /// Determines if the requested pixel coordinate is within bounds. + /// + /// The x coordinate. + /// The y coordinate. + /// true if (x,y) is in bounds, false if it's not. + public bool IsVisible(int x, int y) + { + return x >= 0 && x < width && y >= 0 && y < height; + } + + /// + /// Determines if the requested pixel coordinate is within bounds. + /// + /// The coordinate. + /// true if (pt.X, pt.Y) is in bounds, false if it's not. + public bool IsVisible(Point pt) + { + return IsVisible(pt.X, pt.Y); + } + + /// + /// Determines if the requested row offset is within bounds. + /// + /// The row. + /// true if y >= 0 and y < height, otherwise false + public bool IsRowVisible(int y) + { + return y >= 0 && y < Height; + } + + /// + /// Determines if the requested column offset is within bounds. + /// + /// The column. + /// true if x >= 0 and x < width, otherwise false. + public bool IsColumnVisible(int x) + { + return x >= 0 && x < Width; + } + + [Obsolete("Use GetBilinearSampleWrapped(float, float) instead")] + public ColorBgra GetBilinearSample(float x, float y, bool wrap) + { + return GetBilinearSampleWrapped(x, y); + } + + public ColorBgra GetBilinearSampleWrapped(float x, float y) + { + if (!Utility.IsNumber(x) || !Utility.IsNumber(y)) + { + return ColorBgra.Transparent; + } + + float u = x; + float v = y; + + unchecked + { + int iu = (int)Math.Floor(u); + uint sxfrac = (uint)(256 * (u - (float)iu)); + uint sxfracinv = 256 - sxfrac; + + int iv = (int)Math.Floor(v); + uint syfrac = (uint)(256 * (v - (float)iv)); + uint syfracinv = 256 - syfrac; + + uint wul = (uint)(sxfracinv * syfracinv); + uint wur = (uint)(sxfrac * syfracinv); + uint wll = (uint)(sxfracinv * syfrac); + uint wlr = (uint)(sxfrac * syfrac); + + int sx = iu; + if (sx < 0) + { + sx = (width - 1) + ((sx + 1) % width); + } + else if (sx > (width - 1)) + { + sx = sx % width; + } + + int sy = iv; + if (sy < 0) + { + sy = (height - 1) + ((sy + 1) % height); + } + else if (sy > (height - 1)) + { + sy = sy % height; + } + + int sleft = sx; + int sright; + + if (sleft == (width - 1)) + { + sright = 0; + } + else + { + sright = sleft + 1; + } + + int stop = sy; + int sbottom; + + if (stop == (height - 1)) + { + sbottom = 0; + } + else + { + sbottom = stop + 1; + } + + ColorBgra cul = GetPointUnchecked(sleft, stop); + ColorBgra cur = GetPointUnchecked(sright, stop); + ColorBgra cll = GetPointUnchecked(sleft, sbottom); + ColorBgra clr = GetPointUnchecked(sright, sbottom); + + ColorBgra c = ColorBgra.BlendColors4W16IP(cul, wul, cur, wur, cll, wll, clr, wlr); + + return c; + } + } + + [Obsolete("Use GetBilinearSample(float, float) instead")] + public unsafe ColorBgra GetBilinearSample2(float x, float y) + { + return GetBilinearSample(x, y); + } + + public unsafe ColorBgra GetBilinearSample(float x, float y) + { + if (!Utility.IsNumber(x) || !Utility.IsNumber(y)) + { + return ColorBgra.Transparent; + } + + float u = x; + float v = y; + + if (u >= 0 && v >= 0 && u < width && v < height) + { + unchecked + { + int iu = (int)Math.Floor(u); + uint sxfrac = (uint)(256 * (u - (float)iu)); + uint sxfracinv = 256 - sxfrac; + + int iv = (int)Math.Floor(v); + uint syfrac = (uint)(256 * (v - (float)iv)); + uint syfracinv = 256 - syfrac; + + uint wul = (uint)(sxfracinv * syfracinv); + uint wur = (uint)(sxfrac * syfracinv); + uint wll = (uint)(sxfracinv * syfrac); + uint wlr = (uint)(sxfrac * syfrac); + + int sx = iu; + int sy = iv; + int sleft = sx; + int sright; + + if (sleft == (width - 1)) + { + sright = sleft; + } + else + { + sright = sleft + 1; + } + + int stop = sy; + int sbottom; + + if (stop == (height - 1)) + { + sbottom = stop; + } + else + { + sbottom = stop + 1; + } + + ColorBgra *cul = GetPointAddressUnchecked(sleft, stop); + ColorBgra *cur = cul + (sright - sleft); + ColorBgra *cll = GetPointAddressUnchecked(sleft, sbottom); + ColorBgra *clr = cll + (sright - sleft); + + ColorBgra c = ColorBgra.BlendColors4W16IP(*cul, wul, *cur, wur, *cll, wll, *clr, wlr); + return c; + } + } + else + { + return ColorBgra.FromUInt32(0); + } + } + + [Obsolete("Use GetBilinearSampleClamped(float, float) instead")] + public unsafe ColorBgra GetBilinearSample2Clamped(float x, float y) + { + return GetBilinearSampleClamped(x, y); + } + + public unsafe ColorBgra GetBilinearSampleClamped(float x, float y) + { + if (!Utility.IsNumber(x) || !Utility.IsNumber(y)) + { + return ColorBgra.Transparent; + } + + float u = x; + float v = y; + + if (u < 0) + { + u = 0; + } + else if (u > this.Width - 1) + { + u = this.Width - 1; + } + + if (v < 0) + { + v = 0; + } + else if (v > this.Height - 1) + { + v = this.Height - 1; + } + + unchecked + { + int iu = (int)Math.Floor(u); + uint sxfrac = (uint)(256 * (u - (float)iu)); + uint sxfracinv = 256 - sxfrac; + + int iv = (int)Math.Floor(v); + uint syfrac = (uint)(256 * (v - (float)iv)); + uint syfracinv = 256 - syfrac; + + uint wul = (uint)(sxfracinv * syfracinv); + uint wur = (uint)(sxfrac * syfracinv); + uint wll = (uint)(sxfracinv * syfrac); + uint wlr = (uint)(sxfrac * syfrac); + + int sx = iu; + int sy = iv; + int sleft = sx; + int sright; + + if (sleft == (width - 1)) + { + sright = sleft; + } + else + { + sright = sleft + 1; + } + + int stop = sy; + int sbottom; + + if (stop == (height - 1)) + { + sbottom = stop; + } + else + { + sbottom = stop + 1; + } + + ColorBgra *cul = GetPointAddressUnchecked(sleft, stop); + ColorBgra *cur = cul + (sright - sleft); + ColorBgra *cll = GetPointAddressUnchecked(sleft, sbottom); + ColorBgra *clr = cll + (sright - sleft); + + ColorBgra c = ColorBgra.BlendColors4W16IP(*cul, wul, *cur, wur, *cll, wll, *clr, wlr); + return c; + } + } + + /// + /// Gets or sets the pixel value at the requested offset. + /// + /// + /// This property is implemented with correctness and error checking in mind. If performance + /// is a concern, do not use it. + /// + public ColorBgra this[int x, int y] + { + get + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + if (x < 0 || y < 0 || x >= this.width || y >= this.height) + { + throw new ArgumentOutOfRangeException("(x,y)", new Point(x, y), "Coordinates out of range, max=" + new Size(width - 1, height - 1).ToString()); + } + + unsafe + { + return *GetPointAddressUnchecked(x, y); + } + } + + set + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + if (x < 0 || y < 0 || x >= this.width || y >= this.height) + { + throw new ArgumentOutOfRangeException("(x,y)", new Point(x, y), "Coordinates out of range, max=" + new Size(width - 1, height - 1).ToString()); + } + + unsafe + { + *GetPointAddressUnchecked(x, y) = value; + } + } + } + + /// + /// Gets or sets the pixel value at the requested offset. + /// + /// + /// This property is implemented with correctness and error checking in mind. If performance + /// is a concern, do not use it. + /// + public ColorBgra this[Point pt] + { + get + { + return this[pt.X, pt.Y]; + } + + set + { + this[pt.X, pt.Y] = value; + } + } + + /// + /// Helper function. Same as calling CreateAliasedBounds(Bounds). + /// + /// A GDI+ Bitmap that aliases the entire Surface. + public Bitmap CreateAliasedBitmap() + { + return CreateAliasedBitmap(this.Bounds); + } + + /// + /// Helper function. Same as calling CreateAliasedBounds(bounds, true). + /// + /// A GDI+ Bitmap that aliases the entire Surface. + public Bitmap CreateAliasedBitmap(Rectangle bounds) + { + return CreateAliasedBitmap(bounds, true); + } + + /// + /// Creates a GDI+ Bitmap object that aliases the same memory that this Surface does. + /// Then you can use GDI+ to draw on to this surface. + /// Note: Since the Bitmap does not hold a reference to this Surface object, nor to + /// the MemoryBlock that it contains, you must hold a reference to the Surface object + /// for as long as you wish to use the aliased Bitmap. Otherwise the memory may be + /// freed and the Bitmap will look corrupt or cause other errors. You may use the + /// RenderArgs class to help manage this lifetime instead. + /// + /// The rectangle of interest within this Surface that you wish to alias. + /// If true, the returned bitmap will use PixelFormat.Format32bppArgb. + /// If false, the returned bitmap will use PixelFormat.Format32bppRgb. + /// A GDI+ Bitmap that aliases the requested portion of the Surface. + /// bounds was not entirely within the boundaries of the Surface + /// This Surface instance is already disposed. + public Bitmap CreateAliasedBitmap(Rectangle bounds, bool alpha) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + if (bounds.IsEmpty) + { + throw new ArgumentOutOfRangeException(); + } + + Rectangle clipped = Rectangle.Intersect(this.Bounds, bounds); + + if (clipped != bounds) + { + throw new ArgumentOutOfRangeException(); + } + + unsafe + { + return new Bitmap(bounds.Width, bounds.Height, stride, alpha ? this.PixelFormat : PixelFormat.Format32bppRgb, + new IntPtr((void *)((byte *)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y)))); + } + } + + /// + /// Creates a new Surface and copies the pixels from a Bitmap to it. + /// + /// The Bitmap to duplicate. + /// A new Surface that is the same size as the given Bitmap and that has the same pixel values. + public static Surface CopyFromBitmap(Bitmap bitmap) + { + Surface surface = new Surface(bitmap.Width, bitmap.Height); + BitmapData bd = bitmap.LockBits(surface.Bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + + unsafe + { + for (int y = 0; y < bd.Height; ++y) + { + Memory.Copy((void *)surface.GetRowAddress(y), + (byte *)bd.Scan0.ToPointer() + (y * bd.Stride), (ulong)bd.Width * ColorBgra.SizeOf); + } + } + + bitmap.UnlockBits(bd); + return surface; + } + + /// + /// Copies the contents of the given surface to the upper left corner of this surface. + /// + /// The surface to copy pixels from. + /// + /// The source surface does not need to have the same dimensions as this surface. Clipping + /// will be handled automatically. No resizing will be done. + /// + public void CopySurface(Surface source) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + if (this.stride == source.stride && + (this.width * ColorBgra.SizeOf) == this.stride && + this.width == source.width && + this.height == source.height) + { + unsafe + { + Memory.Copy(this.scan0.VoidStar, + source.scan0.VoidStar, + ((ulong)(height - 1) * (ulong)stride) + ((ulong)width * (ulong)ColorBgra.SizeOf)); + } + } + else + { + int copyWidth = Math.Min(width, source.width); + int copyHeight = Math.Min(height, source.height); + + unsafe + { + for (int y = 0; y < copyHeight; ++y) + { + Memory.Copy(GetRowAddressUnchecked(y), source.GetRowAddressUnchecked(y), (ulong)copyWidth * (ulong)ColorBgra.SizeOf); + } + } + } + } + + /// + /// Copies the contents of the given surface to a location within this surface. + /// + /// The surface to copy pixels from. + /// + /// The offset within this surface to start copying pixels to. This will map to (0,0) in the source. + /// + /// + /// The source surface does not need to have the same dimensions as this surface. Clipping + /// will be handled automatically. No resizing will be done. + /// + public void CopySurface(Surface source, Point dstOffset) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + Rectangle dstRect = new Rectangle(dstOffset, source.Size); + dstRect.Intersect(Bounds); + + if (dstRect.Width == 0 || dstRect.Height == 0) + { + return; + } + + Point sourceOffset = new Point(dstRect.Location.X - dstOffset.X, dstRect.Location.Y - dstOffset.Y); + Rectangle sourceRect = new Rectangle(sourceOffset, dstRect.Size); + Surface sourceWindow = source.CreateWindow(sourceRect); + Surface dstWindow = this.CreateWindow(dstRect); + dstWindow.CopySurface(sourceWindow); + + dstWindow.Dispose(); + sourceWindow.Dispose(); + } + + /// + /// Copies the contents of the given surface to the upper left of this surface. + /// + /// The surface to copy pixels from. + /// + /// The region of the source to copy from. The upper left of this rectangle + /// will be mapped to (0,0) on this surface. + /// The source surface does not need to have the same dimensions as this surface. Clipping + /// will be handled automatically. No resizing will be done. + /// + public void CopySurface(Surface source, Rectangle sourceRoi) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + sourceRoi.Intersect(source.Bounds); + int copiedWidth = Math.Min(this.width, sourceRoi.Width); + int copiedHeight = Math.Min(this.Height, sourceRoi.Height); + + if (copiedWidth == 0 || copiedHeight == 0) + { + return; + } + + using (Surface src = source.CreateWindow(sourceRoi)) + { + CopySurface(src); + } + } + + /// + /// Copies a rectangular region of the given surface to a specific location on this surface. + /// + /// The surface to copy pixels from. + /// The location on this surface to start copying pixels to. + /// The region of the source surface to copy pixels from. + /// + /// sourceRoi.Location will be mapped to dstOffset.Location. + /// The source surface does not need to have the same dimensions as this surface. Clipping + /// will be handled automatically. No resizing will be done. + /// + public void CopySurface(Surface source, Point dstOffset, Rectangle sourceRoi) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + Rectangle dstRoi = new Rectangle(dstOffset, sourceRoi.Size); + dstRoi.Intersect(Bounds); + + if (dstRoi.Height == 0 || dstRoi.Width == 0) + { + return; + } + + sourceRoi.X += dstRoi.X - dstOffset.X; + sourceRoi.Y += dstRoi.Y - dstOffset.Y; + sourceRoi.Width = dstRoi.Width; + sourceRoi.Height = dstRoi.Height; + + using (Surface src = source.CreateWindow(sourceRoi)) + { + CopySurface(src, dstOffset); + } + } + + /// + /// Copies a region of the given surface to this surface. + /// + /// The surface to copy pixels from. + /// The region to clip copying to. + /// + /// The upper left corner of the source surface will be mapped to the upper left of this + /// surface, and only those pixels that are defined by the region will be copied. + /// The source surface does not need to have the same dimensions as this surface. Clipping + /// will be handled automatically. No resizing will be done. + /// + public void CopySurface(Surface source, PdnRegion region) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + Rectangle[] scans = region.GetRegionScansReadOnlyInt(); + for (int i = 0; i < scans.Length; ++i) + { + Rectangle rect = scans[i]; + + rect.Intersect(this.Bounds); + rect.Intersect(source.Bounds); + + if (rect.Width == 0 || rect.Height == 0) + { + continue; + } + + unsafe + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra *dst = this.GetPointAddressUnchecked(rect.Left, y); + ColorBgra *src = source.GetPointAddressUnchecked(rect.Left, y); + Memory.Copy(dst, src, (ulong)rect.Width * (ulong)ColorBgra.SizeOf); + } + } + } + } + + /// + /// Copies a region of the given surface to this surface. + /// + /// The surface to copy pixels from. + /// The region to clip copying to. + /// + /// The upper left corner of the source surface will be mapped to the upper left of this + /// surface, and only those pixels that are defined by the region will be copied. + /// The source surface does not need to have the same dimensions as this surface. Clipping + /// will be handled automatically. No resizing will be done. + /// + public void CopySurface(Surface source, Rectangle[] region, int startIndex, int length) + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle rect = region[i]; + + rect.Intersect(this.Bounds); + rect.Intersect(source.Bounds); + + if (rect.Width == 0 || rect.Height == 0) + { + continue; + } + + unsafe + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* dst = this.GetPointAddressUnchecked(rect.Left, y); + ColorBgra* src = source.GetPointAddressUnchecked(rect.Left, y); + Memory.Copy(dst, src, (ulong)rect.Width * (ulong)ColorBgra.SizeOf); + } + } + } + } + + public void CopySurface(Surface source, Rectangle[] region) + { + CopySurface(source, region, 0, region.Length); + } + + object ICloneable.Clone() + { + return Clone(); + } + + /// + /// Creates a new surface with the same dimensions and pixel values as this one. + /// + /// A new surface that is a clone of the current one. + public Surface Clone() + { + if (disposed) + { + throw new ObjectDisposedException("Surface"); + } + + Surface ret = new Surface(this.Size); + ret.CopySurface(this); + return ret; + } + + /// + /// Clears the surface to all-white (BGRA = [255,255,255,255]). + /// + public void Clear() + { + Clear(ColorBgra.FromBgra(255, 255, 255, 255)); + } + + /// + /// Clears the surface to the given color value. + /// + /// The color value to fill the surface with. + public void Clear(ColorBgra color) + { + new UnaryPixelOps.Constant(color).Apply(this, this.Bounds); + } + + [Obsolete("Use Clear(Rectangle, ColorBgra) instead")] + public void Clear(ColorBgra color, Rectangle rect) + { + Clear(rect, color); + } + + /// + /// Clears the given rectangular region within the surface to the given color value. + /// + /// The color value to fill the rectangular region with. + /// The rectangular region to fill. + public void Clear(Rectangle rect, ColorBgra color) + { + Rectangle rect2 = Rectangle.Intersect(this.Bounds, rect); + + if (rect2 != rect) + { + throw new ArgumentOutOfRangeException("rectangle is out of bounds"); + } + + new UnaryPixelOps.Constant(color).Apply(this, rect); + } + + public void Clear(PdnRegion region, ColorBgra color) + { + foreach (Rectangle rect in region.GetRegionScansReadOnlyInt()) + { + Clear(rect, color); + } + } + + public void ClearWithCheckboardPattern() + { + unsafe + { + for (int y = 0; y < this.height; ++y) + { + ColorBgra* dstPtr = GetRowAddressUnchecked(y); + + for (int x = 0; x < this.width; ++x) + { + byte v = (byte)((((x ^ y) & 8) * 8) + 191); + *dstPtr = ColorBgra.FromBgra(v, v, v, 255); + ++dstPtr; + } + } + } + } + + /// + /// Fits the source surface to this surface using super sampling. If the source surface is less wide + /// or less tall than this surface (i.e. magnification), bicubic resampling is used instead. If either + /// the source or destination has a dimension that is only 1 pixel, nearest neighbor is used. + /// + /// The Surface to read pixels from. + /// This method was implemented with correctness, not performance, in mind. + public void SuperSamplingFitSurface(Surface source) + { + SuperSamplingFitSurface(source, this.Bounds); + } + + /// + /// Fits the source surface to this surface using super sampling. If the source surface is less wide + /// or less tall than this surface (i.e. magnification), bicubic resampling is used instead. If either + /// the source or destination has a dimension that is only 1 pixel, nearest neighbor is used. + /// + /// The surface to read pixels from. + /// The rectangle to clip rendering to. + /// This method was implemented with correctness, not performance, in mind. + public void SuperSamplingFitSurface(Surface source, Rectangle dstRoi) + { + if (source.Width == Width && source.Height == Height) + { + CopySurface(source); + } + else if (source.Width <= Width || source.Height <= Height) + { + if (source.width < 2 || source.height < 2 || this.width < 2 || this.height < 2) + { + this.NearestNeighborFitSurface(source, dstRoi); + } + else + { + this.BicubicFitSurface(source, dstRoi); + } + } + else unsafe + { + Rectangle dstRoi2 = Rectangle.Intersect(dstRoi, this.Bounds); + + for (int dstY = dstRoi2.Top; dstY < dstRoi2.Bottom; ++dstY) + { + double srcTop = (double)(dstY * source.height) / (double)height; + double srcTopFloor = Math.Floor(srcTop); + double srcTopWeight = 1 - (srcTop - srcTopFloor); + int srcTopInt = (int)srcTopFloor; + + double srcBottom = (double)((dstY + 1) * source.height) / (double)height; + double srcBottomFloor = Math.Floor(srcBottom - 0.00001); + double srcBottomWeight = srcBottom - srcBottomFloor; + int srcBottomInt = (int)srcBottomFloor; + + ColorBgra *dstPtr = this.GetPointAddressUnchecked(dstRoi2.Left, dstY); + + for (int dstX = dstRoi2.Left; dstX < dstRoi2.Right; ++dstX) + { + double srcLeft = (double)(dstX * source.width) / (double)width; + double srcLeftFloor = Math.Floor(srcLeft); + double srcLeftWeight = 1 - (srcLeft - srcLeftFloor); + int srcLeftInt = (int)srcLeftFloor; + + double srcRight = (double)((dstX + 1) * source.width) / (double)width; + double srcRightFloor = Math.Floor(srcRight - 0.00001); + double srcRightWeight = srcRight - srcRightFloor; + int srcRightInt = (int)srcRightFloor; + + double blueSum = 0; + double greenSum = 0; + double redSum = 0; + double alphaSum = 0; + + // left fractional edge + ColorBgra *srcLeftPtr = source.GetPointAddressUnchecked(srcLeftInt, srcTopInt + 1); + + for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) + { + double a = srcLeftPtr->A; + blueSum += srcLeftPtr->B * srcLeftWeight * a; + greenSum += srcLeftPtr->G * srcLeftWeight * a; + redSum += srcLeftPtr->R * srcLeftWeight * a; + alphaSum += srcLeftPtr->A * srcLeftWeight; + srcLeftPtr = (ColorBgra*)((byte*)srcLeftPtr + source.stride); + } + + // right fractional edge + ColorBgra *srcRightPtr = source.GetPointAddressUnchecked(srcRightInt, srcTopInt + 1); + for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) + { + double a = srcRightPtr->A; + blueSum += srcRightPtr->B * srcRightWeight * a; + greenSum += srcRightPtr->G * srcRightWeight * a; + redSum += srcRightPtr->R * srcRightWeight * a; + alphaSum += srcRightPtr->A * srcRightWeight; + srcRightPtr = (ColorBgra*)((byte*)srcRightPtr + source.stride); + } + + // top fractional edge + ColorBgra *srcTopPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcTopInt); + for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) + { + double a = srcTopPtr->A; + blueSum += srcTopPtr->B * srcTopWeight * a; + greenSum += srcTopPtr->G * srcTopWeight * a; + redSum += srcTopPtr->R * srcTopWeight * a; + alphaSum += srcTopPtr->A * srcTopWeight; + ++srcTopPtr; + } + + // bottom fractional edge + ColorBgra *srcBottomPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcBottomInt); + for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) + { + double a = srcBottomPtr->A; + blueSum += srcBottomPtr->B * srcBottomWeight * a; + greenSum += srcBottomPtr->G * srcBottomWeight * a; + redSum += srcBottomPtr->R * srcBottomWeight * a; + alphaSum += srcBottomPtr->A * srcBottomWeight; + ++srcBottomPtr; + } + + // center area + for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) + { + ColorBgra *srcPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcY); + + for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) + { + double a = srcPtr->A; + blueSum += (double)srcPtr->B * a; + greenSum += (double)srcPtr->G * a; + redSum += (double)srcPtr->R * a; + alphaSum += (double)srcPtr->A; + ++srcPtr; + } + } + + // four corner pixels + ColorBgra srcTL = source.GetPoint(srcLeftInt, srcTopInt); + double srcTLA = srcTL.A; + blueSum += srcTL.B * (srcTopWeight * srcLeftWeight) * srcTLA; + greenSum += srcTL.G * (srcTopWeight * srcLeftWeight) * srcTLA; + redSum += srcTL.R * (srcTopWeight * srcLeftWeight) * srcTLA; + alphaSum += srcTL.A * (srcTopWeight * srcLeftWeight); + + ColorBgra srcTR = source.GetPoint(srcRightInt, srcTopInt); + double srcTRA = srcTR.A; + blueSum += srcTR.B * (srcTopWeight * srcRightWeight) * srcTRA; + greenSum += srcTR.G * (srcTopWeight * srcRightWeight) * srcTRA; + redSum += srcTR.R * (srcTopWeight * srcRightWeight) * srcTRA; + alphaSum += srcTR.A * (srcTopWeight * srcRightWeight); + + ColorBgra srcBL = source.GetPoint(srcLeftInt, srcBottomInt); + double srcBLA = srcBL.A; + blueSum += srcBL.B * (srcBottomWeight * srcLeftWeight) * srcBLA; + greenSum += srcBL.G * (srcBottomWeight * srcLeftWeight) * srcBLA; + redSum += srcBL.R * (srcBottomWeight * srcLeftWeight) * srcBLA; + alphaSum += srcBL.A * (srcBottomWeight * srcLeftWeight); + + ColorBgra srcBR = source.GetPoint(srcRightInt, srcBottomInt); + double srcBRA = srcBR.A; + blueSum += srcBR.B * (srcBottomWeight * srcRightWeight) * srcBRA; + greenSum += srcBR.G * (srcBottomWeight * srcRightWeight) * srcBRA; + redSum += srcBR.R * (srcBottomWeight * srcRightWeight) * srcBRA; + alphaSum += srcBR.A * (srcBottomWeight * srcRightWeight); + + double area = (srcRight - srcLeft) * (srcBottom - srcTop); + + double alpha = alphaSum / area; + double blue; + double green; + double red; + + if (alpha == 0) + { + blue = 0; + green = 0; + red = 0; + } + else + { + blue = blueSum / alphaSum; + green = greenSum / alphaSum; + red = redSum / alphaSum; + } + + // add 0.5 so that rounding goes in the direction we want it to + blue += 0.5; + green += 0.5; + red += 0.5; + alpha += 0.5; + + dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24); + ++dstPtr; + } + } + } + } + + /// + /// Fits the source surface to this surface using nearest neighbor resampling. + /// + /// The surface to read pixels from. + public void NearestNeighborFitSurface(Surface source) + { + NearestNeighborFitSurface(source, this.Bounds); + } + + /// + /// Fits the source surface to this surface using nearest neighbor resampling. + /// + /// The surface to read pixels from. + /// The rectangle to clip rendering to. + public void NearestNeighborFitSurface(Surface source, Rectangle dstRoi) + { + Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); + + unsafe + { + for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) + { + int srcY = (dstY * source.height) / height; + ColorBgra *srcRow = source.GetRowAddressUnchecked(srcY); + ColorBgra *dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY); + + for (int dstX = roi.Left; dstX < roi.Right; ++dstX) + { + int srcX = (dstX * source.width) / width; + *dstPtr = *(srcRow + srcX); + ++dstPtr; + } + } + } + } + + /// + /// Fits the source surface to this surface using bicubic interpolation. + /// + /// The Surface to read pixels from. + /// + /// This method was implemented with correctness, not performance, in mind. + /// Based on: "Bicubic Interpolation for Image Scaling" by Paul Bourke, + /// http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/ + /// + public void BicubicFitSurface(Surface source) + { + BicubicFitSurface(source, this.Bounds); + } + + private double CubeClamped(double x) + { + if (x >= 0) + { + return x * x * x; + } + else + { + return 0; + } + } + + /// + /// Implements R() as defined at http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/ + /// + private double R(double x) + { + return (CubeClamped(x + 2) - (4 * CubeClamped(x + 1)) + (6 * CubeClamped(x)) - (4 * CubeClamped(x - 1))) / 6; + } + + /// + /// Fits the source surface to this surface using bicubic interpolation. + /// + /// The Surface to read pixels from. + /// The rectangle to clip rendering to. + /// + /// This method was implemented with correctness, not performance, in mind. + /// Based on: "Bicubic Interpolation for Image Scaling" by Paul Bourke, + /// http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/ + /// + public void BicubicFitSurface(Surface source, Rectangle dstRoi) + { + float leftF = (1 * (float)(width - 1)) / (float)(source.width - 1); + float topF = (1 * (height - 1)) / (float)(source.height - 1); + float rightF = ((float)(source.width - 3) * (float)(width - 1)) / (float)(source.width - 1); + float bottomF = ((float)(source.Height - 3) * (float)(height - 1)) / (float)(source.height - 1); + + int left = (int)Math.Ceiling((double)leftF); + int top = (int)Math.Ceiling((double)topF); + int right = (int)Math.Floor((double)rightF); + int bottom = (int)Math.Floor((double)bottomF); + + Rectangle[] rois = new Rectangle[] { + Rectangle.FromLTRB(left, top, right, bottom), + new Rectangle(0, 0, width, top), + new Rectangle(0, top, left, height - top), + new Rectangle(right, top, width - right, height - top), + new Rectangle(left, bottom, right - left, height - bottom) + }; + + for (int i = 0; i < rois.Length; ++i) + { + rois[i].Intersect(dstRoi); + + if (rois[i].Width > 0 && rois[i].Height > 0) + { + if (i == 0) + { + BicubicFitSurfaceUnchecked(source, rois[i]); + } + else + { + BicubicFitSurfaceChecked(source, rois[i]); + } + } + } + } + + /// + /// Implements bicubic filtering with bounds checking at every pixel. + /// + private void BicubicFitSurfaceChecked(Surface source, Rectangle dstRoi) + { + if (this.width < 2 || this.height < 2 || source.width < 2 || source.height < 2) + { + SuperSamplingFitSurface(source, dstRoi); + } + else + { + unsafe + { + Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); + Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1)); + + IntPtr rColCacheIP = Memory.Allocate(4 * (ulong)roi.Width * (ulong)sizeof(double)); + double* rColCache = (double*)rColCacheIP.ToPointer(); + + // Precompute and then cache the value of R() for each column + for (int dstX = roi.Left; dstX < roi.Right; ++dstX) + { + double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); + double srcColumnFloor = Math.Floor(srcColumn); + double srcColumnFrac = srcColumn - srcColumnFloor; + int srcColumnInt = (int)srcColumn; + + for (int m = -1; m <= 2; ++m) + { + int index = (m + 1) + ((dstX - roi.Left) * 4); + double x = m - srcColumnFrac; + rColCache[index] = R(x); + } + } + + // Set this up so we can cache the R()'s for every row + double* rRowCache = stackalloc double[4]; + + for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) + { + double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1); + double srcRowFloor = (double)Math.Floor(srcRow); + double srcRowFrac = srcRow - srcRowFloor; + int srcRowInt = (int)srcRow; + ColorBgra *dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY); + + // Compute the R() values for this row + for (int n = -1; n <= 2; ++n) + { + double x = srcRowFrac - n; + rRowCache[n + 1] = R(x); + } + + // See Perf Note below + //int nFirst = Math.Max(-srcRowInt, -1); + //int nLast = Math.Min(source.height - srcRowInt - 1, 2); + + for (int dstX = roi.Left; dstX < roi.Right; dstX++) + { + double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); + double srcColumnFloor = Math.Floor(srcColumn); + double srcColumnFrac = srcColumn - srcColumnFloor; + int srcColumnInt = (int)srcColumn; + + double blueSum = 0; + double greenSum = 0; + double redSum = 0; + double alphaSum = 0; + double totalWeight = 0; + + // See Perf Note below + //int mFirst = Math.Max(-srcColumnInt, -1); + //int mLast = Math.Min(source.width - srcColumnInt - 1, 2); + + ColorBgra *srcPtr = source.GetPointAddressUnchecked(srcColumnInt - 1, srcRowInt - 1); + + for (int n = -1; n <= 2; ++n) + { + int srcY = srcRowInt + n; + + for (int m = -1; m <= 2; ++m) + { + // Perf Note: It actually benchmarks faster on my system to do + // a bounds check for every (m,n) than it is to limit the loop + // to nFirst-Last and mFirst-mLast. + // I'm leaving the code above, albeit commented out, so that + // benchmarking between these two can still be performed. + if (source.IsVisible(srcColumnInt + m, srcY)) + { + double w0 = rColCache[(m + 1) + (4 * (dstX - roi.Left))]; + double w1 = rRowCache[n + 1]; + double w = w0 * w1; + + blueSum += srcPtr->B * w * srcPtr->A; + greenSum += srcPtr->G * w * srcPtr->A; + redSum += srcPtr->R * w * srcPtr->A; + alphaSum += srcPtr->A * w; + + totalWeight += w; + } + + ++srcPtr; + } + + srcPtr = (ColorBgra *)((byte *)(srcPtr - 4) + source.stride); + } + + double alpha = alphaSum / totalWeight; + double blue; + double green; + double red; + + if (alpha == 0) + { + blue = 0; + green = 0; + red = 0; + } + else + { + blue = blueSum / alphaSum; + green = greenSum / alphaSum; + red = redSum / alphaSum; + + // add 0.5 to ensure truncation to uint results in rounding + alpha += 0.5; + blue += 0.5; + green += 0.5; + red += 0.5; + } + + dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24); + ++dstPtr; + } // for (dstX... + } // for (dstY... + + Memory.Free(rColCacheIP); + } // unsafe + } + } + + /// + /// Implements bicubic filtering with NO bounds checking at any pixel. + /// + public void BicubicFitSurfaceUnchecked(Surface source, Rectangle dstRoi) + { + if (this.width < 2 || this.height < 2 || source.width < 2 || source.height < 2) + { + SuperSamplingFitSurface(source, dstRoi); + } + else + { + unsafe + { + Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); + Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1)); + + IntPtr rColCacheIP = Memory.Allocate(4 * (ulong)roi.Width * (ulong)sizeof(double)); + double* rColCache = (double*)rColCacheIP.ToPointer(); + + // Precompute and then cache the value of R() for each column + for (int dstX = roi.Left; dstX < roi.Right; ++dstX) + { + double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); + double srcColumnFloor = Math.Floor(srcColumn); + double srcColumnFrac = srcColumn - srcColumnFloor; + int srcColumnInt = (int)srcColumn; + + for (int m = -1; m <= 2; ++m) + { + int index = (m + 1) + ((dstX - roi.Left) * 4); + double x = m - srcColumnFrac; + rColCache[index] = R(x); + } + } + + // Set this up so we can cache the R()'s for every row + double* rRowCache = stackalloc double[4]; + + for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) + { + double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1); + double srcRowFloor = Math.Floor(srcRow); + double srcRowFrac = srcRow - srcRowFloor; + int srcRowInt = (int)srcRow; + ColorBgra *dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY); + + // Compute the R() values for this row + for (int n = -1; n <= 2; ++n) + { + double x = srcRowFrac - n; + rRowCache[n + 1] = R(x); + } + + rColCache = (double*)rColCacheIP.ToPointer(); + ColorBgra *srcRowPtr = source.GetRowAddressUnchecked(srcRowInt - 1); + + for (int dstX = roi.Left; dstX < roi.Right; dstX++) + { + double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); + double srcColumnFloor = Math.Floor(srcColumn); + double srcColumnFrac = srcColumn - srcColumnFloor; + int srcColumnInt = (int)srcColumn; + + double blueSum = 0; + double greenSum = 0; + double redSum = 0; + double alphaSum = 0; + double totalWeight = 0; + + ColorBgra *srcPtr = srcRowPtr + srcColumnInt - 1; + for (int n = 0; n <= 3; ++n) + { + double w0 = rColCache[0] * rRowCache[n]; + double w1 = rColCache[1] * rRowCache[n]; + double w2 = rColCache[2] * rRowCache[n]; + double w3 = rColCache[3] * rRowCache[n]; + + double a0 = srcPtr[0].A; + double a1 = srcPtr[1].A; + double a2 = srcPtr[2].A; + double a3 = srcPtr[3].A; + + alphaSum += (a0 * w0) + (a1 * w1) + (a2 * w2) + (a3 * w3); + totalWeight += w0 + w1 + w2 + w3; + + blueSum += (a0 * srcPtr[0].B * w0) + (a1 * srcPtr[1].B * w1) + (a2 * srcPtr[2].B * w2) + (a3 * srcPtr[3].B * w3); + greenSum += (a0 * srcPtr[0].G * w0) + (a1 * srcPtr[1].G * w1) + (a2 * srcPtr[2].G * w2) + (a3 * srcPtr[3].G * w3); + redSum += (a0 * srcPtr[0].R * w0) + (a1 * srcPtr[1].R * w1) + (a2 * srcPtr[2].R * w2) + (a3 * srcPtr[3].R * w3); + + srcPtr = (ColorBgra *)((byte *)srcPtr + source.stride); + } + + double alpha = alphaSum / totalWeight; + + double blue; + double green; + double red; + + if (alpha == 0) + { + blue = 0; + green = 0; + red = 0; + } + else + { + blue = blueSum / alphaSum; + green = greenSum / alphaSum; + red = redSum / alphaSum; + + // add 0.5 to ensure truncation to uint results in rounding + alpha += 0.5; + blue += 0.5; + green += 0.5; + red += 0.5; + } + + dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24); + ++dstPtr; + rColCache += 4; + } // for (dstX... + } // for (dstY... + + Memory.Free(rColCacheIP); + } // unsafe + } + } + + /// + /// Fits the source surface to this surface using bilinear interpolation. + /// + /// The surface to read pixels from. + /// This method was implemented with correctness, not performance, in mind. + public void BilinearFitSurface(Surface source) + { + BilinearFitSurface(source, this.Bounds); + } + + /// + /// Fits the source surface to this surface using bilinear interpolation. + /// + /// The surface to read pixels from. + /// The rectangle to clip rendering to. + /// This method was implemented with correctness, not performance, in mind. + public void BilinearFitSurface(Surface source, Rectangle dstRoi) + { + if (dstRoi.Width < 2 || dstRoi.Height < 2 || this.width < 2 || this.height < 2) + { + SuperSamplingFitSurface(source, dstRoi); + } + else + { + unsafe + { + Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); + + for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) + { + ColorBgra *dstRowPtr = this.GetRowAddressUnchecked(dstY); + float srcRow = (float)(dstY * (source.height - 1)) / (float)(height - 1); + + for (int dstX = roi.Left; dstX < roi.Right; dstX++) + { + float srcColumn = (float)(dstX * (source.width - 1)) / (float)(width - 1); + *dstRowPtr = source.GetBilinearSample(srcColumn, srcRow); + ++dstRowPtr; + } + } + } + } + } + + /// + /// Fits the source surface to this surface using the given algorithm. + /// + /// The surface to copy pixels from. + /// The algorithm to use. + public void FitSurface(ResamplingAlgorithm algorithm, Surface source) + { + FitSurface(algorithm, source, this.Bounds); + } + + /// + /// Fits the source surface to this surface using the given algorithm. + /// + /// The surface to copy pixels from. + /// The rectangle to clip rendering to. + /// The algorithm to use. + public void FitSurface(ResamplingAlgorithm algorithm, Surface source, Rectangle dstRoi) + { + switch (algorithm) + { + case ResamplingAlgorithm.Bicubic: + BicubicFitSurface(source, dstRoi); + break; + + case ResamplingAlgorithm.Bilinear: + BilinearFitSurface(source, dstRoi); + break; + + case ResamplingAlgorithm.NearestNeighbor: + NearestNeighborFitSurface(source, dstRoi); + break; + + case ResamplingAlgorithm.SuperSampling: + SuperSamplingFitSurface(source, dstRoi); + break; + + default: + throw new InvalidEnumArgumentException("algorithm"); + } + } + + private MemoryBlock GetRootMemoryBlock(MemoryBlock block) + { + MemoryBlock p = block; + + while (p.Parent != null) + { + p = p.Parent; + } + + return p; + } + + internal void GetDrawBitmapInfo(out IntPtr bitmapHandle, out Point childOffset, out Size parentSize) + { + MemoryBlock rootBlock = GetRootMemoryBlock(this.scan0); + long childOffsetBytes = this.scan0.Pointer.ToInt64() - rootBlock.Pointer.ToInt64(); + int childY = (int)(childOffsetBytes / this.stride); + int childX = (int)((childOffsetBytes - (childY * this.stride)) / ColorBgra.SizeOf); + childOffset = new Point(childX, childY); + parentSize = new Size(this.stride / ColorBgra.SizeOf, childY + this.height); + bitmapHandle = rootBlock.BitmapHandle; + } + + /// + /// Releases all resources held by this Surface object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + + if (disposing) + { + scan0.Dispose(); + scan0 = null; + } + } + } + } +} diff --git a/src/Core/SurfaceBox.cs b/src/Core/SurfaceBox.cs new file mode 100644 index 0000000..d875b5f --- /dev/null +++ b/src/Core/SurfaceBox.cs @@ -0,0 +1,558 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Renders a Surface to the screen. + /// + public sealed class SurfaceBox + : Control + { + public const int MaxSideLength = 32767; + + private int justPaintWhite = 0; // when this is non-zero, we just paint white (startup optimization) + private ScaleFactor scaleFactor; + private PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(); + private SurfaceBoxRendererList rendererList; + private SurfaceBoxBaseRenderer baseRenderer; + private Surface surface; + + // Each concrete instance of SurfaceBox holds a strong reference to a single + // double buffer Surface. Statically, we store a weak reference to it. This way + // when there are no more SurfaceBox instances, the double buffer Surface can + // be cleaned up by the GC. + private static WeakReference doubleBufferSurfaceWeakRef = null; + private Surface doubleBufferSurface = null; + + public SurfaceBoxRendererList RendererList + { + get + { + return this.rendererList; + } + } + + public Surface Surface + { + get + { + return this.surface; + } + + set + { + this.surface = value; + this.baseRenderer.Source = value; + + if (this.surface != null) + { + // Maintain the scalefactor + this.Size = this.scaleFactor.ScaleSize(surface.Size); + this.rendererList.SourceSize = this.surface.Size; + this.rendererList.DestinationSize = this.Size; + } + + Invalidate(); + } + } + + [Obsolete("This functionality was moved to the DocumentView class", true)] + public bool DrawGrid + { + get + { + return false; + } + + set + { + } + } + + public void FitToSize(Size fit) + { + ScaleFactor newSF = ScaleFactor.Min(fit.Width, surface.Width, + fit.Height, surface.Height, + ScaleFactor.MinValue); + + this.scaleFactor = newSF; + this.Size = this.scaleFactor.ScaleSize(surface.Size); + } + + public void RenderTo(Surface dst) + { + dst.Clear(ColorBgra.Transparent); + + if (this.surface != null) + { + SurfaceBoxRendererList sbrl = new SurfaceBoxRendererList(this.surface.Size, dst.Size); + SurfaceBoxBaseRenderer sbbr = new SurfaceBoxBaseRenderer(sbrl, this.surface); + sbrl.Add(sbbr, true); + sbrl.Render(dst, new Point(0, 0)); + sbrl.Remove(sbbr); + } + } + + /// + /// Increments the "just paint white" counter. When this counter is non-zero, + /// the OnPaint() method will only paint white. This is used as an optimization + /// during Paint.NET's startup so that it doesn't have to touch all the pages + /// of the blank document's layer. + /// + public void IncrementJustPaintWhite() + { + ++this.justPaintWhite; + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + // This code fixes the size of the surfaceBox as necessary to + // maintain the aspect ratio of the surface. Keeping the mouse + // within 32767 is delegated to the new overflow-checking code + // in Tool.cs. + + Size mySize = this.Size; + if (this.Width == MaxSideLength && surface != null) + { + // Windows forms probably clamped this control's width, so we have to fix the height. + mySize.Height = (int)(((long)(MaxSideLength + 1) * (long)surface.Height) / (long)surface.Width); + } + else if (mySize.Width == 0) + { + mySize.Width = 1; + } + + if (this.Width == MaxSideLength && surface != null) + { + // Windows forms probably clamped this control's height, so we have to fix the width. + mySize.Width = (int)(((long)(MaxSideLength + 1) * (long)surface.Width) / (long)surface.Height); + } + else if (mySize.Height == 0) + { + mySize.Height = 1; + } + + if (mySize != this.Size) + { + this.Size = mySize; + } + + if (surface == null) + { + this.scaleFactor = ScaleFactor.OneToOne; + } + else + { + ScaleFactor newSF = ScaleFactor.Max(this.Width, surface.Width, + this.Height, surface.Height, + ScaleFactor.OneToOne); + + this.scaleFactor = newSF; + } + + this.rendererList.DestinationSize = this.Size; + } + + public ScaleFactor ScaleFactor + { + get + { + return this.scaleFactor; + } + } + + public SurfaceBox() + { + InitializeComponent(); + this.scaleFactor = ScaleFactor.OneToOne; + + this.rendererList = new SurfaceBoxRendererList(this.Size, this.Size); + this.rendererList.Invalidated += new InvalidateEventHandler(Renderers_Invalidated); + this.baseRenderer = new SurfaceBoxBaseRenderer(this.rendererList, null); + this.rendererList.Add(this.baseRenderer, false); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.baseRenderer != null) + { + this.rendererList.Remove(this.baseRenderer); + this.baseRenderer.Dispose(); + this.baseRenderer = null; + } + + if (this.doubleBufferSurface != null) + { + this.doubleBufferSurface.Dispose(); + this.doubleBufferSurface = null; + } + } + + base.Dispose(disposing); + } + + /// + /// This event is raised after painting has been performed. This is required because + /// the normal Paint event is raised *before* painting has been performed. + /// + public event PaintEventHandler2 Painted; + private void OnPainted(PaintEventArgs2 e) + { + if (Painted != null) + { + Painted(this, e); + } + } + + public event PaintEventHandler2 PrePaint; + private void OnPrePaint(PaintEventArgs2 e) + { + if (PrePaint != null) + { + PrePaint(this, e); + } + } + + private Surface GetDoubleBuffer(Size size) + { + Surface localDBSurface = null; + Size oldSize = new Size(0, 0); + + // If we already have a double buffer surface reference, but if that surface + // is already disposed then don't worry about it. + if (this.doubleBufferSurface != null && this.doubleBufferSurface.IsDisposed) + { + oldSize = this.doubleBufferSurface.Size; + this.doubleBufferSurface = null; + } + + // If we already have a double buffer surface reference, but if that surface + // is too small, then nuke it. + if (this.doubleBufferSurface != null && + (this.doubleBufferSurface.Width < size.Width || this.doubleBufferSurface.Height < size.Height)) + { + oldSize = this.doubleBufferSurface.Size; + this.doubleBufferSurface.Dispose(); + this.doubleBufferSurface = null; + doubleBufferSurfaceWeakRef = null; + } + + // If we don't have a double buffer, then we'd better get one. + if (this.doubleBufferSurface != null) + { + // Got one! + localDBSurface = this.doubleBufferSurface; + } + else if (doubleBufferSurfaceWeakRef != null) + { + // First, try to get the one that's already shared amongst all SurfaceBox instances. + localDBSurface = doubleBufferSurfaceWeakRef.Target; + + // If it's disposed, then forget about it. + if (localDBSurface != null && localDBSurface.IsDisposed) + { + oldSize = localDBSurface.Size; + localDBSurface = null; + doubleBufferSurfaceWeakRef = null; + } + } + + // Make sure the surface is big enough. + if (localDBSurface != null && (localDBSurface.Width < size.Width || localDBSurface.Height < size.Height)) + { + oldSize = localDBSurface.Size; + localDBSurface.Dispose(); + localDBSurface = null; + doubleBufferSurfaceWeakRef = null; + } + + // So, do we have a surface? If not then we'd better make one. + if (localDBSurface == null) + { + Size newSize = new Size(Math.Max(size.Width, oldSize.Width), Math.Max(size.Height, oldSize.Height)); + localDBSurface = new Surface(newSize.Width, newSize.Height); + doubleBufferSurfaceWeakRef = new WeakReference(localDBSurface); + } + + this.doubleBufferSurface = localDBSurface; + Surface window = localDBSurface.CreateWindow(0, 0, size.Width, size.Height); + return window; + } + + protected override void OnPaint(PaintEventArgs e) + { + if (this.surface != null) + { + PdnRegion clipRegion = null; + Rectangle[] rects = this.realUpdateRects; + + if (rects == null) + { + clipRegion = new PdnRegion(e.Graphics.Clip, true); + clipRegion.Intersect(e.ClipRectangle); + rects = clipRegion.GetRegionScansReadOnlyInt(); + } + + if (this.justPaintWhite > 0) + { + PdnGraphics.FillRectangles(e.Graphics, Color.White, rects); + } + else + { + foreach (Rectangle rect in rects) + { + if (e.Graphics.IsVisible(rect)) + { + PaintEventArgs2 e2 = new PaintEventArgs2(e.Graphics, rect); + OnPaintImpl(e2); + } + } + } + + if (clipRegion != null) + { + clipRegion.Dispose(); + clipRegion = null; + } + } + + if (this.justPaintWhite > 0) + { + --this.justPaintWhite; + } + + base.OnPaint(e); + } + + private void OnPaintImpl(PaintEventArgs2 e) + { + using (Surface doubleBuffer = GetDoubleBuffer(e.ClipRectangle.Size)) + { + using (RenderArgs renderArgs = new RenderArgs(doubleBuffer)) + { + OnPrePaint(e); + DrawArea(renderArgs, e.ClipRectangle.Location); + OnPainted(e); + + IntPtr tracking; + Point childOffset; + Size parentSize; + doubleBuffer.GetDrawBitmapInfo(out tracking, out childOffset, out parentSize); + + PdnGraphics.DrawBitmap(e.Graphics, e.ClipRectangle, e.Graphics.Transform, + tracking, childOffset.X, childOffset.Y); + } + } + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + // do nothing so as to avoid flicker + // tip: for debugging, uncomment the next line! + //base.OnPaintBackground(pevent); + } + + private class RenderContext + { + public Surface[] windows; + public Point[] offsets; + public Rectangle[] rects; + public SurfaceBox owner; + public WaitCallback waitCallback; + + public void RenderThreadMethod(object indexObject) + { + int index = (int)indexObject; + this.owner.rendererList.Render(windows[index], offsets[index]); + this.windows[index].Dispose(); + this.windows[index] = null; + } + } + + private RenderContext renderContext; + + /// + /// Draws an area of the SurfaceBox. + /// + /// The rendering surface object to draw to. + /// The virtual offset of ra, in client (destination) coordinates. + /// + /// If drawing to ra.Surface or ra.Bitmap, copy the roi of the source surface to (0,0) of ra.Surface or ra.Bitmap + /// If drawing to ra.Graphics, copy the roi of the surface to (roi.X, roi.Y) of ra.Graphics + /// + private unsafe void DrawArea(RenderArgs ra, Point offset) + { + if (surface == null) + { + return; + } + + if (renderContext == null || (renderContext.windows != null && renderContext.windows.Length != Processor.LogicalCpuCount)) + { + renderContext = new RenderContext(); + renderContext.owner = this; + renderContext.waitCallback = new WaitCallback(renderContext.RenderThreadMethod); + renderContext.windows = new Surface[Processor.LogicalCpuCount]; + renderContext.offsets = new Point[Processor.LogicalCpuCount]; + renderContext.rects = new Rectangle[Processor.LogicalCpuCount]; + } + + Utility.SplitRectangle(ra.Bounds, renderContext.rects); + + for (int i = 0; i < renderContext.rects.Length; ++i) + { + if (renderContext.rects[i].Width > 0 && renderContext.rects[i].Height > 0) + { + renderContext.offsets[i] = new Point(renderContext.rects[i].X + offset.X, renderContext.rects[i].Y + offset.Y); + renderContext.windows[i] = ra.Surface.CreateWindow(renderContext.rects[i]); + } + else + { + renderContext.windows[i] = null; + } + } + + for (int i = 0; i < renderContext.windows.Length; ++i) + { + if (renderContext.windows[i] != null) + { + this.threadPool.QueueUserWorkItem(renderContext.waitCallback, BoxedConstants.GetInt32(i)); + } + } + + this.threadPool.Drain(); + } + + private Rectangle[] realUpdateRects = null; + protected override void WndProc(ref Message m) + { + IntPtr preR = m.Result; + + // Ignore focus + if (m.Msg == 7 /* WM_SETFOCUS */) + { + return; + } + else if (m.Msg == 0x000f /* WM_PAINT */) + { + this.realUpdateRects = UI.GetUpdateRegion(this); + + if (this.realUpdateRects != null && + this.realUpdateRects.Length >= 5) // '5' chosen arbitrarily + { + this.realUpdateRects = null; + } + + base.WndProc(ref m); + } + else + { + base.WndProc (ref m); + } + } + + /// + /// Converts from control client coordinates to surface coordinates + /// This is useful when this.Bounds != surface.Bounds (i.e. some sort of zooming is in effect) + /// + /// + /// + public PointF ClientToSurface(PointF clientPt) + { + return ScaleFactor.UnscalePoint(clientPt); + } + + public Point ClientToSurface(Point clientPt) + { + return ScaleFactor.UnscalePoint(clientPt); + } + + public SizeF ClientToSurface(SizeF clientSize) + { + return ScaleFactor.UnscaleSize(clientSize); + } + + public Size ClientToSurface(Size clientSize) + { + return Size.Round(ClientToSurface((SizeF)clientSize)); + } + + public RectangleF ClientToSurface(RectangleF clientRect) + { + return new RectangleF(ClientToSurface(clientRect.Location), ClientToSurface(clientRect.Size)); + } + + public Rectangle ClientToSurface(Rectangle clientRect) + { + return new Rectangle(ClientToSurface(clientRect.Location), ClientToSurface(clientRect.Size)); + } + + public PointF SurfaceToClient(PointF surfacePt) + { + return ScaleFactor.ScalePoint(surfacePt); + } + + public Point SurfaceToClient(Point surfacePt) + { + return ScaleFactor.ScalePoint(surfacePt); + } + + public SizeF SurfaceToClient(SizeF surfaceSize) + { + return ScaleFactor.ScaleSize(surfaceSize); + } + + public Size SurfaceToClient(Size surfaceSize) + { + return Size.Round(SurfaceToClient((SizeF)surfaceSize)); + } + + public RectangleF SurfaceToClient(RectangleF surfaceRect) + { + return new RectangleF(SurfaceToClient(surfaceRect.Location), SurfaceToClient(surfaceRect.Size)); + } + + public Rectangle SurfaceToClient(Rectangle surfaceRect) + { + return new Rectangle(SurfaceToClient(surfaceRect.Location), SurfaceToClient(surfaceRect.Size)); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + } + + private void Renderers_Invalidated(object sender, InvalidateEventArgs e) + { + Rectangle rect = SurfaceToClient(e.InvalidRect); + rect.Inflate(1, 1); + Invalidate(rect); + } + } +} + diff --git a/src/Core/SurfaceBox.resx b/src/Core/SurfaceBox.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/SurfaceBoxBaseRenderer.cs b/src/Core/SurfaceBoxBaseRenderer.cs new file mode 100644 index 0000000..fe43696 --- /dev/null +++ b/src/Core/SurfaceBoxBaseRenderer.cs @@ -0,0 +1,276 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class SurfaceBoxBaseRenderer + : SurfaceBoxRenderer + { + private Surface source; + private RenderDelegate renderDelegate; + + public Surface Source + { + get + { + return this.source; + } + + set + { + this.source = value; + Flush(); + } + } + + private void Flush() + { + this.renderDelegate = null; + } + + protected override void OnVisibleChanged() + { + Invalidate(); + } + + private void ChooseRenderDelegate() + { + if (SourceSize.Width > DestinationSize.Width) + { + // zoom out + this.renderDelegate = new RenderDelegate(RenderZoomOutRotatedGridMultisampling); + } + else if (SourceSize == DestinationSize) + { + // zoom 100% + this.renderDelegate = new RenderDelegate(RenderOneToOne); + } + else if (SourceSize.Width < DestinationSize.Width) + { + // zoom in + this.renderDelegate = new RenderDelegate(RenderZoomInNearestNeighbor); + } + } + + public override void OnDestinationSizeChanged() + { + ChooseRenderDelegate(); + this.OwnerList.InvalidateLookups(); + base.OnDestinationSizeChanged(); + } + + public override void OnSourceSizeChanged() + { + ChooseRenderDelegate(); + this.OwnerList.InvalidateLookups(); + base.OnSourceSizeChanged(); + } + + public static void RenderOneToOne(Surface dst, Surface source, Point offset) + { + unsafe + { + Rectangle srcRect = new Rectangle(offset, dst.Size); + srcRect.Intersect(source.Bounds); + + for (int dstRow = 0; dstRow < srcRect.Height; ++dstRow) + { + ColorBgra* dstRowPtr = dst.GetRowAddressUnchecked(dstRow); + ColorBgra* srcRowPtr = source.GetPointAddressUnchecked(offset.X, dstRow + offset.Y); + + int dstCol = offset.X; + int dstColEnd = offset.X + srcRect.Width; + int checkerY = dstRow + offset.Y; + + while (dstCol < dstColEnd) + { + int b = srcRowPtr->B; + int g = srcRowPtr->G; + int r = srcRowPtr->R; + int a = srcRowPtr->A; + + // Blend it over the checkerboard background + int v = (((dstCol ^ checkerY) & 8) << 3) + 191; + a = a + (a >> 7); + int vmia = v * (256 - a); + + r = ((r * a) + vmia) >> 8; + g = ((g * a) + vmia) >> 8; + b = ((b * a) + vmia) >> 8; + + dstRowPtr->Bgra = (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)255 << 24); + ++dstRowPtr; + ++srcRowPtr; + ++dstCol; + } + } + } + } + + private void RenderOneToOne(Surface dst, Point offset) + { + RenderOneToOne(dst, this.source, offset); + } + + private void RenderZoomInNearestNeighbor(Surface dst, Point offset) + { + unsafe + { + int[] d2SLookupY = OwnerList.Dst2SrcLookupY; + int[] d2SLookupX = OwnerList.Dst2SrcLookupX; + + for (int dstRow = 0; dstRow < dst.Height; ++dstRow) + { + int nnY = dstRow + offset.Y; + int srcY = d2SLookupY[nnY]; + ColorBgra *dstPtr = dst.GetRowAddressUnchecked(dstRow); + ColorBgra *srcRow = this.source.GetRowAddressUnchecked(srcY); + + for (int dstCol = 0; dstCol < dst.Width; ++dstCol) + { + int nnX = dstCol + offset.X; + int srcX = d2SLookupX[nnX]; + + ColorBgra src = *(srcRow + srcX); + int b = src.B; + int g = src.G; + int r = src.R; + int a = src.A; + + // Blend it over the checkerboard background + int v = (((dstCol + offset.X) ^ (dstRow + offset.Y)) & 8) * 8 + 191; + a = a + (a >> 7); + int vmia = v * (256 - a); + + r = ((r * a) + vmia) >> 8; + g = ((g * a) + vmia) >> 8; + b = ((b * a) + vmia) >> 8; + + dstPtr->Bgra = (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)255 << 24); + + ++dstPtr; + } + } + } + } + + public static void RenderZoomOutRotatedGridMultisampling(Surface dst, Surface source, Point offset, Size destinationSize) + { + unsafe + { + const int fpShift = 12; + const int fpFactor = (1 << fpShift); + + Size sourceSize = source.Size; + long fDstLeftLong = ((long)offset.X * fpFactor * (long)sourceSize.Width) / (long)destinationSize.Width; + long fDstTopLong = ((long)offset.Y * fpFactor * (long)sourceSize.Height) / (long)destinationSize.Height; + long fDstRightLong = ((long)(offset.X + dst.Width) * fpFactor * (long)sourceSize.Width) / (long)destinationSize.Width; + long fDstBottomLong = ((long)(offset.Y + dst.Height) * fpFactor * (long)sourceSize.Height) / (long)destinationSize.Height; + int fDstLeft = (int)fDstLeftLong; + int fDstTop = (int)fDstTopLong; + int fDstRight = (int)fDstRightLong; + int fDstBottom = (int)fDstBottomLong; + int dx = (fDstRight - fDstLeft) / dst.Width; + int dy = (fDstBottom - fDstTop) / dst.Height; + + for (int dstRow = 0, fDstY = fDstTop; + dstRow < dst.Height && fDstY < fDstBottom; + ++dstRow, fDstY += dy) + { + int srcY1 = fDstY >> fpShift; // y + int srcY2 = (fDstY + (dy >> 2)) >> fpShift; // y + 0.25 + int srcY3 = (fDstY + (dy >> 1)) >> fpShift; // y + 0.50 + int srcY4 = (fDstY + (dy >> 1) + (dy >> 2)) >> fpShift; // y + 0.75 + +#if DEBUG + Debug.Assert(source.IsRowVisible(srcY1)); + Debug.Assert(source.IsRowVisible(srcY2)); + Debug.Assert(source.IsRowVisible(srcY3)); + Debug.Assert(source.IsRowVisible(srcY4)); + Debug.Assert(dst.IsRowVisible(dstRow)); +#endif + + ColorBgra* src1 = source.GetRowAddressUnchecked(srcY1); + ColorBgra* src2 = source.GetRowAddressUnchecked(srcY2); + ColorBgra* src3 = source.GetRowAddressUnchecked(srcY3); + ColorBgra* src4 = source.GetRowAddressUnchecked(srcY4); + ColorBgra* dstPtr = dst.GetRowAddressUnchecked(dstRow); + int checkerY = dstRow + offset.Y; + int checkerX = offset.X; + int maxCheckerX = checkerX + dst.Width; + + for (int fDstX = fDstLeft; + checkerX < maxCheckerX && fDstX < fDstRight; + ++checkerX, fDstX += dx) + { + int srcX1 = (fDstX + (dx >> 2)) >> fpShift; // x + 0.25 + int srcX2 = (fDstX + (dx >> 1) + (dx >> 2)) >> fpShift; // x + 0.75 + int srcX3 = fDstX >> fpShift; // x + int srcX4 = (fDstX + (dx >> 1)) >> fpShift; // x + 0.50 + +#if DEBUG + Debug.Assert(source.IsColumnVisible(srcX1)); + Debug.Assert(source.IsColumnVisible(srcX2)); + Debug.Assert(source.IsColumnVisible(srcX3)); + Debug.Assert(source.IsColumnVisible(srcX4)); +#endif + + ColorBgra* p1 = src1 + srcX1; + ColorBgra* p2 = src2 + srcX2; + ColorBgra* p3 = src3 + srcX3; + ColorBgra* p4 = src4 + srcX4; + + int r = (2 + p1->R + p2->R + p3->R + p4->R) >> 2; + int g = (2 + p1->G + p2->G + p3->G + p4->G) >> 2; + int b = (2 + p1->B + p2->B + p3->B + p4->B) >> 2; + int a = (2 + p1->A + p2->A + p3->A + p4->A) >> 2; + + // Blend it over the checkerboard background + int v = ((checkerX ^ checkerY) & 8) * 8 + 191; + a = a + (a >> 7); + int vmia = v * (256 - a); + + r = ((r * a) + vmia) >> 8; + g = ((g * a) + vmia) >> 8; + b = ((b * a) + vmia) >> 8; + + dstPtr->Bgra = (uint)b + ((uint)g << 8) + ((uint)r << 16) + 0xff000000; + ++dstPtr; + } + } + } + } + + private void RenderZoomOutRotatedGridMultisampling(Surface dst, Point offset) + { + RenderZoomOutRotatedGridMultisampling(dst, this.source, offset, this.DestinationSize); + } + + public override void Render(Surface dst, Point offset) + { + if (this.renderDelegate == null) + { + ChooseRenderDelegate(); + } + + this.renderDelegate(dst, offset); + } + + public SurfaceBoxBaseRenderer(SurfaceBoxRendererList ownerList, Surface source) + : base(ownerList) + { + this.source = source; + ChooseRenderDelegate(); + } + } +} diff --git a/src/Core/SurfaceBoxGraphicsRenderer.cs b/src/Core/SurfaceBoxGraphicsRenderer.cs new file mode 100644 index 0000000..0555fc9 --- /dev/null +++ b/src/Core/SurfaceBoxGraphicsRenderer.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// This class handles rendering something to a SurfaceBox via a Graphics context. + /// + public abstract class SurfaceBoxGraphicsRenderer + : SurfaceBoxRenderer + { + public SurfaceBoxGraphicsRenderer(SurfaceBoxRendererList ownerList) + : base(ownerList) + { + } + + public abstract void RenderToGraphics(Graphics g, Point offset); + + public virtual bool ShouldRender() + { + return true; + } + + public override sealed void Render(Surface dst, Point offset) + { + if (ShouldRender()) + { + using (RenderArgs ra = new RenderArgs(dst)) + { + RenderToGraphics(ra.Graphics, offset); + } + } + } + } +} diff --git a/src/Core/SurfaceBoxGridRenderer.cs b/src/Core/SurfaceBoxGridRenderer.cs new file mode 100644 index 0000000..4d21824 --- /dev/null +++ b/src/Core/SurfaceBoxGridRenderer.cs @@ -0,0 +1,115 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public sealed class SurfaceBoxGridRenderer + : SurfaceBoxRenderer + { + public override void OnDestinationSizeChanged() + { + if (this.Visible) + { + this.OwnerList.InvalidateLookups(); + } + + base.OnDestinationSizeChanged(); + } + + public override void OnSourceSizeChanged() + { + if (this.Visible) + { + this.OwnerList.InvalidateLookups(); + } + + base.OnSourceSizeChanged(); + } + + protected override void OnVisibleChanged() + { + if (this.Visible) + { + this.OwnerList.InvalidateLookups(); + } + + Invalidate(); + } + + public unsafe override void Render(Surface dst, System.Drawing.Point offset) + { + if (OwnerList.ScaleFactor < new ScaleFactor(2, 1)) + { + return; + } + + int[] d2SLookupX = OwnerList.Dst2SrcLookupX; + int[] d2SLookupY = OwnerList.Dst2SrcLookupY; + int[] s2DLookupX = OwnerList.Src2DstLookupX; + int[] s2DLookupY = OwnerList.Src2DstLookupY; + + ColorBgra[] blackAndWhite = new ColorBgra[2] { ColorBgra.White, ColorBgra.Black }; + + // draw horizontal lines + int sTop = d2SLookupY[offset.Y]; + int sBottom = d2SLookupY[offset.Y + dst.Height]; + + for (int srcY = sTop; srcY <= sBottom; ++srcY) + { + int dstY = s2DLookupY[srcY]; + int dstRow = dstY - offset.Y; + + if (dst.IsRowVisible(dstRow)) + { + ColorBgra *dstRowPtr = dst.GetRowAddress(dstRow); + ColorBgra *dstRowEndPtr = dstRowPtr + dst.Width; + + dstRowPtr += offset.X & 1; + + while (dstRowPtr < dstRowEndPtr) + { + *dstRowPtr = ColorBgra.Black; + dstRowPtr += 2; + } + } + } + + // draw vertical lines + int sLeft = d2SLookupX[offset.X]; + int sRight = d2SLookupX[offset.X + dst.Width]; + + for (int srcX = sLeft; srcX <= sRight; ++srcX) + { + int dstX = s2DLookupX[srcX]; + int dstCol = dstX - offset.X; + + if (dst.IsColumnVisible(dstX - offset.X)) + { + byte *dstColPtr = (byte *)dst.GetPointAddress(dstCol, 0); + byte *dstColEndPtr = dstColPtr + dst.Stride * dst.Height; + + dstColPtr += (offset.Y & 1) * dst.Stride; + + while (dstColPtr < dstColEndPtr) + { + *((ColorBgra *)dstColPtr) = ColorBgra.Black; + dstColPtr += 2 * dst.Stride; + } + } + } + } + + public SurfaceBoxGridRenderer(SurfaceBoxRendererList ownerList) + : base(ownerList) + { + } + } +} diff --git a/src/Core/SurfaceBoxRenderer.cs b/src/Core/SurfaceBoxRenderer.cs new file mode 100644 index 0000000..115b650 --- /dev/null +++ b/src/Core/SurfaceBoxRenderer.cs @@ -0,0 +1,170 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// This class handles rendering something to a SurfaceBox. + /// + public abstract class SurfaceBoxRenderer + : IDisposable + { + private bool disposed = false; + private SurfaceBoxRendererList ownerList; + private bool visible; + + public const int MinXCoordinate = -131072; + public const int MaxXCoordinate = +131072; + public const int MinYCoordinate = -131072; + public const int MaxYCoordinate = +131072; + + public bool IsDisposed + { + get + { + return this.disposed; + } + } + + public static Rectangle MaxBounds + { + get + { + return Rectangle.FromLTRB(MinXCoordinate, MinYCoordinate, MaxXCoordinate + 1, MaxYCoordinate + 1); + } + } + + protected object SyncRoot + { + get + { + return OwnerList.SyncRoot; + } + } + + protected SurfaceBoxRendererList OwnerList + { + get + { + return this.ownerList; + } + } + + public virtual void OnSourceSizeChanged() + { + } + + public virtual void OnDestinationSizeChanged() + { + } + + public Size SourceSize + { + get + { + return this.OwnerList.SourceSize; + } + } + + public Size DestinationSize + { + get + { + return this.OwnerList.DestinationSize; + } + } + + protected virtual void OnVisibleChanging() + { + } + + protected abstract void OnVisibleChanged(); + + public bool Visible + { + get + { + return this.visible; + } + + set + { + if (this.visible != value) + { + OnVisibleChanging(); + this.visible = value; + OnVisibleChanged(); + } + } + } + + protected delegate void RenderDelegate(Surface dst, Point offset); + + /// + /// Renders, at the appropriate scale, the layer's imagery. + /// + /// The Surface to render to. + /// The (x,y) location of the upper-left corner of dst within DestinationSize. + public abstract void Render(Surface dst, Point offset); + + protected virtual void OnInvalidate(Rectangle rect) + { + this.OwnerList.Invalidate(rect); + } + + public void Invalidate(Rectangle rect) + { + OnInvalidate(rect); + } + + public void Invalidate(RectangleF rectF) + { + Rectangle rect = Utility.RoundRectangle(rectF); + Invalidate(rect); + } + + public void Invalidate(PdnRegion region) + { + foreach (Rectangle rect in region.GetRegionScansReadOnlyInt()) + { + Invalidate(rect); + } + } + + public void Invalidate() + { + Invalidate(Rectangle.FromLTRB(MinXCoordinate, MinYCoordinate, MaxXCoordinate + 1, MaxYCoordinate + 1)); + } + + public SurfaceBoxRenderer(SurfaceBoxRendererList ownerList) + { + this.ownerList = ownerList; + this.visible = true; + } + + ~SurfaceBoxRenderer() + { + this.Dispose(false); + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + this.disposed = true; + } + } +} diff --git a/src/Core/SurfaceBoxRendererList.cs b/src/Core/SurfaceBoxRendererList.cs new file mode 100644 index 0000000..0f721dc --- /dev/null +++ b/src/Core/SurfaceBoxRendererList.cs @@ -0,0 +1,449 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class SurfaceBoxRendererList + { + private SurfaceBoxRenderer[] list; + private SurfaceBoxRenderer[] topList; + private Size sourceSize; + private Size destinationSize; + private ScaleFactor scaleFactor; // ratio is dst:src + private object lockObject = new object(); + + public object SyncRoot + { + get + { + return this.lockObject; + } + } + + public ScaleFactor ScaleFactor + { + get + { + return this.scaleFactor; + } + } + + public SurfaceBoxRenderer[][] Renderers + { + get + { + return new SurfaceBoxRenderer[][] { list, topList }; + } + } + + private void ComputeScaleFactor() + { + scaleFactor = new ScaleFactor(this.DestinationSize.Width, this.SourceSize.Width); + } + + public Point SourceToDestination(Point pt) + { + return this.scaleFactor.ScalePoint(pt); + } + + public RectangleF SourceToDestination(Rectangle rect) + { + return this.scaleFactor.ScaleRectangle((RectangleF)rect); + } + + public Point DestinationToSource(Point pt) + { + return this.scaleFactor.UnscalePoint(pt); + } + + public void Add(SurfaceBoxRenderer addMe, bool alwaysOnTop) + { + SurfaceBoxRenderer[] startList = alwaysOnTop ? this.topList : this.list; + SurfaceBoxRenderer[] listPlusOne = new SurfaceBoxRenderer[startList.Length + 1]; + + for (int i = 0; i < startList.Length; ++i) + { + listPlusOne[i] = startList[i]; + } + + listPlusOne[listPlusOne.Length - 1] = addMe; + + if (alwaysOnTop) + { + this.topList = listPlusOne; + } + else + { + this.list = listPlusOne; + } + + Invalidate(); + } + + public void Remove(SurfaceBoxRenderer removeMe) + { + if (this.list.Length == 0 && this.topList.Length == 0) + { + throw new InvalidOperationException("zero items left, can't remove anything"); + } + else + { + bool found = false; + + if (this.list.Length > 0) + { + SurfaceBoxRenderer[] listSubOne = new SurfaceBoxRenderer[this.list.Length - 1]; + bool foundHere = false; + int dstIndex = 0; + + for (int i = 0; i < this.list.Length; ++i) + { + if (this.list[i] == removeMe) + { + if (foundHere) + { + throw new ArgumentException("removeMe appeared multiple times in the list"); + } + else + { + foundHere = true; + } + } + else + { + if (dstIndex == this.list.Length - 1) + { + // was not found + } + else + { + listSubOne[dstIndex] = this.list[i]; + ++dstIndex; + } + } + } + + if (foundHere) + { + this.list = listSubOne; + found = true; + } + } + + if (this.topList.Length > 0) + { + SurfaceBoxRenderer[] topListSubOne = new SurfaceBoxRenderer[this.topList.Length - 1]; + int topDstIndex = 0; + bool foundHere = false; + + for (int i = 0; i < this.topList.Length; ++i) + { + if (this.topList[i] == removeMe) + { + if (found || foundHere) + { + throw new ArgumentException("removeMe appeared multiple times in the list"); + } + else + { + foundHere = true; + } + } + else + { + if (topDstIndex == this.topList.Length - 1) + { + // was not found + } + else + { + topListSubOne[topDstIndex] = this.topList[i]; + ++topDstIndex; + } + } + } + + if (foundHere) + { + this.topList = topListSubOne; + found = true; + } + } + + if (!found) + { + throw new ArgumentException("removeMe was not found", "removeMe"); + } + + Invalidate(); + } + } + + private void OnDestinationSizeChanged() + { + InvalidateLookups(); + + if (this.destinationSize.Width != 0 && this.sourceSize.Width != 0) + { + ComputeScaleFactor(); + + for (int i = 0; i < this.list.Length; ++i) + { + this.list[i].OnDestinationSizeChanged(); + } + + for (int i = 0; i < this.topList.Length; ++i) + { + this.topList[i].OnDestinationSizeChanged(); + } + } + } + + public Size DestinationSize + { + get + { + return this.destinationSize; + } + + set + { + if (this.destinationSize != value) + { + this.destinationSize = value; + OnDestinationSizeChanged(); + } + } + } + + private void OnSourceSizeChanged() + { + InvalidateLookups(); + + if (this.destinationSize.Width != 0 && this.sourceSize.Width != 0) + { + ComputeScaleFactor(); + + for (int i = 0; i < this.list.Length; ++i) + { + this.list[i].OnSourceSizeChanged(); + } + + for (int i = 0; i < this.topList.Length; ++i) + { + this.topList[i].OnSourceSizeChanged(); + } + } + } + + public Size SourceSize + { + get + { + return this.sourceSize; + } + + set + { + if (this.sourceSize != value) + { + this.sourceSize = value; + OnSourceSizeChanged(); + } + } + } + + public int[] Dst2SrcLookupX + { + get + { + lock (this.SyncRoot) + { + CreateD2SLookupX(); + } + + return this.d2SLookupX; + } + } + + private int[] d2SLookupX; // maps from destination->source coordinates + private void CreateD2SLookupX() + { + if (this.d2SLookupX == null || this.d2SLookupX.Length != this.DestinationSize.Width + 1) + { + this.d2SLookupX = new int[this.DestinationSize.Width + 1]; + + for (int x = 0; x < d2SLookupX.Length; ++x) + { + Point pt = new Point(x, 0); + Point surfacePt = this.DestinationToSource(pt); + + // Sometimes the scale factor is slightly different on one axis than + // on another, simply due to accuracy. So we have to clamp this value to + // be within bounds. + d2SLookupX[x] = Utility.Clamp(surfacePt.X, 0, this.SourceSize.Width - 1); + } + } + } + + public int[] Dst2SrcLookupY + { + get + { + lock (this.SyncRoot) + { + CreateD2SLookupY(); + } + + return this.d2SLookupY; + } + } + + private int[] d2SLookupY; // maps from destination->source coordinates + private void CreateD2SLookupY() + { + if (this.d2SLookupY == null || this.d2SLookupY.Length != this.DestinationSize.Height + 1) + { + this.d2SLookupY = new int[this.DestinationSize.Height + 1]; + + for (int y = 0; y < d2SLookupY.Length; ++y) + { + Point pt = new Point(0, y); + Point surfacePt = this.DestinationToSource(pt); + + // Sometimes the scale factor is slightly different on one axis than + // on another, simply due to accuracy. So we have to clamp this value to + // be within bounds. + d2SLookupY[y] = Utility.Clamp(surfacePt.Y, 0, this.SourceSize.Height - 1); + } + } + } + + public int[] Src2DstLookupX + { + get + { + lock (this.SyncRoot) + { + CreateS2DLookupX(); + } + + return this.s2DLookupX; + } + } + + private int[] s2DLookupX; // maps from source->destination coordinates + private void CreateS2DLookupX() + { + if (this.s2DLookupX == null || this.s2DLookupX.Length != this.SourceSize.Width + 1) + { + this.s2DLookupX = new int[this.SourceSize.Width + 1]; + + for (int x = 0; x < s2DLookupX.Length; ++x) + { + Point pt = new Point(x, 0); + Point clientPt = this.SourceToDestination(pt); + + // Sometimes the scale factor is slightly different on one axis than + // on another, simply due to accuracy. So we have to clamp this value to + // be within bounds. + s2DLookupX[x] = Utility.Clamp(clientPt.X, 0, this.DestinationSize.Width - 1); + } + } + } + + public int[] Src2DstLookupY + { + get + { + lock (this.SyncRoot) + { + CreateS2DLookupY(); + } + + return this.s2DLookupY; + } + } + + private int[] s2DLookupY; // maps from source->destination coordinates + private void CreateS2DLookupY() + { + if (this.s2DLookupY == null || this.s2DLookupY.Length != this.SourceSize.Height + 1) + { + this.s2DLookupY = new int[this.SourceSize.Height + 1]; + + for (int y = 0; y < s2DLookupY.Length; ++y) + { + Point pt = new Point(0, y); + Point clientPt = this.SourceToDestination(pt); + + // Sometimes the scale factor is slightly different on one axis than + // on another, simply due to accuracy. So we have to clamp this value to + // be within bounds. + s2DLookupY[y] = Utility.Clamp(clientPt.Y, 0, this.DestinationSize.Height - 1); + } + } + } + + public void InvalidateLookups() + { + this.s2DLookupX = null; + this.s2DLookupY = null; + this.d2SLookupX = null; + this.d2SLookupY = null; + } + + public void Render(Surface dst, Point offset) + { + foreach (SurfaceBoxRenderer sbr in this.list) + { + if (sbr.Visible) + { + sbr.Render(dst, offset); + } + } + + foreach (SurfaceBoxRenderer sbr in this.topList) + { + if (sbr.Visible) + { + sbr.Render(dst, offset); + } + } + } + + public event InvalidateEventHandler Invalidated; + + public void Invalidate(Rectangle rect) + { + if (Invalidated != null) + { + Invalidated(this, new InvalidateEventArgs(rect)); + } + } + + public void Invalidate() + { + Invalidate(SurfaceBoxRenderer.MaxBounds); + } + + public SurfaceBoxRendererList(Size sourceSize, Size destinationSize) + { + this.list = new SurfaceBoxRenderer[0]; + this.topList = new SurfaceBoxRenderer[0]; + this.sourceSize = sourceSize; + this.destinationSize = destinationSize; + } + } +} diff --git a/src/Core/SwatchControl.cs b/src/Core/SwatchControl.cs new file mode 100644 index 0000000..86abf48 --- /dev/null +++ b/src/Core/SwatchControl.cs @@ -0,0 +1,292 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet +{ + public sealed class SwatchControl + : Control + { + private List colors = new List(); + private const int defaultUnscaledSwatchSize = 12; + private int unscaledSwatchSize = defaultUnscaledSwatchSize; + private bool mouseDown = false; + private int mouseDownIndex = -1; + private bool blinkHighlight = false; + private const int blinkInterval = 500; + private System.Windows.Forms.Timer blinkHighlightTimer; + + [Browsable(false)] + public bool BlinkHighlight + { + get + { + return this.blinkHighlight; + } + + set + { + this.blinkHighlight = value; + this.blinkHighlightTimer.Enabled = value; + Invalidate(); + } + } + + public event EventHandler ColorsChanged; + private void OnColorsChanged() + { + if (ColorsChanged != null) + { + ColorsChanged(this, EventArgs.Empty); + } + } + + [Browsable(false)] + public ColorBgra[] Colors + { + get + { + return this.colors.ToArray(); + } + + set + { + this.colors = new List(value); + this.mouseDown = false; + Invalidate(); + OnColorsChanged(); + } + } + + [DefaultValue(defaultUnscaledSwatchSize)] + [Browsable(true)] + public int UnscaledSwatchSize + { + get + { + return this.unscaledSwatchSize; + } + + set + { + this.unscaledSwatchSize = value; + this.mouseDown = false; + Invalidate(); + } + } + + public event EventHandler>> ColorClicked; + private void OnColorClicked(int index, MouseButtons buttons) + { + if (ColorClicked != null) + { + ColorClicked(this, new EventArgs>(Pair.Create(index, buttons))); + } + } + + public SwatchControl() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.blinkHighlightTimer = new Timer(); + this.blinkHighlightTimer.Tick += new EventHandler(BlinkHighlightTimer_Tick); + this.blinkHighlightTimer.Enabled = false; + this.blinkHighlightTimer.Interval = blinkInterval; + this.DoubleBuffered = true; + this.ResizeRedraw = true; + } + + private void BlinkHighlightTimer_Tick(object sender, EventArgs e) + { + Invalidate(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.blinkHighlightTimer != null) + { + this.blinkHighlightTimer.Dispose(); + this.blinkHighlightTimer = null; + } + } + + base.Dispose(disposing); + } + + private int MouseXYToColorIndex(int x, int y) + { + if (x < 0 || y < 0 || x >= ClientSize.Width || y >= ClientSize.Height) + { + return -1; + } + + int scaledSwatchSize = UI.ScaleWidth(this.unscaledSwatchSize); + int swatchColumns = this.ClientSize.Width / scaledSwatchSize; + int row = y / scaledSwatchSize; + int col = x / scaledSwatchSize; + int index = col + (row * swatchColumns); + + // Make sure they aren't on the last item of a row that actually got clipped off + if (col == swatchColumns) + { + index = -1; + } + + return index; + } + + protected override void OnMouseLeave(EventArgs e) + { + this.mouseDown = false; + Invalidate(); + base.OnMouseLeave(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + this.mouseDown = true; + this.mouseDownIndex = MouseXYToColorIndex(e.X, e.Y); + Invalidate(); + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + int colorIndex = MouseXYToColorIndex(e.X, e.Y); + + if (colorIndex == this.mouseDownIndex && + colorIndex >= 0 && + colorIndex < this.colors.Count) + { + OnColorClicked(colorIndex, e.Button); + } + + this.mouseDown = false; + Invalidate(); + base.OnMouseUp(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + Invalidate(); + base.OnMouseMove(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.CompositingMode = CompositingMode.SourceOver; + int scaledSwatchSize = UI.ScaleWidth(this.unscaledSwatchSize); + int swatchColumns = this.ClientSize.Width / scaledSwatchSize; + + Point mousePt = Control.MousePosition; + mousePt = PointToClient(mousePt); + int activeIndex = MouseXYToColorIndex(mousePt.X, mousePt.Y); + + for (int i = 0; i < this.colors.Count; ++i) + { + ColorBgra c = this.colors[i]; + + int swatchX = i % swatchColumns; + int swatchY = i / swatchColumns; + + Rectangle swatchRect = new Rectangle( + swatchX * scaledSwatchSize, + swatchY * scaledSwatchSize, + scaledSwatchSize, + scaledSwatchSize); + + PushButtonState state; + + if (this.mouseDown) + { + if (i == this.mouseDownIndex) + { + state = PushButtonState.Pressed; + } + else + { + state = PushButtonState.Normal; + } + } + else if (i == activeIndex) + { + state = PushButtonState.Hot; + } + else + { + state = PushButtonState.Normal; + } + + bool drawOutline; + + switch (state) + { + case PushButtonState.Hot: + drawOutline = true; + break; + + case PushButtonState.Pressed: + drawOutline = false; + break; + + case PushButtonState.Default: + case PushButtonState.Disabled: + case PushButtonState.Normal: + drawOutline = false; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + Utility.DrawColorRectangle(e.Graphics, swatchRect, c.ToColor(), drawOutline); + } + + if (this.blinkHighlight) + { + int period = (Math.Abs(Environment.TickCount) / blinkInterval) % 2; + Color color; + + switch (period) + { + case 0: + color = SystemColors.Window; + break; + + case 1: + color = SystemColors.Highlight; + break; + + default: + throw new InvalidOperationException(); + } + + using (Pen pen = new Pen(color)) + { + e.Graphics.DrawRectangle(pen, new Rectangle(0, 0, Width - 1, Height - 1)); + } + } + + base.OnPaint(e); + } + } +} diff --git a/src/Core/TaskButton.cs b/src/Core/TaskButton.cs new file mode 100644 index 0000000..ec5d67a --- /dev/null +++ b/src/Core/TaskButton.cs @@ -0,0 +1,69 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + public sealed class TaskButton + { + private static TaskButton cancel = null; + public static TaskButton Cancel + { + get + { + if (cancel == null) + { + cancel = new TaskButton( + PdnResources.GetImageResource("Icons.CancelIcon.png").Reference, + PdnResources.GetString("TaskButton.Cancel.ActionText"), + PdnResources.GetString("TaskButton.Cancel.ExplanationText")); + } + + return cancel; + } + } + + private Image image; + private string actionText; + private string explanationText; + + public Image Image + { + get + { + return this.image; + } + } + + public string ActionText + { + get + { + return this.actionText; + } + } + + public string ExplanationText + { + get + { + return this.explanationText; + } + } + + public TaskButton(Image image, string actionText, string explanationText) + { + this.image = image; + this.actionText = actionText; + this.explanationText = explanationText; + } + } +} diff --git a/src/Core/TaskDialog.cs b/src/Core/TaskDialog.cs new file mode 100644 index 0000000..4ec8f83 --- /dev/null +++ b/src/Core/TaskDialog.cs @@ -0,0 +1,469 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public static class TaskDialog + { + public static int DefaultPixelWidth96Dpi = 300; + + public static TaskButton Show( + IWin32Window owner, + Icon formIcon, + string formTitle, + Image taskImage, + string introText, + TaskButton[] taskButtons, + TaskButton acceptTaskButton, + TaskButton cancelTaskButton) + { + return Show(owner, formIcon, formTitle, taskImage, true, introText, + taskButtons, acceptTaskButton, cancelTaskButton); + } + + public static TaskButton Show( + IWin32Window owner, + Icon formIcon, + string formTitle, + Image taskImage, + bool scaleTaskImageWithDpi, + string introText, + TaskButton[] taskButtons, + TaskButton acceptTaskButton, + TaskButton cancelTaskButton) + { + return Show(owner, formIcon, formTitle, taskImage, scaleTaskImageWithDpi, introText, + taskButtons, acceptTaskButton, cancelTaskButton, DefaultPixelWidth96Dpi); + } + + public static TaskButton Show( + IWin32Window owner, + Icon formIcon, + string formTitle, + Image taskImage, + bool scaleTaskImageWithDpi, + string introText, + TaskButton[] taskButtons, + TaskButton acceptTaskButton, + TaskButton cancelTaskButton, + int pixelWidth96Dpi) + { + return Show(owner, formIcon, formTitle, taskImage, scaleTaskImageWithDpi, introText, + taskButtons, acceptTaskButton, cancelTaskButton, pixelWidth96Dpi, null, null); + } + + public static TaskButton Show( + IWin32Window owner, + Icon formIcon, + string formTitle, + Image taskImage, + bool scaleTaskImageWithDpi, + string introText, + TaskButton[] taskButtons, + TaskButton acceptTaskButton, + TaskButton cancelTaskButton, + int pixelWidth96Dpi, + string auxButtonText, + EventHandler auxButtonClickHandler) + { + using (TaskDialogForm form = new TaskDialogForm()) + { + form.Icon = formIcon; + form.IntroText = introText; + form.Text = formTitle; + form.TaskImage = taskImage; + form.ScaleTaskImageWithDpi = scaleTaskImageWithDpi; + form.TaskButtons = taskButtons; + form.AcceptTaskButton = acceptTaskButton; + form.CancelTaskButton = cancelTaskButton; + + if (auxButtonText != null) + { + form.AuxButtonText = auxButtonText; + } + + if (auxButtonClickHandler != null) + { + form.AuxButtonClick += auxButtonClickHandler; + } + + int pixelWidth = UI.ScaleWidth(pixelWidth96Dpi); + form.ClientSize = new Size(pixelWidth, form.ClientSize.Height); + + DialogResult dr = form.ShowDialog(owner); + TaskButton result = form.DialogResult; + + return result; + } + } + + private sealed class TaskDialogForm + : PdnBaseForm + { + private PictureBox taskImagePB; + private bool scaleTaskImageWithDpi; + private Label introTextLabel; + private TaskButton[] taskButtons; + private CommandButton[] commandButtons; + private HeaderLabel separator; + private TaskButton acceptTaskButton; + private TaskButton cancelTaskButton; + private TaskButton dialogResult; + + private Button auxButton; + + public string AuxButtonText + { + get + { + return this.auxButton.Text; + } + + set + { + this.auxButton.Text = value; + PerformLayout(); + } + } + + public event EventHandler AuxButtonClick; + private void OnAuxButtonClick() + { + if (AuxButtonClick != null) + { + AuxButtonClick(this, EventArgs.Empty); + } + } + + public new TaskButton DialogResult + { + get + { + return this.dialogResult; + } + } + + public Image TaskImage + { + get + { + return this.taskImagePB.Image; + } + + set + { + this.taskImagePB.Image = value; + PerformLayout(); + Invalidate(true); + } + } + + public bool ScaleTaskImageWithDpi + { + get + { + return this.scaleTaskImageWithDpi; + } + + set + { + this.scaleTaskImageWithDpi = value; + PerformLayout(); + Invalidate(true); + } + } + + public string IntroText + { + get + { + return this.introTextLabel.Text; + } + + set + { + this.introTextLabel.Text = value; + PerformLayout(); + Invalidate(true); + } + } + + public TaskButton[] TaskButtons + { + get + { + return (TaskButton[])this.taskButtons.Clone(); + } + + set + { + this.taskButtons = (TaskButton[])value.Clone(); + InitCommandButtons(); + PerformLayout(); + Invalidate(true); + } + } + + public TaskButton AcceptTaskButton + { + get + { + return this.acceptTaskButton; + } + + set + { + this.acceptTaskButton = value; + + IButtonControl newAcceptButton = null; + + for (int i = 0; i < this.commandButtons.Length; ++i) + { + TaskButton asTaskButton = this.commandButtons[i].Tag as TaskButton; + + if (this.acceptTaskButton == asTaskButton) + { + newAcceptButton = this.commandButtons[i]; + } + } + + AcceptButton = newAcceptButton; + } + } + + public TaskButton CancelTaskButton + { + get + { + return this.cancelTaskButton; + } + + set + { + this.cancelTaskButton = value; + + IButtonControl newCancelButton = null; + + for (int i = 0; i < this.commandButtons.Length; ++i) + { + TaskButton asTaskButton = this.commandButtons[i].Tag as TaskButton; + + if (this.cancelTaskButton == asTaskButton) + { + newCancelButton = this.commandButtons[i]; + } + } + + CancelButton = newCancelButton; + } + } + + public TaskDialogForm() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + SuspendLayout(); + this.introTextLabel = new Label(); + this.auxButton = new Button(); + this.taskImagePB = new PictureBox(); + this.separator = new HeaderLabel(); + // + // introTextLabel + // + this.introTextLabel.Name = "introTextLabel"; + // + // taskImagePB + // + this.taskImagePB.Name = "taskImagePB"; + this.taskImagePB.SizeMode = PictureBoxSizeMode.StretchImage; + // + // auxButton + // + this.auxButton.Name = "auxButton"; + this.auxButton.AutoSize = true; + this.auxButton.FlatStyle = FlatStyle.System; + this.auxButton.Visible = false; + this.auxButton.Click += + delegate(object sender, EventArgs e) + { + OnAuxButtonClick(); + }; + // + // separator + // + this.separator.Name = "separator"; + this.separator.RightMargin = 0; + // + // TaskDialogForm + // + this.Name = "TaskDialogForm"; + this.ClientSize = new Size(300, 100); + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MinimizeBox = false; + this.MaximizeBox = false; + this.ShowInTaskbar = false; + this.StartPosition = FormStartPosition.CenterParent; + this.Controls.Add(this.introTextLabel); + this.Controls.Add(this.taskImagePB); + this.Controls.Add(this.auxButton); + this.Controls.Add(this.separator); + ResumeLayout(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int leftMargin = UI.ScaleWidth(8); + int rightMargin = UI.ScaleWidth(8); + int topMargin = UI.ScaleHeight(8); + int bottomMargin = UI.ScaleHeight(8); + int imageToIntroHMargin = UI.ScaleWidth(8); + int topSectionToLinksVMargin = UI.ScaleHeight(8); + int commandButtonVMargin = UI.ScaleHeight(0); + int afterCommandButtonsVMargin = UI.ScaleHeight(8); + int insetWidth = ClientSize.Width - leftMargin - rightMargin; + + if (this.taskImagePB.Image == null) + { + this.taskImagePB.Location = new Point(0, topMargin); + this.taskImagePB.Size = new Size(0, 0); + this.taskImagePB.Visible = false; + } + else + { + this.taskImagePB.Location = new Point(leftMargin, topMargin); + + if (this.scaleTaskImageWithDpi) + { + this.taskImagePB.Size = UI.ScaleSize(this.taskImagePB.Image.Size); + } + else + { + this.taskImagePB.Size = this.taskImagePB.Image.Size; + } + + this.taskImagePB.Visible = true; + } + + this.introTextLabel.Location = new Point(this.taskImagePB.Right + imageToIntroHMargin, this.taskImagePB.Top); + this.introTextLabel.Width = ClientSize.Width - this.introTextLabel.Left - rightMargin; + this.introTextLabel.Height = this.introTextLabel.GetPreferredSize(new Size(this.introTextLabel.Width, 1)).Height; + + int y = Math.Max(this.taskImagePB.Bottom, this.introTextLabel.Bottom); + y += topSectionToLinksVMargin; + + if (!string.IsNullOrEmpty(this.auxButton.Text)) + { + this.auxButton.Visible = true; + this.auxButton.Location = new Point(leftMargin, y); + this.auxButton.PerformLayout(); + y += this.auxButton.Height; + y += topSectionToLinksVMargin; + } + else + { + this.auxButton.Visible = false; + } + + if (this.commandButtons != null) + { + this.separator.Location = new Point(leftMargin, y); + this.separator.Width = insetWidth; + y += this.separator.Height; + + for (int i = 0; i < this.commandButtons.Length; ++i) + { + this.commandButtons[i].Location = new Point(leftMargin, y); + this.commandButtons[i].Width = insetWidth; + this.commandButtons[i].PerformLayout(); + y += this.commandButtons[i].Height + commandButtonVMargin; + } + + y += afterCommandButtonsVMargin; + } + + this.ClientSize = new Size(ClientSize.Width, y); + base.OnLayout(levent); + } + + private void InitCommandButtons() + { + SuspendLayout(); + + if (this.commandButtons != null) + { + foreach (CommandButton commandButton in this.commandButtons) + { + Controls.Remove(commandButton); + commandButton.Tag = null; + commandButton.Click -= CommandButton_Click; + commandButton.Dispose(); + } + + this.commandButtons = null; + } + + this.commandButtons = new CommandButton[this.taskButtons.Length]; + + IButtonControl newAcceptButton = null; + IButtonControl newCancelButton = null; + + for (int i = 0; i < this.commandButtons.Length; ++i) + { + TaskButton taskButton = this.taskButtons[i]; + CommandButton commandButton = new CommandButton(); + + commandButton.ActionText = taskButton.ActionText; + commandButton.ActionImage = taskButton.Image; + commandButton.AutoSize = true; + commandButton.ExplanationText = taskButton.ExplanationText; + commandButton.Tag = taskButton; + commandButton.Click += CommandButton_Click; + + this.commandButtons[i] = commandButton; + Controls.Add(commandButton); + + if (this.acceptTaskButton == taskButton) + { + newAcceptButton = commandButton; + } + + if (this.cancelTaskButton == taskButton) + { + newCancelButton = commandButton; + } + } + + AcceptButton = newAcceptButton; + CancelButton = newCancelButton; + + if (newAcceptButton != null && newAcceptButton is Control) + { + ((Control)newAcceptButton).Select(); + } + + ResumeLayout(); + } + + private void CommandButton_Click(object sender, EventArgs e) + { + CommandButton commandButton = (CommandButton)sender; + this.dialogResult = (TaskButton)commandButton.Tag; + Close(); + } + } + } +} \ No newline at end of file diff --git a/src/Core/Threading/ThreadPool.cs b/src/Core/Threading/ThreadPool.cs new file mode 100644 index 0000000..4891be3 --- /dev/null +++ b/src/Core/Threading/ThreadPool.cs @@ -0,0 +1,207 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Threading; + +namespace PaintDotNet.Threading +{ + /// + /// Uses the .NET ThreadPool to do our own type of thread pool. The main difference + /// here is that we limit our usage of the thread pool, and that we can also drain + /// the threads we have ("fence"). The default maximum number of threads is + /// Processor.LogicalCpuCount. + /// + public class ThreadPool + { + private static ThreadPool global = new ThreadPool(2 * Processor.LogicalCpuCount); + public static ThreadPool Global + { + get + { + return global; + } + } + + private ArrayList exceptions = ArrayList.Synchronized(new ArrayList()); + private bool useFXTheadPool; + + public static int MinimumCount + { + get + { + return WaitableCounter.MinimumCount; + } + } + + public static int MaximumCount + { + get + { + return WaitableCounter.MaximumCount; + } + } + + public Exception[] Exceptions + { + get + { + return (Exception[])this.exceptions.ToArray(typeof(Exception)); + } + } + + public void ClearExceptions() + { + exceptions.Clear(); + } + + public void DrainExceptions() + { + if (this.exceptions.Count > 0) + { + throw new WorkerThreadException("Worker thread threw an exception", (Exception)this.exceptions[0]); + } + + ClearExceptions(); + } + + private WaitableCounter counter; + + public ThreadPool() + : this(Processor.LogicalCpuCount) + { + } + + public ThreadPool(int maxThreads) + : this(maxThreads, true) + { + } + + public ThreadPool(int maxThreads, bool useFXThreadPool) + { + if (maxThreads < MinimumCount || maxThreads > MaximumCount) + { + throw new ArgumentOutOfRangeException("maxThreads", "must be between " + MinimumCount.ToString() + " and " + MaximumCount.ToString() + " inclusive"); + } + + this.counter = new WaitableCounter(maxThreads); + this.useFXTheadPool = useFXThreadPool; + } + + /* + private sealed class FunctionCallTrampoline + { + private Delegate theDelegate; + private object[] parameters; + + public void WaitCallback(object ignored) + { + theDelegate.DynamicInvoke(this.parameters); + } + + public FunctionCallTrampoline(Delegate theDelegate, object[] parameters) + { + this.theDelegate = theDelegate; + this.parameters = parameters; + } + } + + public void QueueFunctionCall(Delegate theDelegate, params object[] parameters) + { + FunctionCallTrampoline fct = new FunctionCallTrampoline(theDelegate, parameters); + QueueUserWorkItem(fct.WaitCallback, null); + } + */ + + public void QueueUserWorkItem(WaitCallback callback) + { + QueueUserWorkItem(callback, null); + } + + public void QueueUserWorkItem(WaitCallback callback, object state) + { + IDisposable token = counter.AcquireToken(); + ThreadWrapperContext twc = new ThreadWrapperContext(callback, state, token, this.exceptions); + + if (this.useFXTheadPool) + { + System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(twc.ThreadWrapper), twc); + } + else + { + Thread thread = new Thread(new ThreadStart(twc.ThreadWrapper)); + thread.IsBackground = true; + thread.Start(); + } + } + + public bool IsDrained(uint msTimeout) + { + bool result = counter.IsEmpty(msTimeout); + + if (result) + { + Drain(); + } + + return result; + } + + public bool IsDrained() + { + return IsDrained(0); + } + + public void Drain() + { + counter.WaitForEmpty(); + DrainExceptions(); + } + + private sealed class ThreadWrapperContext + { + private WaitCallback callback; + private object context; + private IDisposable counterToken; + private ArrayList exceptionsBucket; + + public ThreadWrapperContext(WaitCallback callback, object context, + IDisposable counterToken, ArrayList exceptionsBucket) + { + this.callback = callback; + this.context = context; + this.counterToken = counterToken; + this.exceptionsBucket = exceptionsBucket; + } + + public void ThreadWrapper() + { + using (IDisposable token = this.counterToken) + { + try + { + this.callback(this.context); + } + + catch (Exception ex) + { + this.exceptionsBucket.Add(ex); + } + } + } + + public void ThreadWrapper(object state) + { + ThreadWrapper(); + } + } + } +} diff --git a/src/Core/Threading/WaitableCounter.cs b/src/Core/Threading/WaitableCounter.cs new file mode 100644 index 0000000..c93e512 --- /dev/null +++ b/src/Core/Threading/WaitableCounter.cs @@ -0,0 +1,137 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Diagnostics; +using System.Threading; + +namespace PaintDotNet.Threading +{ + /// + /// Threading primitive that allows you to "count" and to wait on two conditions: + /// 1. Empty -- this is when we have not dished out any "tokens" + /// 2. NotFull -- this is when we currently have 1 or more "tokens" out in the wild + /// Note that the tokens given by Acquire() *must* be disposed. Otherwise things + /// won't work right! + /// + public class WaitableCounter + { + /// + /// The minimum value that may be passed to the constructor for initialization. + /// + public static int MinimumCount + { + get + { + return WaitHandleArray.MinimumCount; + } + } + + /// + /// The maximum value that may be passed to the construct for initialization. + /// + public static int MaximumCount + { + get + { + return WaitHandleArray.MaximumCount; + } + } + + private sealed class CounterToken + : IDisposable + { + private WaitableCounter parent; + private int index; + + public int Index + { + get + { + return this.index; + } + } + + public CounterToken(WaitableCounter parent, int index) + { + this.parent = parent; + this.index = index; + } + + public void Dispose() + { + parent.Release(this); + } + } + + private WaitHandleArray freeEvents; // each of these is signaled (set) when the corresponding slot is 'free' + private WaitHandleArray inUseEvents; // each of these is signaled (set) when the corresponding slot is 'in use' + + private object theLock; + + public WaitableCounter(int maxCount) + { + if (maxCount < 1 || maxCount > 64) + { + throw new ArgumentOutOfRangeException("maxCount", "must be between 1 and 64, inclusive"); + } + + this.freeEvents = new WaitHandleArray(maxCount); + this.inUseEvents = new WaitHandleArray(maxCount); + + for (int i = 0; i < maxCount; ++i) + { + this.freeEvents[i] = new ManualResetEvent(true); + this.inUseEvents[i] = new ManualResetEvent(false); + } + + this.theLock = new object(); + } + + private void Release(CounterToken token) + { + ((ManualResetEvent)this.inUseEvents[token.Index]).Reset(); + ((ManualResetEvent)this.freeEvents[token.Index]).Set(); + } + + public IDisposable AcquireToken() + { + lock (this.theLock) + { + int index = WaitForNotFull(); + ((ManualResetEvent)this.freeEvents[index]).Reset(); + ((ManualResetEvent)this.inUseEvents[index]).Set(); + return new CounterToken(this, index); + } + } + + public bool IsEmpty() + { + return IsEmpty(0); + } + + public bool IsEmpty(uint msTimeout) + { + return freeEvents.AreAllSignaled(msTimeout); + } + + public void WaitForEmpty() + { + freeEvents.WaitAll(); + } + + public int WaitForNotFull() + { + int returnVal = freeEvents.WaitAny(); + return returnVal; + } + + } +} diff --git a/src/Core/ThumbnailManager.cs b/src/Core/ThumbnailManager.cs new file mode 100644 index 0000000..c7e3952 --- /dev/null +++ b/src/Core/ThumbnailManager.cs @@ -0,0 +1,392 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Threading; +using System.Text; + +namespace PaintDotNet +{ + using ThumbnailReadyArgs = EventArgs>; + using ThumbnailReadyHandler = EventHandler>>; + using ThumbnailStackItem = Triple>>, int>; + using ThumbnailReadyEventDetails = Triple>>, object, EventArgs>>; + + // TODO: Add calls to VerifyNotDispose() for the next release where we can get enough testing for it + + public sealed class ThumbnailManager + : IDisposable + { + private bool disposed = false; + private int updateLatency = 67; + + public bool IsDisposed + { + get + { + return this.disposed; + } + } + + public int UpdateLatency + { + get + { + return this.updateLatency; + } + + set + { + this.updateLatency = value; + } + } + + private Stack renderQueue; + private ISynchronizeInvoke syncContext; + private Thread renderThread; + private volatile bool quitRenderThread; + private object updateLock; + + /* + private void VerifyNotDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException("ThumbnailManager"); + } + } + * */ + + // This event is non-signaled during the period of time between when the rendering thread has popped + // an item from the renderQueue, and when it finishes holding a reference to it (i.e. either it + // finishes rendering the thumbnail, or it decides not to). At all other times, this event is signaled. + private ManualResetEvent renderingInactive; + + private List thumbnailReadyInvokeList = + new List(); + + private void DrainThumbnailReadyInvokeList() + { + List invokeListCopy = null; + + lock (this.thumbnailReadyInvokeList) + { + invokeListCopy = this.thumbnailReadyInvokeList; + this.thumbnailReadyInvokeList = new List(); + } + + foreach (ThumbnailReadyEventDetails invokeMe in invokeListCopy) + { + invokeMe.First.Invoke(invokeMe.Second, invokeMe.Third); + } + } + + private void OnThumbnailReady(IThumbnailProvider dw, ThumbnailReadyHandler callback, Surface thumb) + { + Pair data = Pair.Create(dw, thumb); + ThumbnailReadyArgs e = new ThumbnailReadyArgs(data); + + lock (this.thumbnailReadyInvokeList) + { + this.thumbnailReadyInvokeList.Add(new ThumbnailReadyEventDetails(callback, this, e)); + } + + try + { + this.syncContext.BeginInvoke(new Procedure(DrainThumbnailReadyInvokeList), null); + } + + catch (ObjectDisposedException) + { + // Ignore this error + } + + catch (InvalidOperationException) + { + // If syncContext was destroyed, then ignore + } + } + + public ThumbnailManager(ISynchronizeInvoke syncContext) + { + this.syncContext = syncContext; + this.updateLock = new object(); + this.quitRenderThread = false; + this.renderQueue = new Stack(); + this.renderingInactive = new ManualResetEvent(true); + this.renderThread = new Thread(new ThreadStart(RenderThread)); + this.renderThread.Start(); + } + + ~ThumbnailManager() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + this.disposed = true; + + if (disposing) + { + this.quitRenderThread = true; + + lock (this.updateLock) + { + Monitor.Pulse(this.updateLock); + } + + if (this.renderThread != null) + { + this.renderThread.Join(); + this.renderThread = null; + } + + if (this.renderingInactive != null) + { + this.renderingInactive.Close(); + this.renderingInactive = null; + } + } + } + + public void DrainQueue() + { + int oldLatency = this.updateLatency; + this.updateLatency = 0; + + int count = 1; + while (count > 0) + { + lock (this.updateLock) + { + count = this.renderQueue.Count; + } + + this.renderingInactive.WaitOne(); + } + + this.updateLatency = oldLatency; + DrainThumbnailReadyInvokeList(); + } + + public void RemoveFromQueue(IThumbnailProvider nukeMe) + { + lock (this.updateLock) + { + ThumbnailStackItem[] tsiArray = this.renderQueue.ToArray(); + List tsiAccumulate = new List(); + + for (int i = 0; i < tsiArray.Length; ++i) + { + if (tsiArray[i].First != nukeMe) + { + tsiAccumulate.Add(tsiArray[i]); + } + } + + this.renderQueue.Clear(); + + for (int i = 0; i < tsiAccumulate.Count; ++i) + { + this.renderQueue.Push(tsiAccumulate[i]); + } + } + } + + public void ClearQueue() + { + int oldUpdateLatency = this.updateLatency; + this.updateLatency = 0; + + lock (this.updateLock) + { + this.renderQueue.Clear(); + } + + this.renderingInactive.WaitOne(); + this.updateLatency = oldUpdateLatency; + } + + public void QueueThumbnailUpdate(IThumbnailProvider updateMe, int thumbSideLength, ThumbnailReadyHandler callback) + { + if (thumbSideLength < 1) + { + throw new ArgumentOutOfRangeException("thumbSideLength", "must be greater than or equal to 1"); + } + + lock (this.updateLock) + { + bool doIt = false; + ThumbnailStackItem addMe = new ThumbnailStackItem(updateMe, callback, thumbSideLength); + + if (this.renderQueue.Count == 0) + { + doIt = true; + } + else + { + ThumbnailStackItem top = this.renderQueue.Peek(); + + if (addMe != top) + { + doIt = true; + } + } + + // Only add this item to the queue if the item is not already at the top of the queue + if (doIt) + { + this.renderQueue.Push(addMe); + } + + Monitor.Pulse(this.updateLock); + } + } + + private void RenderThread() + { + try + { + RenderThreadImpl(); + } + + finally + { + this.renderingInactive.Set(); + } + } + + private void RenderThreadImpl() + { + while (true) + { + ThumbnailStackItem renderMe = new ThumbnailStackItem(); + + // Wait for either a new item to render, or a signal to quit + lock (this.updateLock) + { + if (this.quitRenderThread) + { + return; + } + + while (this.renderQueue.Count == 0) + { + Monitor.Wait(this.updateLock); + + if (this.quitRenderThread) + { + return; + } + } + + this.renderingInactive.Reset(); + renderMe = this.renderQueue.Pop(); + } + + // Sleep for a short while. Our main goal is to ensure that the + // item is not re-queued for updating very soon after we start + // rendering it. + Thread.Sleep(this.updateLatency); + + bool doRender = true; + + // While we were asleep, ensure that this same item has not been + // re-added to the render queue. If it has, we will skip rendering + // it. This covers the scenario where an item is updated many + // times in rapid succession, in which case we want to wait until + // the item has settled down before spending any CPU time rendering + // its thumbnail. + lock (this.updateLock) + { + if (this.quitRenderThread) + { + return; + } + + if (this.renderQueue.Count > 0) + { + if (renderMe == this.renderQueue.Peek()) + { + doRender = false; + } + } + } + + if (doRender) + { + try + { + Surface thumb; + + using (new ThreadBackground(ThreadBackgroundFlags.All)) + { + thumb = renderMe.First.RenderThumbnail(renderMe.Third); + } + + // If this same item has already been re-queued for an update, then throw + // away what we just rendered. Otherwise we may get flickering as the + // item was being updated while we were rendering the preview. + bool discard = false; + + lock (this.updateLock) + { + if (this.quitRenderThread) + { + thumb.Dispose(); + thumb = null; + return; + } + + if (this.renderQueue.Count > 0) + { + if (renderMe == this.renderQueue.Peek()) + { + discard = true; + thumb.Dispose(); + thumb = null; + } + } + } + + if (!discard) + { + OnThumbnailReady(renderMe.First, renderMe.Second, thumb); + } + } + + catch (Exception ex) + { + try + { + Tracing.Ping("Exception in RenderThread while calling CreateThumbnail: " + ex.ToString()); + } + + catch (Exception) + { + } + } + } + + this.renderingInactive.Set(); + } + } + } +} \ No newline at end of file diff --git a/src/Core/UnaryPixelOp.cs b/src/Core/UnaryPixelOp.cs new file mode 100644 index 0000000..f892170 --- /dev/null +++ b/src/Core/UnaryPixelOp.cs @@ -0,0 +1,151 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Threading; + +namespace PaintDotNet +{ + /// + /// Defines a way to operate on a pixel, or a region of pixels, in a unary fashion. + /// That is, it is a simple function F that takes one parameter and returns a + /// result of the form: d = F(c) + /// + [Serializable] + public unsafe abstract class UnaryPixelOp + : PixelOp + { + public abstract ColorBgra Apply(ColorBgra color); + + public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) + { + unsafe + { + while (length > 0) + { + *dst = Apply(*src); + ++dst; + ++src; + --length; + } + } + } + + public unsafe virtual void Apply(ColorBgra* ptr, int length) + { + unsafe + { + while (length > 0) + { + *ptr = Apply(*ptr); + ++ptr; + --length; + } + } + } + + private unsafe void ApplyRectangle(Surface surface, Rectangle rect) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra *ptr = surface.GetPointAddress(rect.Left, y); + Apply(ptr, rect.Width); + } + } + + public void Apply(Surface surface, Rectangle[] roi, int startIndex, int length) + { + Rectangle regionBounds = Utility.GetRegionBounds(roi, startIndex, length); + + if (regionBounds != Rectangle.Intersect(surface.Bounds, regionBounds)) + { + throw new ArgumentOutOfRangeException("roi", "Region is out of bounds"); + } + + unsafe + { + for (int x = startIndex; x < startIndex + length; ++x) + { + ApplyRectangle(surface, roi[x]); + } + } + } + + public void Apply(Surface surface, Rectangle[] roi) + { + Apply(surface, roi, 0, roi.Length); + } + + public void Apply(Surface surface, RectangleF[] roiF, int startIndex, int length) + { + Rectangle regionBounds = Rectangle.Truncate(Utility.GetRegionBounds(roiF, startIndex, length)); + + if (regionBounds != Rectangle.Intersect(surface.Bounds, regionBounds)) + { + throw new ArgumentOutOfRangeException("roiF", "Region is out of bounds"); + } + + unsafe + { + for (int x = startIndex; x < startIndex + length; ++x) + { + ApplyRectangle(surface, Rectangle.Truncate(roiF[x])); + } + } + } + + public void Apply(Surface surface, RectangleF[] roiF) + { + Apply(surface, roiF, 0, roiF.Length); + } + + public unsafe void Apply(Surface surface, Rectangle roi) + { + ApplyRectangle(surface, roi); + } + + public void Apply(Surface surface, Scanline scan) + { + Apply(surface.GetPointAddress(scan.X, scan.Y), scan.Length); + } + + public void Apply(Surface surface, Scanline[] scans) + { + foreach (Scanline scan in scans) + { + Apply(surface, scan); + } + } + + public override void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int scanLength) + { + Apply(dst.GetPointAddress(dstOffset), src.GetPointAddress(srcOffset), scanLength); + } + + public void Apply(Surface dst, Surface src, Rectangle roi) + { + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra *dstPtr = dst.GetPointAddress(roi.Left, y); + ColorBgra *srcPtr = src.GetPointAddress(roi.Left, y); + Apply(dstPtr, srcPtr, roi.Width); + } + } + + public void Apply(Surface surface, PdnRegion roi) + { + Apply(surface, roi.GetRegionScansReadOnlyInt()); + } + + public UnaryPixelOp() + { + } + } +} diff --git a/src/Core/UnaryPixelOps.cs b/src/Core/UnaryPixelOps.cs new file mode 100644 index 0000000..5dd9bb6 --- /dev/null +++ b/src/Core/UnaryPixelOps.cs @@ -0,0 +1,883 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + /// + /// Provides a set of standard UnaryPixelOps. + /// + public sealed class UnaryPixelOps + { + private UnaryPixelOps() + { + } + + /// + /// Passes through the given color value. + /// result(color) = color + /// + [Serializable] + public class Identity + : UnaryPixelOp + { + public override ColorBgra Apply(ColorBgra color) + { + return color; + } + + public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) + { + Memory.Copy(dst, src, (ulong)length * (ulong)sizeof(ColorBgra)); + } + + public unsafe override void Apply(ColorBgra* ptr, int length) + { + return; + } + } + + /// + /// Always returns a constant color. + /// + [Serializable] + public class Constant + : UnaryPixelOp + { + private ColorBgra setColor; + + public override ColorBgra Apply(ColorBgra color) + { + return setColor; + } + + public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (length > 0) + { + *dst = setColor; + ++dst; + --length; + } + } + + public unsafe override void Apply(ColorBgra* ptr, int length) + { + while (length > 0) + { + *ptr = setColor; + ++ptr; + --length; + } + } + + public Constant(ColorBgra setColor) + { + this.setColor = setColor; + } + } + + /// + /// Blends pixels with the specified constant color. + /// + [Serializable] + public class BlendConstant + : UnaryPixelOp + { + private ColorBgra blendColor; + + public override ColorBgra Apply(ColorBgra color) + { + int a = blendColor.A; + int invA = 255 - a; + + int r = ((color.R * invA) + (blendColor.R * a)) / 256; + int g = ((color.G * invA) + (blendColor.G * a)) / 256; + int b = ((color.B * invA) + (blendColor.B * a)) / 256; + byte a2 = ComputeAlpha(color.A, blendColor.A); + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, a2); + } + + public BlendConstant(ColorBgra blendColor) + { + this.blendColor = blendColor; + } + } + + /// + /// Used to set a given channel of a pixel to a given, predefined color. + /// Useful if you want to set only the alpha value of a given region. + /// + [Serializable] + public class SetChannel + : UnaryPixelOp + { + private int channel; + private byte setValue; + + public override ColorBgra Apply(ColorBgra color) + { + color[channel] = setValue; + return color; + } + + public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (length > 0) + { + *dst = *src; + (*dst)[channel] = setValue; + ++dst; + ++src; + --length; + } + } + + public override unsafe void Apply(ColorBgra* ptr, int length) + { + while (length > 0) + { + (*ptr)[channel] = setValue; + ++ptr; + --length; + } + } + + + public SetChannel(int channel, byte setValue) + { + this.channel = channel; + this.setValue = setValue; + } + } + + /// + /// Specialization of SetChannel that sets the alpha channel. + /// + /// This class depends on the system being litte-endian with the alpha channel + /// occupying the 8 most-significant-bits of a ColorBgra instance. + /// By the way, we use addition instead of bitwise-OR because an addition can be + /// perform very fast (0.5 cycles) on a Pentium 4. + [Serializable] + public class SetAlphaChannel + : UnaryPixelOp + { + private UInt32 addValue; + + public override ColorBgra Apply(ColorBgra color) + { + return ColorBgra.FromUInt32((color.Bgra & 0x00ffffff) + addValue); + } + + public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (length > 0) + { + dst->Bgra = (src->Bgra & 0x00ffffff) + addValue; + ++dst; + ++src; + --length; + } + } + + public override unsafe void Apply(ColorBgra* ptr, int length) + { + while (length > 0) + { + ptr->Bgra = (ptr->Bgra & 0x00ffffff) + addValue; + ++ptr; + --length; + } + } + + public SetAlphaChannel(byte alphaValue) + { + addValue = (uint)alphaValue << 24; + } + } + + /// + /// Specialization of SetAlphaChannel that always sets alpha to 255. + /// + [Serializable] + public class SetAlphaChannelTo255 + : UnaryPixelOp + { + public override ColorBgra Apply(ColorBgra color) + { + return ColorBgra.FromUInt32(color.Bgra | 0xff000000); + } + + public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (length > 0) + { + dst->Bgra = src->Bgra | 0xff000000; + ++dst; + ++src; + --length; + } + } + + public override unsafe void Apply(ColorBgra* ptr, int length) + { + while (length > 0) + { + ptr->Bgra |= 0xff000000; + ++ptr; + --length; + } + } + } + + /// + /// Inverts a pixel's color, and passes through the alpha component. + /// + [Serializable] + public class Invert + : UnaryPixelOp + { + public override ColorBgra Apply(ColorBgra color) + { + return ColorBgra.FromBgra((byte)(255 - color.B), (byte)(255 - color.G), (byte)(255 - color.R), color.A); + } + } + + /// + /// If the color is within the red tolerance, remove it + /// + [Serializable] + public class RedEyeRemove + : UnaryPixelOp + { + private int tolerence; + private double setSaturation; + + public RedEyeRemove(int tol, int sat) + { + tolerence = tol; + setSaturation = (double)sat / 100; + } + + public override ColorBgra Apply(ColorBgra color) + { + // The higher the saturation, the more red it is + int saturation = GetSaturation(color); + + // The higher the difference between the other colors, the more red it is + int difference = color.R - Math.Max(color.B,color.G); + + // If it is within tolerence, and the saturation is high + if ((difference > tolerence) && (saturation > 100)) + { + double i = 255.0 * color.GetIntensity(); + byte ib = (byte)(i * setSaturation); // adjust the red color for user inputted saturation + return ColorBgra.FromBgra((byte)color.B,(byte)color.G, ib, color.A); + } + else + { + return color; + } + } + + //Saturation formula from RgbColor.cs, public HsvColor ToHsv() + private int GetSaturation(ColorBgra color) + { + double min; + double max; + double delta; + + double r = (double) color.R / 255; + double g = (double) color.G / 255; + double b = (double) color.B / 255; + + double s; + + min = Math.Min(Math.Min(r, g), b); + max = Math.Max(Math.Max(r, g), b); + delta = max - min; + + if (max == 0 || delta == 0) + { + // R, G, and B must be 0, or all the same. + // In this case, S is 0, and H is undefined. + // Using H = 0 is as good as any... + s = 0; + } + else + { + s = delta / max; + } + + return (int)(s * 255); + } + } + + /// + /// Inverts a pixel's color and its alpha component. + /// + [Serializable] + public class InvertWithAlpha + : UnaryPixelOp + { + public override ColorBgra Apply(ColorBgra color) + { + return ColorBgra.FromBgra((byte)(255 - color.B), (byte)(255 - color.G), (byte)(255 - color.R), (byte)(255 - color.A)); + } + } + + /// + /// Averages the input color's red, green, and blue channels. The alpha component + /// is unaffected. + /// + [Serializable] + public class AverageChannels + : UnaryPixelOp + { + public override ColorBgra Apply(ColorBgra color) + { + byte average = (byte)(((int)color.R + (int)color.G + (int)color.B) / 3); + return ColorBgra.FromBgra(average, average, average, color.A); + } + } + + [Serializable] + public class Desaturate + : UnaryPixelOp + { + public override ColorBgra Apply(ColorBgra color) + { + byte i = color.GetIntensityByte(); + return ColorBgra.FromBgra(i, i, i, color.A); + } + + public unsafe override void Apply(ColorBgra* ptr, int length) + { + while (length > 0) + { + byte i = ptr->GetIntensityByte(); + + ptr->R = i; + ptr->G = i; + ptr->B = i; + + ++ptr; + --length; + } + } + + public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (length > 0) + { + byte i = src->GetIntensityByte(); + + dst->B = i; + dst->G = i; + dst->R = i; + dst->A = src->A; + + ++dst; + ++src; + --length; + } + } + } + + [Serializable] + public class LuminosityCurve + : UnaryPixelOp + { + public byte[] Curve = new byte[256]; + + public LuminosityCurve() + { + for (int i = 0; i < 256; ++i) + { + Curve[i] = (byte)i; + } + } + + public override ColorBgra Apply(ColorBgra color) + { + byte lumi = color.GetIntensityByte(); + int diff = Curve[lumi] - lumi; + + return ColorBgra.FromBgraClamped( + color.B + diff, + color.G + diff, + color.R + diff, + color.A); + } + } + + [Serializable] + public class ChannelCurve + : UnaryPixelOp + { + public byte[] CurveB = new byte[256]; + public byte[] CurveG = new byte[256]; + public byte[] CurveR = new byte[256]; + + public ChannelCurve() + { + for (int i = 0; i < 256; ++i) + { + CurveB[i] = (byte)i; + CurveG[i] = (byte)i; + CurveR[i] = (byte)i; + } + } + + public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (--length >= 0) + { + dst->B = CurveB[src->B]; + dst->G = CurveG[src->G]; + dst->R = CurveR[src->R]; + dst->A = src->A; + + ++dst; + ++src; + } + } + + public override unsafe void Apply(ColorBgra* ptr, int length) + { + while (--length >= 0) + { + ptr->B = CurveB[ptr->B]; + ptr->G = CurveG[ptr->G]; + ptr->R = CurveR[ptr->R]; + + ++ptr; + } + } + + public override ColorBgra Apply(ColorBgra color) + { + return ColorBgra.FromBgra(CurveB[color.B], CurveG[color.G], CurveR[color.R], color.A); + } + + public override void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int scanLength) + { + base.Apply (dst, dstOffset, src, srcOffset, scanLength); + } + } + + [Serializable] + public class Level + : ChannelCurve, + ICloneable + { + private ColorBgra colorInLow; + public ColorBgra ColorInLow + { + get + { + return colorInLow; + } + + set + { + if (value.R == 255) + { + value.R = 254; + } + + if (value.G == 255) + { + value.G = 254; + } + + if (value.B == 255) + { + value.B = 254; + } + + if (colorInHigh.R < value.R + 1) + { + colorInHigh.R = (byte)(value.R + 1); + } + + if (colorInHigh.G < value.G + 1) + { + colorInHigh.G = (byte)(value.R + 1); + } + + if (colorInHigh.B < value.B + 1) + { + colorInHigh.B = (byte)(value.R + 1); + } + + colorInLow = value; + UpdateLookupTable(); + } + } + + private ColorBgra colorInHigh; + public ColorBgra ColorInHigh + { + get + { + return colorInHigh; + } + + set + { + if (value.R == 0) + { + value.R = 1; + } + + if (value.G == 0) + { + value.G = 1; + } + + if (value.B == 0) + { + value.B = 1; + } + + if (colorInLow.R > value.R - 1) + { + colorInLow.R = (byte)(value.R - 1); + } + + if (colorInLow.G > value.G - 1) + { + colorInLow.G = (byte)(value.R - 1); + } + + if (colorInLow.B > value.B - 1) + { + colorInLow.B = (byte)(value.R - 1); + } + + colorInHigh = value; + UpdateLookupTable(); + } + } + + private ColorBgra colorOutLow; + public ColorBgra ColorOutLow + { + get + { + return colorOutLow; + } + + set + { + if (value.R == 255) + { + value.R = 254; + } + + if (value.G == 255) + { + value.G = 254; + } + + if (value.B == 255) + { + value.B = 254; + } + + if (colorOutHigh.R < value.R + 1) + { + colorOutHigh.R = (byte)(value.R + 1); + } + + if (colorOutHigh.G < value.G + 1) + { + colorOutHigh.G = (byte)(value.G + 1); + } + + if (colorOutHigh.B < value.B + 1) + { + colorOutHigh.B = (byte)(value.B + 1); + } + + colorOutLow = value; + UpdateLookupTable(); + } + } + + private ColorBgra colorOutHigh; + public ColorBgra ColorOutHigh + { + get + { + return colorOutHigh; + } + + set + { + if (value.R == 0) + { + value.R = 1; + } + + if (value.G == 0) + { + value.G = 1; + } + + if (value.B == 0) + { + value.B = 1; + } + + if (colorOutLow.R > value.R - 1) + { + colorOutLow.R = (byte)(value.R - 1); + } + + if (colorOutLow.G > value.G - 1) + { + colorOutLow.G = (byte)(value.G - 1); + } + + if (colorOutLow.B > value.B - 1) + { + colorOutLow.B = (byte)(value.B - 1); + } + + colorOutHigh = value; + UpdateLookupTable(); + } + } + + private float[] gamma = new float[3]; + public float GetGamma(int index) + { + if (index < 0 || index >= 3) + { + throw new ArgumentOutOfRangeException("index", index, "Index must be between 0 and 2"); + } + + return gamma[index]; + } + + public void SetGamma(int index, float val) + { + if (index < 0 || index >= 3) + { + throw new ArgumentOutOfRangeException("index", index, "Index must be between 0 and 2"); + } + + gamma[index] = Utility.Clamp(val, 0.1f, 10.0f); + UpdateLookupTable(); + } + + public bool isValid = true; + + public static Level AutoFromLoMdHi(ColorBgra lo, ColorBgra md, ColorBgra hi) + { + float[] gamma = new float[3]; + + for (int i = 0; i < 3; i++) + { + if (lo[i] < md[i] && md[i] < hi[i]) + { + gamma[i] = (float)Utility.Clamp(Math.Log(0.5, (float)(md[i] - lo[i]) / (float)(hi[i] - lo[i])), 0.1, 10.0); + } + else + { + gamma[i] = 1.0f; + } + } + + return new Level(lo, hi, gamma, ColorBgra.FromColor(Color.Black), ColorBgra.FromColor(Color.White)); + } + + private void UpdateLookupTable() + { + for (int i = 0; i < 3; i++) + { + if (colorOutHigh[i] < colorOutLow[i] || + colorInHigh[i] <= colorInLow[i] || + gamma[i] < 0) + { + isValid = false; + return; + } + + for (int j = 0; j < 256; j++) + { + ColorBgra col = Apply(j, j, j); + CurveB[j] = col.B; + CurveG[j] = col.G; + CurveR[j] = col.R; + } + } + } + + public Level() + : this(ColorBgra.FromColor(Color.Black), + ColorBgra.FromColor(Color.White), + new float[] { 1, 1, 1 }, + ColorBgra.FromColor(Color.Black), + ColorBgra.FromColor(Color.White)) + { + } + + public Level(ColorBgra in_lo, ColorBgra in_hi, float[] gamma, ColorBgra out_lo, ColorBgra out_hi) + { + colorInLow = in_lo; + colorInHigh = in_hi; + colorOutLow = out_lo; + colorOutHigh = out_hi; + + if (gamma.Length != 3) + { + throw new ArgumentException("gamma", "gamma must be a float[3]"); + } + + this.gamma = gamma; + UpdateLookupTable(); + } + + public ColorBgra Apply(float r, float g, float b) + { + ColorBgra ret = new ColorBgra(); + float[] input = new float[] { b, g, r }; + + for (int i = 0; i < 3; i++) + { + float v = (input[i] - colorInLow[i]); + + if (v < 0) + { + ret[i] = colorOutLow[i]; + } + else if (v + colorInLow[i] >= colorInHigh[i]) + { + ret[i] = colorOutHigh[i]; + } + else + { + ret[i] = (byte)Utility.Clamp( + colorOutLow[i] + (colorOutHigh[i] - colorOutLow[i]) * Math.Pow(v / (colorInHigh[i] - colorInLow[i]), gamma[i]), + 0.0f, + 255.0f); + } + } + + return ret; + } + + public void UnApply(ColorBgra after, float[] beforeOut, float[] slopesOut) + { + if (beforeOut.Length != 3) + { + throw new ArgumentException("before must be a float[3]", "before"); + } + + if (slopesOut.Length != 3) + { + throw new ArgumentException("slopes must be a float[3]", "slopes"); + } + + for (int i = 0; i < 3; i++) + { + beforeOut[i] = colorInLow[i] + (colorInHigh[i] - colorInLow[i]) * + (float)Math.Pow((float)(after[i] - colorOutLow[i]) / (colorOutHigh[i] - colorOutLow[i]), 1 / gamma[i]); + + slopesOut[i] = (float)(colorInHigh[i] - colorInLow[i]) / ((colorOutHigh[i] - colorOutLow[i]) * gamma[i]) * + (float)Math.Pow((float)(after[i] - colorOutLow[i]) / (colorOutHigh[i] - colorOutLow[i]), 1 / gamma[i] - 1); + + if (float.IsInfinity(slopesOut[i]) || float.IsNaN(slopesOut[i])) + { + slopesOut[i] = 0; + } + } + } + + public object Clone() + { + Level copy = new Level(colorInLow, colorInHigh, (float[])gamma.Clone(), colorOutLow, colorOutHigh); + + copy.CurveB = (byte[])this.CurveB.Clone(); + copy.CurveG = (byte[])this.CurveG.Clone(); + copy.CurveR = (byte[])this.CurveR.Clone(); + + return copy; + } + } + + [Serializable] + public class HueSaturationLightness + : UnaryPixelOp + { + private int hueDelta; + private int satFactor; + private UnaryPixelOp blendOp; + + public HueSaturationLightness(int hueDelta, int satDelta, int lightness) + { + this.hueDelta = hueDelta; + this.satFactor = (satDelta * 1024) / 100; + + if (lightness == 0) + { + blendOp = new UnaryPixelOps.Identity(); + } + else if (lightness > 0) + { + blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(255, 255, 255, (byte)((lightness * 255) / 100))); + } + else // if (lightness < 0) + { + blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(0, 0, 0, (byte)((-lightness * 255) / 100))); + } + } + + public override ColorBgra Apply(ColorBgra color) + { + //adjust saturation + byte intensity = color.GetIntensityByte(); + color.R = Utility.ClampToByte((intensity * 1024 + (color.R - intensity) * satFactor) >> 10); + color.G = Utility.ClampToByte((intensity * 1024 + (color.G - intensity) * satFactor) >> 10); + color.B = Utility.ClampToByte((intensity * 1024 + (color.B - intensity) * satFactor) >> 10); + + HsvColor hsvColor = HsvColor.FromColor(color.ToColor()); + int hue = hsvColor.Hue; + + hue += hueDelta; + + while (hue < 0) + { + hue += 360; + } + + while (hue > 360) + { + hue -= 360; + } + + hsvColor.Hue = hue; + + ColorBgra newColor = ColorBgra.FromColor(hsvColor.ToColor()); + newColor = blendOp.Apply(newColor); + newColor.A = color.A; + + return newColor; + } + } + } +} diff --git a/src/Core/UnitsComboBox.cs b/src/Core/UnitsComboBox.cs new file mode 100644 index 0000000..fa74920 --- /dev/null +++ b/src/Core/UnitsComboBox.cs @@ -0,0 +1,140 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class UnitsComboBox + : UserControl, + IUnitsComboBox + { + private ComboBox comboBox; + private UnitsComboBoxHandler comboBoxHandler; + + public UnitsComboBox() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + this.comboBoxHandler = new UnitsComboBoxHandler(this.comboBox); + } + + private void InitializeComponent() + { + this.comboBox = new ComboBox(); + this.comboBox.Dock = DockStyle.Fill; + this.comboBox.FlatStyle = FlatStyle.System; + this.Controls.Add(this.comboBox); + } + + public UnitsDisplayType UnitsDisplayType + { + get + { + return this.comboBoxHandler.UnitsDisplayType; + } + + set + { + this.comboBoxHandler.UnitsDisplayType = value; + } + } + + public bool LowercaseStrings + { + get + { + return this.comboBoxHandler.LowercaseStrings; + } + + set + { + this.comboBoxHandler.LowercaseStrings = value; + } + } + + public MeasurementUnit Units + { + get + { + return this.comboBoxHandler.Units; + } + + set + { + this.comboBoxHandler.Units = value; + } + } + + public string UnitsText + { + get + { + return this.comboBoxHandler.UnitsText; + } + } + + public bool PixelsAvailable + { + get + { + return this.comboBoxHandler.PixelsAvailable; + } + + set + { + this.comboBoxHandler.PixelsAvailable = value; + } + } + + public bool InchesAvailable + { + get + { + return this.comboBoxHandler.InchesAvailable; + } + } + + public bool CentimetersAvailable + { + get + { + return this.comboBoxHandler.CentimetersAvailable; + } + } + + public void RemoveUnit(MeasurementUnit removeMe) + { + this.comboBoxHandler.AddUnit(removeMe); + } + + public void AddUnit(MeasurementUnit addMe) + { + this.comboBoxHandler.AddUnit(addMe); + } + + public event EventHandler UnitsChanged + { + add + { + this.comboBoxHandler.UnitsChanged += value; + } + + remove + { + this.comboBoxHandler.UnitsChanged -= value; + } + } + } +} diff --git a/src/Core/UnitsComboBox.resx b/src/Core/UnitsComboBox.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/UnitsComboBoxHandler.cs b/src/Core/UnitsComboBoxHandler.cs new file mode 100644 index 0000000..f95d79e --- /dev/null +++ b/src/Core/UnitsComboBoxHandler.cs @@ -0,0 +1,304 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class UnitsComboBoxHandler + : IUnitsComboBox + { + private ComboBox comboBox; + + [Browsable(false)] + public ComboBox ComboBox + { + get + { + return this.comboBox; + } + } + + public UnitsComboBoxHandler(ComboBox comboBox) + { + this.comboBox = comboBox; + this.comboBox.DropDownStyle = ComboBoxStyle.DropDownList; + this.comboBox.SelectedIndexChanged += ComboBox_SelectedIndexChanged; + ReloadItems(); + } + + private bool lowercase = true; + + private Hashtable unitsToString; + private Hashtable stringToUnits; + + // maps from MeasurementUnit->bool for whether that item should be in the list or not + private Hashtable measurementItems; + + private UnitsDisplayType unitsDisplayType = UnitsDisplayType.Plural; + + [DefaultValue(UnitsDisplayType.Plural)] + public UnitsDisplayType UnitsDisplayType + { + get + { + return this.unitsDisplayType; + } + + set + { + if (this.unitsDisplayType != value) + { + this.unitsDisplayType = value; + ReloadItems(); + } + } + } + + [DefaultValue(true)] + public bool LowercaseStrings + { + get + { + return this.lowercase; + } + + set + { + if (this.lowercase != value) + { + this.lowercase = value; + ReloadItems(); + } + } + } + + [DefaultValue(MeasurementUnit.Pixel)] + public MeasurementUnit Units + { + get + { + object selected = this.stringToUnits[ComboBox.SelectedItem]; + return (MeasurementUnit)selected; + } + + set + { + object selectMe = this.unitsToString[value]; + ComboBox.SelectedItem = selectMe; + } + } + + [Browsable(false)] + public string UnitsText + { + get + { + if (ComboBox.SelectedItem == null) + { + return string.Empty; + } + else + { + return (string)ComboBox.SelectedItem; + } + } + } + + [DefaultValue(true)] + public bool PixelsAvailable + { + get + { + return (bool)this.measurementItems[MeasurementUnit.Pixel]; + } + + set + { + if (value != this.PixelsAvailable) + { + if (value) + { + AddUnit(MeasurementUnit.Pixel); + } + else + { + if (this.Units == MeasurementUnit.Pixel) + { + if (this.InchesAvailable) + { + this.Units = MeasurementUnit.Inch; + } + else if (this.CentimetersAvailable) + { + this.Units = MeasurementUnit.Centimeter; + } + } + + RemoveUnit(MeasurementUnit.Pixel); + } + } + } + } + + [DefaultValue(true)] + public bool InchesAvailable + { + get + { + return (bool)this.measurementItems[MeasurementUnit.Inch]; + } + } + + [DefaultValue(true)] + public bool CentimetersAvailable + { + get + { + return (bool)this.measurementItems[MeasurementUnit.Centimeter]; + } + } + + public void RemoveUnit(MeasurementUnit removeMe) + { + InitMeasurementItems(); + this.measurementItems[removeMe] = false; + ReloadItems(); + } + + public void AddUnit(MeasurementUnit addMe) + { + InitMeasurementItems(); + this.measurementItems[addMe] = true; + ReloadItems(); + } + + private void InitMeasurementItems() + { + if (this.measurementItems == null) + { + this.measurementItems = new Hashtable(); + this.measurementItems.Add(MeasurementUnit.Pixel, true); + this.measurementItems.Add(MeasurementUnit.Centimeter, true); + this.measurementItems.Add(MeasurementUnit.Inch, true); + } + } + + private void ReloadItems() + { + string suffix; + + switch (this.unitsDisplayType) + { + case UnitsDisplayType.Plural: + suffix = ".Plural"; + break; + + case UnitsDisplayType.Singular: + suffix = string.Empty; + break; + + case UnitsDisplayType.Ratio: + suffix = ".Ratio"; + break; + + default: + throw new InvalidEnumArgumentException("UnitsDisplayType"); + } + + InitMeasurementItems(); + + MeasurementUnit oldUnits; + + if (this.unitsToString == null) + { + oldUnits = MeasurementUnit.Pixel; + } + else + { + oldUnits = this.Units; + } + + ComboBox.Items.Clear(); + + string pixelsString = PdnResources.GetString("MeasurementUnit.Pixel" + suffix); + string inchesString = PdnResources.GetString("MeasurementUnit.Inch" + suffix); + string centimetersString = PdnResources.GetString("MeasurementUnit.Centimeter" + suffix); + + if (lowercase) + { + // TODO: we shouldn't really be using ToLower() here, these should be separately localizable strings + + pixelsString = pixelsString.ToLower(); + inchesString = inchesString.ToLower(); + centimetersString = centimetersString.ToLower(); + } + + this.unitsToString = new Hashtable(); + this.unitsToString.Add(MeasurementUnit.Pixel, pixelsString); + this.unitsToString.Add(MeasurementUnit.Inch, inchesString); + this.unitsToString.Add(MeasurementUnit.Centimeter, centimetersString); + + this.stringToUnits = new Hashtable(); + + if ((bool)this.measurementItems[MeasurementUnit.Pixel]) + { + this.stringToUnits.Add(pixelsString, MeasurementUnit.Pixel); + ComboBox.Items.Add(pixelsString); + } + + if ((bool)this.measurementItems[MeasurementUnit.Inch]) + { + this.stringToUnits.Add(inchesString, MeasurementUnit.Inch); + ComboBox.Items.Add(inchesString); + } + + if ((bool)this.measurementItems[MeasurementUnit.Centimeter]) + { + this.stringToUnits.Add(centimetersString, MeasurementUnit.Centimeter); + ComboBox.Items.Add(centimetersString); + } + + if (!(bool)this.measurementItems[oldUnits]) + { + if (ComboBox.Items.Count == 0) + { + ComboBox.SelectedItem = null; + } + else + { + ComboBox.SelectedIndex = 0; + } + } + else + { + this.Units = oldUnits; + } + } + + public event EventHandler UnitsChanged; + + private void OnUnitsChanged() + { + if (UnitsChanged != null) + { + UnitsChanged(this, EventArgs.Empty); + } + } + + private void ComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + this.OnUnitsChanged(); + } + } +} diff --git a/src/Core/UnitsComboBoxStrip.cs b/src/Core/UnitsComboBoxStrip.cs new file mode 100644 index 0000000..d6721e9 --- /dev/null +++ b/src/Core/UnitsComboBoxStrip.cs @@ -0,0 +1,127 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class UnitsComboBoxStrip + : ToolStripComboBox, + IUnitsComboBox + { + private UnitsComboBoxHandler comboBoxHandler; + + public UnitsComboBoxStrip() + { + this.comboBoxHandler = new UnitsComboBoxHandler(this.ComboBox); + } + + public UnitsDisplayType UnitsDisplayType + { + get + { + return this.comboBoxHandler.UnitsDisplayType; + } + + set + { + this.comboBoxHandler.UnitsDisplayType = value; + } + } + + public bool LowercaseStrings + { + get + { + return this.comboBoxHandler.LowercaseStrings; + } + + set + { + this.comboBoxHandler.LowercaseStrings = value; + } + } + + public MeasurementUnit Units + { + get + { + return this.comboBoxHandler.Units; + } + + set + { + this.comboBoxHandler.Units = value; + } + } + + public string UnitsText + { + get + { + return this.comboBoxHandler.UnitsText; + } + } + + public bool PixelsAvailable + { + get + { + return this.comboBoxHandler.PixelsAvailable; + } + + set + { + this.comboBoxHandler.PixelsAvailable = value; + } + } + + public bool InchesAvailable + { + get + { + return this.comboBoxHandler.InchesAvailable; + } + } + + public bool CentimetersAvailable + { + get + { + return this.comboBoxHandler.CentimetersAvailable; + } + } + + public void RemoveUnit(MeasurementUnit removeMe) + { + this.comboBoxHandler.AddUnit(removeMe); + } + + public void AddUnit(MeasurementUnit addMe) + { + this.comboBoxHandler.AddUnit(addMe); + } + + public event EventHandler UnitsChanged + { + add + { + this.comboBoxHandler.UnitsChanged += value; + } + + remove + { + this.comboBoxHandler.UnitsChanged -= value; + } + } + } +} diff --git a/src/Core/UnitsDisplayType.cs b/src/Core/UnitsDisplayType.cs new file mode 100644 index 0000000..3ec17a7 --- /dev/null +++ b/src/Core/UnitsDisplayType.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum UnitsDisplayType + { + Singular, + Plural, + Ratio + } +} diff --git a/src/Core/UserControl2.cs b/src/Core/UserControl2.cs new file mode 100644 index 0000000..a703bea --- /dev/null +++ b/src/Core/UserControl2.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public abstract class UserControl2 + : UserControl + { + public abstract bool IsMouseCaptured(); + } +} diff --git a/src/Core/Utility.cs b/src/Core/Utility.cs new file mode 100644 index 0000000..8334c1d --- /dev/null +++ b/src/Core/Utility.cs @@ -0,0 +1,3273 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using PaintDotNet.Threading; +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Net; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Defines miscellaneous constants and static functions. + /// + /// // TODO: refactor into mini static classes + public sealed class Utility + { + private Utility() + { + } + + internal static bool IsNumber(float x) + { + return x >= float.MinValue && x <= float.MaxValue; + } + + internal static bool IsNumber(double x) + { + return x >= double.MinValue && x <= double.MaxValue; + } + + internal static int Min(int val0, params int[] vals) + { + int min = val0; + + for (int i = 0; i < vals.Length; ++i) + { + if (vals[i] < min) + { + min = vals[i]; + } + } + + return min; + } + + internal static int Max(int val0, params int[] vals) + { + int max = val0; + + for (int i = 0; i < vals.Length; ++i) + { + if (vals[i] > max) + { + max = vals[i]; + } + } + + return max; + } + + public static PointF[] GetRgssOffsets(int quality) + { + unsafe + { + int sampleCount = quality * quality; + PointF[] samplesArray = new PointF[sampleCount]; + + fixed (PointF* pSamplesArray = samplesArray) + { + GetRgssOffsets(pSamplesArray, sampleCount, quality); + } + + return samplesArray; + } + } + + public static unsafe void GetRgssOffsets(PointF* samplesArray, int sampleCount, int quality) + { + if (sampleCount < 1) + { + throw new ArgumentOutOfRangeException("sampleCount", "sampleCount must be [0, int.MaxValue]"); + } + + if (sampleCount != quality * quality) + { + throw new ArgumentOutOfRangeException("sampleCount != (quality * quality)"); + } + + if (sampleCount == 1) + { + samplesArray[0] = new PointF(0.0f, 0.0f); + } + else + { + for (int i = 0; i < sampleCount; ++i) + { + double y = (i + 1d) / (sampleCount + 1d); + double x = y * quality; + + x -= (int)x; + + samplesArray[i] = new PointF((float)(x - 0.5d), (float)(y - 0.5d)); + } + } + } + + public static bool IsObsolete(Type type, bool inherit) + { + object[] attrs = type.GetCustomAttributes(typeof(ObsoleteAttribute), inherit); + return (attrs.Length != 0); + } + + public static void DrawDropShadow1px(Graphics g, Rectangle rect) + { + Brush b0 = new SolidBrush(Color.FromArgb(15, Color.Black)); + Brush b1 = new SolidBrush(Color.FromArgb(47, Color.Black)); + Pen p2 = new Pen(Color.FromArgb(63, Color.Black)); + + g.FillRectangle(b0, rect.Left, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Left + 1, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Left, rect.Top + 1, 1, 1); + + g.FillRectangle(b0, rect.Right - 1, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Right - 2, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Right - 1, rect.Top + 1, 1, 1); + + g.FillRectangle(b0, rect.Left, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Left + 1, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Left, rect.Bottom - 2, 1, 1); + + g.FillRectangle(b0, rect.Right - 1, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Right - 2, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Right - 1, rect.Bottom - 2, 1, 1); + + g.DrawLine(p2, rect.Left + 2, rect.Top, rect.Right - 3, rect.Top); + g.DrawLine(p2, rect.Left, rect.Top + 2, rect.Left, rect.Bottom - 3); + g.DrawLine(p2, rect.Left + 2, rect.Bottom - 1, rect.Right - 3, rect.Bottom - 1); + g.DrawLine(p2, rect.Right - 1, rect.Top + 2, rect.Right - 1, rect.Bottom - 3); + + b0.Dispose(); + b0 = null; + b1.Dispose(); + b1 = null; + p2.Dispose(); + p2 = null; + } + + public static Keys LetterOrDigitCharToKeys(char c) + { + if (c >= 'a' && c <= 'z') + { + return (Keys)((int)(c - 'a') + (int)Keys.A); + } + else if (c >= 'A' && c <= 'Z') + { + return (Keys)((int)(c - 'A') + (int)Keys.A); + } + else if (c >= '0' && c <= '9') + { + return (Keys)((int)(c - '0') + (int)Keys.D0); + } + else + { + return Keys.None; + } + } + + public static Control FindFocus() + { + foreach (Form form in Application.OpenForms) + { + Control focused = FindFocus(form); + + if (focused != null) + { + return focused; + } + } + + return null; + } + + private static Control FindFocus(Control c) + { + if (c.Focused) + { + return c; + } + + foreach (Control child in c.Controls) + { + Control f = FindFocus(child); + + if (f != null) + { + return f; + } + } + + return null; + } + + public static void DrawColorRectangle(Graphics g, Rectangle rect, Color color, bool drawBorder) + { + int inflateAmt = drawBorder ? -2 : 0; + Rectangle colorRectangle = Rectangle.Inflate(rect, inflateAmt, inflateAmt); + Brush colorBrush = new LinearGradientBrush(colorRectangle, Color.FromArgb(255, color), color, 90.0f, false); + HatchBrush backgroundBrush = new HatchBrush(HatchStyle.LargeCheckerBoard, Color.FromArgb(191, 191, 191), Color.FromArgb(255, 255, 255)); + + if (drawBorder) + { + g.DrawRectangle(Pens.Black, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1); + g.DrawRectangle(Pens.White, rect.Left + 1, rect.Top + 1, rect.Width - 3, rect.Height - 3); + } + + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.Half; + g.FillRectangle(backgroundBrush, colorRectangle); + g.FillRectangle(colorBrush, colorRectangle); + g.PixelOffsetMode = oldPOM; + + backgroundBrush.Dispose(); + colorBrush.Dispose(); + } + + public static Size ComputeThumbnailSize(Size originalSize, int maxEdgeLength) + { + Size thumbSize; + + if (originalSize.Width > originalSize.Height) + { + int longSide = Math.Min(originalSize.Width, maxEdgeLength); + thumbSize = new Size(longSide, Math.Max(1, (originalSize.Height * longSide) / originalSize.Width)); + } + else if (originalSize.Height > originalSize.Width) + { + int longSide = Math.Min(originalSize.Height, maxEdgeLength); + thumbSize = new Size(Math.Max(1, (originalSize.Width * longSide) / originalSize.Height), longSide); + } + else // if (docSize.Width == docSize.Height) + { + int longSide = Math.Min(originalSize.Width, maxEdgeLength); + thumbSize = new Size(longSide, longSide); + } + + return thumbSize; + } + + public static bool IsClipboardImageAvailable() + { + try + { + return System.Windows.Forms.Clipboard.ContainsImage() || + System.Windows.Forms.Clipboard.ContainsData(DataFormats.EnhancedMetafile); + } + + catch (ExternalException) + { + return false; + } + } + + public static Font CreateFont(string name, float size, FontStyle style) + { + Font returnFont; + + try + { + returnFont = new Font(name, size, style); + } + + catch (Exception) + { + returnFont = new Font(FontFamily.GenericSansSerif, size); + } + + return returnFont; + } + + public static Font CreateFont(string name, float size, string backupName, float backupSize, FontStyle style) + { + Font returnFont; + + try + { + returnFont = new Font(name, size, style); + } + + catch (Exception) + { + returnFont = CreateFont(backupName, backupSize, style); + } + + return returnFont; + } + + public static readonly Color TransparentKey = Color.FromArgb(192, 192, 192); + + public static string WebExceptionToErrorMessage(WebException wex) + { + string errorMessage; + + switch (wex.Status) + { + case WebExceptionStatus.ProtocolError: + string format = PdnResources.GetString("WebExceptionStatus.ProtocolError.Format"); + HttpStatusCode statusCode = ((HttpWebResponse)wex.Response).StatusCode; + errorMessage = string.Format(format, statusCode.ToString(), (int)statusCode); + break; + + default: + string stringName = "WebExceptionStatus." + wex.Status.ToString(); + errorMessage = PdnResources.GetString(stringName); + break; + } + + return errorMessage; + } + + private static bool allowGCFullCollect = true; + public static bool AllowGCFullCollect + { + get + { + return allowGCFullCollect; + } + + set + { + allowGCFullCollect = value; + } + } + + public static void GCFullCollect() + { + if (AllowGCFullCollect) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + + private static int defaultSimplificationFactor = 50; + public static int DefaultSimplificationFactor + { + get + { + return defaultSimplificationFactor; + } + + set + { + defaultSimplificationFactor = value; + } + } + + public static bool IsArrowKey(Keys keyData) + { + Keys key = keyData & Keys.KeyCode; + + if (key == Keys.Up || key == Keys.Down || key == Keys.Left || key == Keys.Right) + { + return true; + } + else + { + return false; + } + } + + public static bool DoesControlHaveMouseCaptured(Control control) + { + bool result = false; + + result |= control.Capture; + + foreach (Control c in control.Controls) + { + result |= DoesControlHaveMouseCaptured(c); + } + + return result; + } + + public static void SplitRectangle(Rectangle rect, Rectangle[] rects) + { + int height = rect.Height; + + for (int i = 0; i < rects.Length; ++i) + { + Rectangle newRect = Rectangle.FromLTRB(rect.Left, + rect.Top + ((height * i) / rects.Length), + rect.Right, + rect.Top + ((height * (i + 1)) / rects.Length)); + + rects[i] = newRect; + } + } + + public static long TicksToMs(long ticks) + { + return ticks / 10000; + } + + public static string GetStaticName(Type type) + { + PropertyInfo pi = type.GetProperty("StaticName", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty); + return (string)pi.GetValue(null, null); + } + + public static readonly float[][] Identity5x5F = new float[][] { + new float[] { 1, 0, 0, 0, 0 }, + new float[] { 0, 1, 0, 0, 0 }, + new float[] { 0, 0, 1, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }; + + public static readonly ColorMatrix IdentityColorMatrix = new ColorMatrix(Identity5x5F); + + [ThreadStatic] + private static Matrix identityMatrix = null; + public static Matrix IdentityMatrix + { + get + { + if (identityMatrix == null) + { + identityMatrix = new Matrix(); + identityMatrix.Reset(); + } + + return identityMatrix; + } + } + + /// + /// Rounds an integer to the smallest power of 2 that is greater + /// than or equal to it. + /// + public static int Log2RoundUp(int x) + { + if (x == 0) + { + return 1; + } + + if (x == 1) + { + return 1; + } + + return 1 << (1 + HighestBit(x - 1)); + } + + private static int HighestBit(int x) + { + if (x == 0) + { + return 0; + } + + int b = 0; + int hi = 0; + + while (b <= 30) + { + if ((x & (1 << b)) != 0) + { + hi = b; + } + + ++b; + } + + return hi; + } + + private int CountBits(int x) + { + uint y = (uint)x; + int count = 0; + + for (int bit = 0; bit < 32; ++bit) + { + if ((y & ((uint)1 << bit)) != 0) + { + ++count; + } + } + + return count; + } + + public static string RemoveSpaces(string s) + { + StringBuilder sb = new StringBuilder(); + + foreach (char c in s) + { + if (!char.IsWhiteSpace(c)) + { + sb.Append(c); + } + } + + return sb.ToString(); + } + + public static int Max(int[,] array) + { + int max = int.MinValue; + + for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) + { + for (int j = array.GetLowerBound(1); j <= array.GetUpperBound(1); ++j) + { + if (array[i,j] > max) + { + max = array[i,j]; + } + } + } + + return max; + } + + public static int Sum(int[][] array) + { + int sum = 0; + + for (int i = 0; i < array.Length; ++i) + { + int[] row = array[i]; + + for (int j = 0; j < row.Length; ++j) + { + sum += row[j]; + } + } + + return sum; + } + + // TODO: obsolete these NUD funcitons, move them into PdnNumericUpDown + public static void ClipNumericUpDown(NumericUpDown upDown) + { + if (upDown.Value < upDown.Minimum) + { + upDown.Value = upDown.Minimum; + } + else if (upDown.Value > upDown.Maximum) + { + upDown.Value = upDown.Maximum; + } + } + + public static bool GetUpDownValueFromText(NumericUpDown nud, out double val) + { + if (nud.Text == string.Empty) + { + val = 0; + return false; + } + else + { + try + { + if (nud.DecimalPlaces == 0) + { + val = (double)int.Parse(nud.Text); + } + else + { + val = double.Parse(nud.Text); + } + } + + catch + { + val = 0; + return false; + } + + return true; + } + } + + public static bool CheckNumericUpDown(NumericUpDown upDown) + { + int a; + bool result = int.TryParse(upDown.Text, out a); + + if (result && (a <= (int)upDown.Maximum) && (a >= (int)upDown.Minimum)) + { + return true; + } + else + { + return false; + } + } + + public static void SetNumericUpDownValue(NumericUpDown upDown, decimal newValue) + { + if (upDown.Value != newValue) + { + upDown.Value = newValue; + } + } + + public static void SetNumericUpDownValue(NumericUpDown upDown, int newValue) + { + SetNumericUpDownValue(upDown, (decimal)newValue); + } + + public static string SizeStringFromBytes(long bytes) + { + double bytesDouble = (double)bytes; + string toStringFormat; + string formatString; + + if (bytesDouble > (1024 * 1024 * 1024)) + { + // Gigs + bytesDouble /= 1024 * 1024 * 1024; + toStringFormat = "F1"; + formatString = PdnResources.GetString("Utility.SizeStringFromBytes.GBFormat"); + } + else if (bytesDouble > (1024 * 1024)) + { + // Megs + bytesDouble /= 1024 * 1024; + toStringFormat = "F1"; + formatString = PdnResources.GetString("Utility.SizeStringFromBytes.MBFormat"); + } + else if (bytesDouble > (1024)) + { + // K + bytesDouble /= 1024; + toStringFormat = "F1"; + formatString = PdnResources.GetString("Utility.SizeStringFromBytes.KBFormat"); + } + else + { + // Bytes + toStringFormat = "F0"; + formatString = PdnResources.GetString("Utility.SizeStringFromBytes.BytesFormat"); + } + + string bytesString = bytesDouble.ToString(toStringFormat); + string sizeString = string.Format(formatString, bytesString); + + return sizeString; + } + + public static void ShowWiaError(IWin32Window owner) + { + // WIA requires Windows XP SP1 or later, or Windows Server 2003 + // So if we know they're on WS2k3, we tell them to enable WIA. + // If they're on XP or later, tell them that WIA isn't available. + // Otherwise we tell them they need XP SP1 (for the Win2K folks). + if (OS.Type == OSType.Server) + { + Utility.ErrorBox(owner, PdnResources.GetString("WIA.Error.EnableMe")); + } + else + { + Utility.ErrorBox(owner, PdnResources.GetString("WIA.Error.UnableToLoad")); + } + } + + public static void ShowNonAdminErrorBox(IWin32Window parent) + { + ErrorBox(parent, PdnResources.GetString("NonAdminErrorBox.Message")); + } + + public static void ErrorBox(IWin32Window parent, string message) + { + MessageBox.Show(parent, message, PdnInfo.GetBareProductName(), MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + public static DialogResult ErrorBoxOKCancel(IWin32Window parent, string message) + { + return MessageBox.Show(parent, message, PdnInfo.GetBareProductName(), MessageBoxButtons.OKCancel, MessageBoxIcon.Error); + } + + public static void InfoBox(IWin32Window parent, string message) + { + MessageBox.Show(parent, message, PdnInfo.GetBareProductName(), MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + public static DialogResult InfoBoxOKCancel(IWin32Window parent, string message) + { + return MessageBox.Show(parent, message, PdnInfo.GetBareProductName(), MessageBoxButtons.OKCancel, MessageBoxIcon.Information); + } + + public static DialogResult AskOKCancel(IWin32Window parent, string question) + { + return MessageBox.Show(parent, question, PdnInfo.GetBareProductName(), MessageBoxButtons.OKCancel, MessageBoxIcon.Question); + } + + public static DialogResult AskYesNo(IWin32Window parent, string question) + { + return MessageBox.Show(parent, question, PdnInfo.GetBareProductName(), MessageBoxButtons.YesNo, MessageBoxIcon.Question); + } + + public static DialogResult AskYesNoCancel(IWin32Window parent, string question) + { + return MessageBox.Show(parent, question, PdnInfo.GetBareProductName(), MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); + } + + public static Icon ImageToIcon(Image image) + { + return ImageToIcon(image, Utility.TransparentKey); + } + + public static Icon ImageToIcon(Image image, bool disposeImage) + { + return ImageToIcon(image, Utility.TransparentKey, disposeImage); + } + + public static Icon ImageToIcon(Image image, Color seeThru) + { + return ImageToIcon(image, seeThru, false); + } + + /// + /// Converts an Image to an Icon. + /// + /// The Image to convert to an icon. Must be an appropriate icon size (32x32, 16x16, etc). + /// The color that will be treated as transparent in the icon. + /// Whether or not to dispose the passed-in Image. + /// An Icon representation of the Image. + public static Icon ImageToIcon(Image image, Color seeThru, bool disposeImage) + { + Bitmap bitmap = new Bitmap(image); + + for (int y = 0; y < bitmap.Height; ++y) + { + for (int x = 0; x < bitmap.Width; ++x) + { + if (bitmap.GetPixel(x, y) == seeThru) + { + bitmap.SetPixel(x, y, Color.FromArgb(0)); + } + } + } + + Icon icon = Icon.FromHandle(bitmap.GetHicon()); + bitmap.Dispose(); + + if (disposeImage) + { + image.Dispose(); + } + + return icon; + } + + public static Icon BitmapToIcon(Bitmap bitmap, bool disposeBitmap) + { + Icon icon = Icon.FromHandle(bitmap.GetHicon()); + + if (disposeBitmap) + { + bitmap.Dispose(); + } + + return icon; + } + + public static Icon SurfaceToIcon(Surface surface, bool disposeSurface) + { + Bitmap bitmap = surface.CreateAliasedBitmap(); + Icon icon = Icon.FromHandle(bitmap.GetHicon()); + + bitmap.Dispose(); + + if (disposeSurface) + { + surface.Dispose(); + } + + return icon; + } + + public static Point GetRectangleCenter(Rectangle rect) + { + return new Point((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2); + } + + public static PointF GetRectangleCenter(RectangleF rect) + { + return new PointF((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2); + } + + public static Scanline[] GetRectangleScans(Rectangle rect) + { + Scanline[] scans = new Scanline[rect.Height]; + + for (int y = 0; y < rect.Height; ++y) + { + scans[y] = new Scanline(rect.X, rect.Y + y, rect.Width); + } + + return scans; + } + + public static Scanline[] GetRegionScans(Rectangle[] region) + { + int scanCount = 0; + + for (int i = 0; i < region.Length; ++i) + { + scanCount += region[i].Height; + } + + Scanline[] scans = new Scanline[scanCount]; + int scanIndex = 0; + + foreach (Rectangle rect in region) + { + for (int y = 0; y < rect.Height; ++y) + { + scans[scanIndex] = new Scanline(rect.X, rect.Y + y, rect.Width); + ++scanIndex; + } + } + + return scans; + } + + public static Rectangle[] ScanlinesToRectangles(Scanline[] scans) + { + return ScanlinesToRectangles(scans, 0, scans.Length); + } + + public static Rectangle[] ScanlinesToRectangles(Scanline[] scans, int startIndex, int length) + { + Rectangle[] rects = new Rectangle[length]; + + for (int i = 0; i < length; ++i) + { + Scanline scan = scans[i + startIndex]; + rects[i] = new Rectangle(scan.X, scan.Y, scan.Length, 1); + } + + return rects; + } + + /// + /// Found on Google Groups when searching for "Region.Union" while looking + /// for bugs: + /// --- + /// Hello, + /// + /// I did not run your code, but I know Region.Union is flawed in both 1.0 and + /// 1.1, so I assume it is in the gdi+ unmanged code dll. The best workaround, + /// in terms of speed, is to use a PdnGraphicsPath, but it must be a path with + /// FillMode = FillMode.Winding. You add the rectangles to the path, then you do + /// union onto an empty region with the path. The important point is to do only + /// one union call on a given empty region. We created a "super region" object + /// to hide all these bugs and optimize clipping operations. In fact, it is much + /// faster to use the path than to call Region.Union for each rectangle. + /// + /// Too bad about Region.Union. A lot of people will hit this bug, as it is + /// essential in high-performance animation. + /// + /// Regards, + /// Frank Hileman + /// Prodige Software Corporation + /// --- + /// + /// + /// + /// + /// + public static PdnRegion RectanglesToRegion(RectangleF[] rectsF, int startIndex, int length) + { + PdnRegion region; + + if (rectsF == null || rectsF.Length == 0 || length == 0) + { + region = PdnRegion.CreateEmpty(); + } + else + { + using (PdnGraphicsPath path = new PdnGraphicsPath()) + { + path.FillMode = FillMode.Winding; + + if (startIndex == 0 && length == rectsF.Length) + { + path.AddRectangles(rectsF); + } + else + { + for (int i = startIndex; i < startIndex + length; ++i) + { + path.AddRectangle(rectsF[i]); + } + } + + region = new PdnRegion(path); + } + } + + return region; + } + + public static PdnRegion RectanglesToRegion(RectangleF[] rectsF) + { + return RectanglesToRegion(rectsF, 0, rectsF != null ? rectsF.Length : 0); + } + + public static PdnRegion RectanglesToRegion(RectangleF[] rectsF1, RectangleF[] rectsF2, params RectangleF[][] rectsFA) + { + using (PdnGraphicsPath path = new PdnGraphicsPath()) + { + path.FillMode = FillMode.Winding; + + if (rectsF1 != null && rectsF1.Length > 0) + { + path.AddRectangles(rectsF1); + } + + if (rectsF2 != null && rectsF2.Length > 0) + { + path.AddRectangles(rectsF2); + } + + foreach (RectangleF[] rectsF in rectsFA) + { + if (rectsF != null && rectsF.Length > 0) + { + path.AddRectangles(rectsF); + } + } + + return new PdnRegion(path); + } + } + + public static PdnRegion RectanglesToRegion(Rectangle[] rects, int startIndex, int length) + { + PdnRegion region; + + if (length == 0) + { + region = PdnRegion.CreateEmpty(); + } + else + { + using (PdnGraphicsPath path = new PdnGraphicsPath()) + { + path.FillMode = FillMode.Winding; + if (startIndex == 0 && length == rects.Length) + { + path.AddRectangles(rects); + } + else + { + for (int i = startIndex; i < startIndex + length; ++i) + { + path.AddRectangle(rects[i]); + } + } + + region = new PdnRegion(path); + path.Dispose(); + } + } + + return region; + } + + public static PdnRegion RectanglesToRegion(Rectangle[] rects) + { + return RectanglesToRegion(rects, 0, rects.Length); + } + + public static int GetRegionArea(RectangleF[] rectsF) + { + int area = 0; + + foreach (RectangleF rectF in rectsF) + { + Rectangle rect = Rectangle.Truncate(rectF); + area += rect.Width * rect.Height; + } + + return area; + } + + public static RectangleF RectangleFromCenter(PointF center, float halfSize) + { + RectangleF ret = new RectangleF(center.X, center.Y, 0, 0); + ret.Inflate(halfSize, halfSize); + return ret; + } + + public static List PointListToPointFList(List ptList) + { + List ret = new List(ptList.Count); + + for (int i = 0; i < ptList.Count; ++i) + { + ret.Add((PointF)ptList[i]); + } + + return ret; + } + + public static PointF[] PointArrayToPointFArray(Point[] ptArray) + { + PointF[] ret = new PointF[ptArray.Length]; + + for (int i = 0; i < ret.Length; ++i) + { + ret[i] = (PointF)ptArray[i]; + } + + return ret; + } + + public static Rectangle[] InflateRectangles(Rectangle[] rects, int amount) + { + Rectangle[] inflated = new Rectangle[rects.Length]; + + for (int i = 0; i < rects.Length; ++i) + { + inflated[i] = Rectangle.Inflate(rects[i], amount, amount); + } + + return inflated; + } + + public static void InflateRectanglesInPlace(Rectangle[] rects, int amount) + { + for (int i = 0; i < rects.Length; ++i) + { + rects[i].Inflate(amount, amount); + } + } + + public static RectangleF[] InflateRectangles(RectangleF[] rectsF, int amount) + { + RectangleF[] inflated = new RectangleF[rectsF.Length]; + + for (int i = 0; i < rectsF.Length; ++i) + { + inflated[i] = RectangleF.Inflate(rectsF[i], amount, amount); + } + + return inflated; + } + + public static void InflateRectanglesInPlace(RectangleF[] rectsF, float amount) + { + for (int i = 0; i < rectsF.Length; ++i) + { + rectsF[i].Inflate(amount, amount); + } + } + + public static Rectangle PointsToConstrainedRectangle(Point a, Point b) + { + Rectangle rect = Utility.PointsToRectangle(a, b); + int minWH = Math.Min(rect.Width, rect.Height); + + rect.Width = minWH; + rect.Height = minWH; + + if (rect.Y != a.Y) + { + rect.Location = new Point(rect.X, a.Y - minWH); + } + + if (rect.X != a.X) + { + rect.Location = new Point(a.X - minWH, rect.Y); + } + + return rect; + } + + public static RectangleF PointsToConstrainedRectangle(PointF a, PointF b) + { + RectangleF rect = Utility.PointsToRectangle(a, b); + float minWH = Math.Min(rect.Width, rect.Height); + + rect.Width = minWH; + rect.Height = minWH; + + if (rect.Y != a.Y) + { + rect.Location = new PointF(rect.X, a.Y - minWH); + } + + if (rect.X != a.X) + { + rect.Location = new PointF(a.X - minWH, rect.Y); + } + + return rect; + } + + /// + /// Takes two points and creates a bounding rectangle from them. + /// + /// One corner of the rectangle. + /// The other corner of the rectangle. + /// A Rectangle instance that bounds the two points. + public static Rectangle PointsToRectangle(Point a, Point b) + { + int x = Math.Min(a.X, b.X); + int y = Math.Min(a.Y, b.Y); + int width = Math.Abs(a.X - b.X) + 1; + int height = Math.Abs(a.Y - b.Y) + 1; + + return new Rectangle(x, y, width, height); + } + + public static RectangleF PointsToRectangle(PointF a, PointF b) + { + float x = Math.Min(a.X, b.X); + float y = Math.Min(a.Y, b.Y); + float width = Math.Abs(a.X - b.X) + 1; + float height = Math.Abs(a.Y - b.Y) + 1; + + return new RectangleF(x, y, width, height); + } + + public static Rectangle PointsToRectangleExclusive(Point a, Point b) + { + int x = Math.Min(a.X, b.X); + int y = Math.Min(a.Y, b.Y); + int width = Math.Abs(a.X - b.X); + int height = Math.Abs(a.Y - b.Y); + + return new Rectangle(x, y, width, height); + } + + public static RectangleF PointsToRectangleExclusive(PointF a, PointF b) + { + float x = Math.Min(a.X, b.X); + float y = Math.Min(a.Y, b.Y); + float width = Math.Abs(a.X - b.X); + float height = Math.Abs(a.Y - b.Y); + + return new RectangleF(x, y, width, height); + } + + public static RectangleF[] PointsToRectangles(PointF[] pointsF) + { + if (pointsF.Length == 0) + { + return new RectangleF[] { }; + } + + if (pointsF.Length == 1) + { + return new RectangleF[] { new RectangleF(pointsF[0].X, pointsF[0].Y, 1, 1) }; + } + + RectangleF[] rectsF = new RectangleF[pointsF.Length - 1]; + + for (int i = 0; i < pointsF.Length - 1; ++i) + { + rectsF[i] = PointsToRectangle(pointsF[i], pointsF[i + 1]); + } + + return rectsF; + } + + public static Rectangle[] PointsToRectangles(Point[] points) + { + if (points.Length == 0) + { + return new Rectangle[] { }; + } + + if (points.Length == 1) + { + return new Rectangle[] { new Rectangle(points[0].X, points[0].Y, 1, 1) }; + } + + Rectangle[] rects = new Rectangle[points.Length - 1]; + + for (int i = 0; i < points.Length - 1; ++i) + { + rects[i] = PointsToRectangle(points[i], points[i + 1]); + } + + return rects; + } + + /// + /// Converts a RectangleF to RectangleF by rounding down the Location and rounding + /// up the Size. + /// + public static Rectangle RoundRectangle(RectangleF rectF) + { + float left = (float)Math.Floor(rectF.Left); + float top = (float)Math.Floor(rectF.Top); + float right = (float)Math.Ceiling(rectF.Right); + float bottom = (float)Math.Ceiling(rectF.Bottom); + + return Rectangle.Truncate(RectangleF.FromLTRB(left, top, right, bottom)); + } + + public static Stack Reverse(Stack reverseMe) + { + Stack reversed = new Stack(); + + foreach (object o in reverseMe) + { + reversed.Push(o); + } + + return reversed; + } + + public static void SerializeObjectToStream(object graph, Stream stream) + { + new BinaryFormatter().Serialize(stream, graph); + } + + public static object DeserializeObjectFromStream(Stream stream) + { + return new BinaryFormatter().Deserialize(stream); + } + + [Obsolete("Use rect.Contains() instead", true)] + public static bool IsPointInRectangle(Point pt, Rectangle rect) + { + return rect.Contains(pt); + } + + [Obsolete("Use rect.Contains() instead", true)] + public static bool IsPointInRectangle(int x, int y, Rectangle rect) + { + return rect.Contains(x, y); + } + + public static Bitmap FullCloneBitmap(Bitmap cloneMe) + { + Bitmap bitmap = new Bitmap(cloneMe.Width, cloneMe.Height, cloneMe.PixelFormat); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.DrawImage(cloneMe, 0, 0, cloneMe.Width, cloneMe.Height); + } + + return bitmap; + } + + /// + /// Allows you to find the bounding box for a Region object without requiring + /// the presence of a Graphics object. + /// (Region.GetBounds takes a Graphics instance as its only parameter.) + /// + /// The region you want to find a bounding box for. + /// A RectangleF structure that surrounds the Region. + public static Rectangle GetRegionBounds(PdnRegion region) + { + Rectangle[] rects = region.GetRegionScansReadOnlyInt(); + return GetRegionBounds(rects, 0, rects.Length); + } + + /// + /// Allows you to find the bounding box for a "region" that is described as an + /// array of bounding boxes. + /// + /// The "region" you want to find a bounding box for. + /// A RectangleF structure that surrounds the Region. + public static RectangleF GetRegionBounds(RectangleF[] rectsF, int startIndex, int length) + { + if (rectsF.Length == 0) + { + return RectangleF.Empty; + } + + float left = rectsF[startIndex].Left; + float top = rectsF[startIndex].Top; + float right = rectsF[startIndex].Right; + float bottom = rectsF[startIndex].Bottom; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + RectangleF rectF = rectsF[i]; + + if (rectF.Left < left) + { + left = rectF.Left; + } + + if (rectF.Top < top) + { + top = rectF.Top; + } + + if (rectF.Right > right) + { + right = rectF.Right; + } + + if (rectF.Bottom > bottom) + { + bottom = rectF.Bottom; + } + } + + return RectangleF.FromLTRB(left, top, right, bottom); + } + + public static RectangleF GetTraceBounds(PointF[] pointsF, int startIndex, int length) + { + if (pointsF.Length == 0) + { + return RectangleF.Empty; + } + + float left = pointsF[startIndex].X; + float top = pointsF[startIndex].Y; + float right = 1 + pointsF[startIndex].X; + float bottom = 1 + pointsF[startIndex].Y; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + PointF pointF = pointsF[i]; + + if (pointF.X < left) + { + left = pointF.X; + } + + if (pointF.Y < top) + { + top = pointF.Y; + } + + if (pointF.X > right) + { + right = pointF.X; + } + + if (pointF.Y > bottom) + { + bottom = pointF.Y; + } + } + + return RectangleF.FromLTRB(left, top, right, bottom); + } + + public static Rectangle GetTraceBounds(Point[] points, int startIndex, int length) + { + if (points.Length == 0) + { + return Rectangle.Empty; + } + + int left = points[startIndex].X; + int top = points[startIndex].Y; + int right = 1 + points[startIndex].X; + int bottom = 1 + points[startIndex].Y; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + Point point = points[i]; + + if (point.X < left) + { + left = point.X; + } + + if (point.Y < top) + { + top = point.Y; + } + + if (point.X > right) + { + right = point.X; + } + + if (point.Y > bottom) + { + bottom = point.Y; + } + } + + return Rectangle.FromLTRB(left, top, right, bottom); + } + + /// + /// Allows you to find the bounding box for a "region" that is described as an + /// array of bounding boxes. + /// + /// The "region" you want to find a bounding box for. + /// A RectangleF structure that surrounds the Region. + public static Rectangle GetRegionBounds(Rectangle[] rects, int startIndex, int length) + { + if (rects.Length == 0) + { + return Rectangle.Empty; + } + + int left = rects[startIndex].Left; + int top = rects[startIndex].Top; + int right = rects[startIndex].Right; + int bottom = rects[startIndex].Bottom; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + Rectangle rect = rects[i]; + + if (rect.Left < left) + { + left = rect.Left; + } + + if (rect.Top < top) + { + top = rect.Top; + } + + if (rect.Right > right) + { + right = rect.Right; + } + + if (rect.Bottom > bottom) + { + bottom = rect.Bottom; + } + } + + return Rectangle.FromLTRB(left, top, right, bottom); + } + + public static RectangleF GetRegionBounds(RectangleF[] rectsF) + { + return GetRegionBounds(rectsF, 0, rectsF.Length); + } + + public static Rectangle GetRegionBounds(Rectangle[] rects) + { + return GetRegionBounds(rects, 0, rects.Length); + } + + private static float DistanceSquared(RectangleF[] rectsF, int indexA, int indexB) + { + PointF centerA = new PointF(rectsF[indexA].Left + (rectsF[indexA].Width / 2), rectsF[indexA].Top + (rectsF[indexA].Height / 2)); + PointF centerB = new PointF(rectsF[indexB].Left + (rectsF[indexB].Width / 2), rectsF[indexB].Top + (rectsF[indexB].Height / 2)); + + return ((centerA.X - centerB.X) * (centerA.X - centerB.X)) + + ((centerA.Y - centerB.Y) * (centerA.Y - centerB.Y)); + } + + /// + /// Simplifies a Region into N number of bounding boxes. + /// + /// The Region to simplify. + /// The maximum number of bounding boxes to return, or 0 for however many are necessary (equivalent to using Region.GetRegionScans). + /// + public static Rectangle[] SimplifyRegion(PdnRegion region, int complexity) + { + Rectangle[] rects = region.GetRegionScansReadOnlyInt(); + return SimplifyRegion(rects, complexity); + } + + public static Rectangle[] SimplifyRegion(Rectangle[] rects, int complexity) + { + if (complexity == 0 || rects.Length < complexity) + { + return (Rectangle[])rects.Clone(); + } + + Rectangle[] boxes = new Rectangle[complexity]; + + for (int i = 0; i < complexity; ++i) + { + int startIndex = (i * rects.Length) / complexity; + int length = Math.Min(rects.Length, ((i + 1) * rects.Length) / complexity) - startIndex; + boxes[i] = GetRegionBounds(rects, startIndex, length); + } + + return boxes; + } + + + public static RectangleF[] SimplifyTrace(PointF[] pointsF, int complexity) + { + if (complexity == 0 || + (pointsF.Length - 1) < complexity) + { + return PointsToRectangles(pointsF); + } + + RectangleF[] boxes = new RectangleF[complexity]; + int parLength = pointsF.Length - 1; // "(points as Rectangles).Length" + + for (int i = 0; i < complexity; ++i) + { + int startIndex = (i * parLength) / complexity; + int length = Math.Min(parLength, ((i + 1) * parLength) / complexity) - startIndex; + boxes[i] = GetTraceBounds(pointsF, startIndex, length + 1); + } + + return boxes; + } + + public static Rectangle[] SimplifyTrace(PdnGraphicsPath trace, int complexity) + { + return SimplifyRegion(TraceToRectangles(trace), complexity); + } + + public static Rectangle[] SimplifyTrace(PdnGraphicsPath trace) + { + return SimplifyTrace(trace, DefaultSimplificationFactor); + } + + public static Rectangle[] TraceToRectangles(PdnGraphicsPath trace, int complexity) + { + int pointCount = trace.PointCount; + + if (pointCount == 0) + { + return new Rectangle[0]; + } + + PointF[] pathPoints = trace.PathPoints; + byte[] pathTypes = trace.PathTypes; + int figureStart = 0; + + // first get count of rectangles we'll need + Rectangle[] rects = new Rectangle[pointCount]; + + for (int i = 0; i < pointCount; ++i) + { + byte type = pathTypes[i]; + + Point a = Point.Truncate(pathPoints[i]); + Point b; + + if ((type & (byte)PathPointType.CloseSubpath) != 0) + { + b = Point.Truncate(pathPoints[figureStart]); + figureStart = i + 1; + } + else + { + b = Point.Truncate(pathPoints[i + 1]); + } + + rects[i] = Utility.PointsToRectangle(a, b); + } + + return rects; + } + + public static Rectangle[] TraceToRectangles(PdnGraphicsPath trace) + { + return TraceToRectangles(trace, DefaultSimplificationFactor); + } + + public static RectangleF[] SimplifyTrace(PointF[] pointsF) + { + return SimplifyTrace(pointsF, defaultSimplificationFactor); + } + + public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects, int complexity, int inflationAmount) + { + Rectangle[] simplified = SimplifyRegion(rects, complexity); + + for (int i = 0; i < simplified.Length; ++i) + { + simplified[i].Inflate(inflationAmount, inflationAmount); + } + + return simplified; + } + + public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects) + { + return SimplifyAndInflateRegion(rects, defaultSimplificationFactor, 1); + } + + public static PdnRegion SimplifyAndInflateRegion(PdnRegion region, int complexity, int inflationAmount) + { + Rectangle[] rectRegion = SimplifyRegion(region, complexity); + + for (int i = 0; i < rectRegion.Length; ++i) + { + rectRegion[i].Inflate(inflationAmount, inflationAmount); + } + + return RectanglesToRegion(rectRegion); + } + + public static PdnRegion SimplifyAndInflateRegion(PdnRegion region) + { + return SimplifyAndInflateRegion(region, defaultSimplificationFactor, 1); + } + + public static RectangleF[] TranslateRectangles(RectangleF[] rectsF, PointF offset) + { + RectangleF[] retRectsF = new RectangleF[rectsF.Length]; + int i = 0; + + foreach (RectangleF rectF in rectsF) + { + retRectsF[i] = new RectangleF(rectF.X + offset.X, rectF.Y + offset.Y, rectF.Width, rectF.Height); + ++i; + } + + return retRectsF; + } + + public static Rectangle[] TranslateRectangles(Rectangle[] rects, int dx, int dy) + { + Rectangle[] retRects = new Rectangle[rects.Length]; + + for (int i = 0; i < rects.Length; ++i) + { + retRects[i] = new Rectangle(rects[i].X + dx, rects[i].Y + dy, rects[i].Width, rects[i].Height); + } + + return retRects; + } + + public static void TranslatePointsInPlace(PointF[] ptsF, float dx, float dy) + { + for (int i = 0; i < ptsF.Length; ++i) + { + ptsF[i].X += dx; + ptsF[i].Y += dy; + } + } + + public static void TranslatePointsInPlace(Point[] pts, int dx, int dy) + { + for (int i = 0; i < pts.Length; ++i) + { + pts[i].X += dx; + pts[i].Y += dy; + } + } + + public static Rectangle[] TruncateRectangles(RectangleF[] rectsF) + { + Rectangle[] rects = new Rectangle[rectsF.Length]; + + for (int i = 0; i < rectsF.Length; ++i) + { + rects[i] = Rectangle.Truncate(rectsF[i]); + } + + return rects; + } + + public static Point[] TruncatePoints(PointF[] pointsF) + { + Point[] points = new Point[pointsF.Length]; + + for (int i = 0; i < pointsF.Length; ++i) + { + points[i] = Point.Truncate(pointsF[i]); + } + + return points; + } + + public static Point[] RoundPoints(PointF[] pointsF) + { + Point[] points = new Point[pointsF.Length]; + + for (int i = 0; i < pointsF.Length; ++i) + { + points[i] = Point.Round(pointsF[i]); + } + + return points; + } + + /// + /// The Sutherland-Hodgman clipping alrogithm. + /// http://ezekiel.vancouver.wsu.edu/~cs442/lectures/clip/clip/index.html + /// + /// # Clipping a convex polygon to a convex region (e.g., rectangle) will always produce a convex polygon (or no polygon if completely outside the clipping region). + /// # Clipping a concave polygon to a rectangle may produce several polygons (see figure above) or, as the following algorithm does, produce a single, possibly degenerate, polygon. + /// # Divide and conquer: Clip entire polygon against a single edge (i.e., half-plane). Repeat for each edge in the clipping region. + /// + /// The input is a sequence of vertices: {v0, v1, ... vn} given as an array of Points + /// the result is a sequence of vertices, given as an array of Points. This result may have + /// less than, equal, more than, or 0 vertices. + /// + /// + /// + public static List SutherlandHodgman(RectangleF bounds, List v) + { + List p1 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Left, v); + List p2 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Right, p1); + List p3 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Top, p2); + List p4 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Bottom, p3); + + return p4; + } + + private enum RectangleEdge + { + Left, + Right, + Top, + Bottom + } + + private static List SutherlandHodgmanOneAxis(RectangleF bounds, RectangleEdge edge, List v) + { + if (v.Count == 0) + { + return new List(); + } + + List polygon = new List(); + + PointF s = v[v.Count - 1]; + + for (int i = 0; i < v.Count; ++i) + { + PointF p = v[i]; + bool pIn = IsInside(bounds, edge, p); + bool sIn = IsInside(bounds, edge, s); + + if (sIn && pIn) + { + // case 1: inside -> inside + polygon.Add(p); + } + else if (sIn && !pIn) + { + // case 2: inside -> outside + polygon.Add(LineIntercept(bounds, edge, s, p)); + } + else if (!sIn && !pIn) + { + // case 3: outside -> outside + // emit nothing + } + else if (!sIn && pIn) + { + // case 4: outside -> inside + polygon.Add(LineIntercept(bounds, edge, s, p)); + polygon.Add(p); + } + + s = p; + } + + return polygon; + } + + private static bool IsInside(RectangleF bounds, RectangleEdge edge, PointF p) + { + switch (edge) + { + case RectangleEdge.Left: + return !(p.X < bounds.Left); + + case RectangleEdge.Right: + return !(p.X >= bounds.Right); + + case RectangleEdge.Top: + return !(p.Y < bounds.Top); + + case RectangleEdge.Bottom: + return !(p.Y >= bounds.Bottom); + + default: + throw new InvalidEnumArgumentException("edge"); + } + } + + private static Point LineIntercept(Rectangle bounds, RectangleEdge edge, Point a, Point b) + { + if (a == b) + { + return a; + } + + switch (edge) + { + case RectangleEdge.Bottom: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom); + + case RectangleEdge.Left: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X))); + + case RectangleEdge.Right: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X))); + + case RectangleEdge.Top: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top); + } + + throw new ArgumentException("no intercept found"); + } + + private static PointF LineIntercept(RectangleF bounds, RectangleEdge edge, PointF a, PointF b) + { + if (a == b) + { + return a; + } + + switch (edge) + { + case RectangleEdge.Bottom: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom); + + case RectangleEdge.Left: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X))); + + case RectangleEdge.Right: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X))); + + case RectangleEdge.Top: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top); + } + + throw new ArgumentException("no intercept found"); + } + + public static Point[] GetLinePoints(Point first, Point second) + { + Point[] coords = null; + + int x1 = first.X; + int y1 = first.Y; + int x2 = second.X; + int y2 = second.Y; + int dx = x2 - x1; + int dy = y2 - y1; + int dxabs = Math.Abs(dx); + int dyabs = Math.Abs(dy); + int px = x1; + int py = y1; + int sdx = Math.Sign(dx); + int sdy = Math.Sign(dy); + int x = 0; + int y = 0; + + if (dxabs > dyabs) + { + coords = new Point[dxabs + 1]; + + for (int i = 0; i <= dxabs; i++) + { + y += dyabs; + + if (y >= dxabs) + { + y -= dxabs; + py += sdy; + } + + coords[i] = new Point(px, py); + px += sdx; + } + } + else + // had to add in this cludge for slopes of 1 ... wasn't drawing half the line + if (dxabs == dyabs) + { + coords = new Point[dxabs + 1]; + + for (int i = 0; i <= dxabs; i++) + { + coords[i] = new Point(px, py); + px += sdx; + py += sdy; + } + } + else + { + coords = new Point[dyabs + 1]; + + for (int i = 0; i <= dyabs; i++) + { + x += dxabs; + + if (x >= dyabs) + { + x -= dyabs; + px += sdx; + } + + coords[i] = new Point(px, py); + py += sdy; + } + } + + return coords; + } + + public static long GetTimeMs() + { + return Utility.TicksToMs(DateTime.Now.Ticks); + } + + /// + /// Returns the Distance between two points + /// + public static float Distance(PointF a, PointF b) + { + return Magnitude(new PointF(a.X - b.X, a.Y - b.Y)); + } + + /// + /// Returns the Magnitude (distance to origin) of a point + /// + // TODO: In v4.0 codebase, turn this into an extension method + public static float Magnitude(PointF p) + { + return (float)Math.Sqrt(p.X * p.X + p.Y * p.Y); + } + + // TODO: In v4.0 codebase, turn this into an extension method + public static double Clamp(double x, double min, double max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + // TODO: In v4.0 codebase, turn this into an extension method + public static float Clamp(float x, float min, float max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + // TODO: In v4.0 codebase, turn this into an extension method + public static int Clamp(int x, int min, int max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + public static byte ClampToByte(double x) + { + if (x > 255) + { + return 255; + } + else if (x < 0) + { + return 0; + } + else + { + return (byte)x; + } + } + + public static byte ClampToByte(float x) + { + if (x > 255) + { + return 255; + } + else if (x < 0) + { + return 0; + } + else + { + return (byte)x; + } + } + + public static byte ClampToByte(int x) + { + if (x > 255) + { + return 255; + } + else if (x < 0) + { + return 0; + } + else + { + return (byte)x; + } + } + + public static float Lerp(float from, float to, float frac) + { + return (from + frac * (to - from)); + } + + public static double Lerp(double from, double to, double frac) + { + return (from + frac * (to - from)); + } + + public static PointF Lerp(PointF from, PointF to, float frac) + { + return new PointF(Lerp(from.X, to.X, frac), Lerp(from.Y, to.Y, frac)); + } + + public static int ColorDifference(ColorBgra a, ColorBgra b) + { + return (int)Math.Ceiling(Math.Sqrt(ColorDifferenceSquared(a, b))); + } + + public static int ColorDifferenceSquared(ColorBgra a, ColorBgra b) + { + int diffSq = 0, tmp; + + tmp = a.R - b.R; + diffSq += tmp * tmp; + tmp = a.G - b.G; + diffSq += tmp * tmp; + tmp = a.B - b.B; + diffSq += tmp * tmp; + + return diffSq / 3; + } + + public static DialogResult ShowDialog(Form showMe, IWin32Window owner) + { + DialogResult dr; + + if (showMe is PdnBaseForm) + { + PdnBaseForm showMe2 = (PdnBaseForm)showMe; + double oldOpacity = showMe2.Opacity; + showMe2.Opacity = 0.9; + dr = showMe2.ShowDialog(owner); + showMe2.Opacity = oldOpacity; + } + else + { + double oldOpacity = showMe.Opacity; + showMe.Opacity = 0.9; + dr = showMe.ShowDialog(owner); + showMe.Opacity = oldOpacity; + } + + Control control = owner as Control; + if (control != null) + { + Form form = control.FindForm(); + + if (form != null) + { + form.Activate(); + } + + control.Update(); + } + + return dr; + } + + public static void ShowHelp(Control parent) + { + string helpFileUrlFormat = PdnResources.GetString("HelpFile.Url.Format"); + string baseSiteUrl = InvariantStrings.WebsiteUrl; + string helpFileUrl = string.Format(helpFileUrlFormat, baseSiteUrl); + PdnInfo.OpenUrl(parent, helpFileUrl); + } + + /// + /// Reads a 16-bit unsigned integer from a Stream in little-endian format. + /// + /// + /// -1 on failure, else the 16-bit unsigned integer that was read. + public static int ReadUInt16(Stream stream) + { + int byte1 = stream.ReadByte(); + + if (byte1 == -1) + { + return -1; + } + + int byte2 = stream.ReadByte(); + + if (byte2 == -1) + { + return -1; + } + + return byte1 + (byte2 << 8); + } + + public static void WriteUInt16(Stream stream, UInt16 word) + { + stream.WriteByte((byte)(word & 0xff)); + stream.WriteByte((byte)(word >> 8)); + } + + public static void WriteUInt24(Stream stream, int uint24) + { + stream.WriteByte((byte)(uint24 & 0xff)); + stream.WriteByte((byte)((uint24 >> 8) & 0xff)); + stream.WriteByte((byte)((uint24 >> 16) & 0xff)); + } + + public static void WriteUInt32(Stream stream, UInt32 uint32) + { + stream.WriteByte((byte)(uint32 & 0xff)); + stream.WriteByte((byte)((uint32 >> 8) & 0xff)); + stream.WriteByte((byte)((uint32 >> 16) & 0xff)); + stream.WriteByte((byte)((uint32 >> 24) & 0xff)); + } + + /// + /// Reads a 24-bit unsigned integer from a Stream in little-endian format. + /// + /// + /// -1 on failure, else the 24-bit unsigned integer that was read. + public static int ReadUInt24(Stream stream) + { + int byte1 = stream.ReadByte(); + + if (byte1 == -1) + { + return -1; + } + + int byte2 = stream.ReadByte(); + + if (byte2 == -1) + { + return -1; + } + + int byte3 = stream.ReadByte(); + + if (byte3 == -1) + { + return -1; + } + + return byte1 + (byte2 << 8) + (byte3 << 16); + } + + /// + /// Reads a 32-bit unsigned integer from a Stream in little-endian format. + /// + /// + /// -1 on failure, else the 32-bit unsigned integer that was read. + public static long ReadUInt32(Stream stream) + { + int byte1 = stream.ReadByte(); + + if (byte1 == -1) + { + return -1; + } + + int byte2 = stream.ReadByte(); + + if (byte2 == -1) + { + return -1; + } + + int byte3 = stream.ReadByte(); + + if (byte3 == -1) + { + return -1; + } + + int byte4 = stream.ReadByte(); + + if (byte4 == -1) + { + return -1; + } + + return unchecked((long)((uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24)))); + } + + public static int ReadFromStream(Stream input, byte[] buffer, int offset, int count) + { + int totalBytesRead = 0; + + while (totalBytesRead < count) + { + int bytesRead = input.Read(buffer, offset + totalBytesRead, count - totalBytesRead); + + if (bytesRead == 0) + { + throw new IOException("ran out of data"); + } + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } + + public static long CopyStream(Stream input, Stream output, long maxBytes) + { + long bytesCopied = 0; + byte[] buffer = new byte[4096]; + + while (true) + { + int bytesRead = input.Read(buffer, 0, buffer.Length); + + if (bytesRead == 0) + { + break; + } + else + { + int bytesToCopy; + + if (maxBytes != -1 && (bytesCopied + bytesRead) > maxBytes) + { + bytesToCopy = (int)(maxBytes - bytesCopied); + } + else + { + bytesToCopy = bytesRead; + } + + output.Write(buffer, 0, bytesRead); + bytesCopied += bytesToCopy; + + if (bytesToCopy != bytesRead) + { + break; + } + } + } + + return bytesCopied; + } + + public static long CopyStream(Stream input, Stream output) + { + return CopyStream(input, output, -1); + } + + private struct Edge + { + public int miny; // int + public int maxy; // int + public int x; // fixed point: 24.8 + public int dxdy; // fixed point: 24.8 + + public Edge(int miny, int maxy, int x, int dxdy) + { + this.miny = miny; + this.maxy = maxy; + this.x = x; + this.dxdy = dxdy; + } + } + + public static Scanline[] GetScans(Point[] vertices) + { + return GetScans(vertices, 0, vertices.Length); + } + + public static Scanline[] GetScans(Point[] vertices, int startIndex, int length) + { + if (length > vertices.Length - startIndex) + { + throw new ArgumentException("out of bounds: length > vertices.Length - startIndex"); + } + + int ymax = 0; + + // Build edge table + Edge[] edgeTable = new Edge[length]; + int edgeCount = 0; + + for (int i = startIndex; i < startIndex + length; ++i) + { + Point top = vertices[i]; + Point bottom = vertices[(((i + 1) - startIndex) % length) + startIndex]; + int dy; + + if (top.Y > bottom.Y) + { + Point temp = top; + top = bottom; + bottom = temp; + } + + dy = bottom.Y - top.Y; + + if (dy != 0) + { + edgeTable[edgeCount] = new Edge(top.Y, bottom.Y, top.X << 8, (((bottom.X - top.X) << 8) / dy)); + ymax = Math.Max(ymax, bottom.Y); + ++edgeCount; + } + } + + // Sort edge table by miny + for (int i = 0; i < edgeCount - 1; ++i) + { + int min = i; + + for (int j = i + 1; j < edgeCount; ++j) + { + if (edgeTable[j].miny < edgeTable[min].miny) + { + min = j; + } + } + + if (min != i) + { + Edge temp = edgeTable[min]; + edgeTable[min] = edgeTable[i]; + edgeTable[i] = temp; + } + } + + // Compute how many scanlines we will be emitting + int scanCount = 0; + int activeLow = 0; + int activeHigh = 0; + int yscan1 = edgeTable[0].miny; + + // we assume that edgeTable[0].miny == yscan + while (activeHigh < edgeCount - 1 && + edgeTable[activeHigh + 1].miny == yscan1) + { + ++activeHigh; + } + + while (yscan1 <= ymax) + { + // Find new edges where yscan == miny + while (activeHigh < edgeCount - 1 && + edgeTable[activeHigh + 1].miny == yscan1) + { + ++activeHigh; + } + + int count = 0; + for (int i = activeLow; i <= activeHigh; ++i) + { + if (edgeTable[i].maxy > yscan1) + { + ++count; + } + } + + scanCount += count / 2; + ++yscan1; + + // Remove edges where yscan == maxy + while (activeLow < edgeCount - 1 && + edgeTable[activeLow].maxy <= yscan1) + { + ++activeLow; + } + + if (activeLow > activeHigh) + { + activeHigh = activeLow; + } + } + + // Allocate scanlines that we'll return + Scanline[] scans = new Scanline[scanCount]; + + // Active Edge Table (AET): it is indices into the Edge Table (ET) + int[] active = new int[edgeCount]; + int activeCount = 0; + int yscan2 = edgeTable[0].miny; + int scansIndex = 0; + + // Repeat until both the ET and AET are empty + while (yscan2 <= ymax) + { + // Move any edges from the ET to the AET where yscan == miny + for (int i = 0; i < edgeCount; ++i) + { + if (edgeTable[i].miny == yscan2) + { + active[activeCount] = i; + ++activeCount; + } + } + + // Sort the AET on x + for (int i = 0; i < activeCount - 1; ++i) + { + int min = i; + + for (int j = i + 1; j < activeCount; ++j) + { + if (edgeTable[active[j]].x < edgeTable[active[min]].x) + { + min = j; + } + } + + if (min != i) + { + int temp = active[min]; + active[min] = active[i]; + active[i] = temp; + } + } + + // For each pair of entries in the AET, fill in pixels between their info + for (int i = 0; i < activeCount; i += 2) + { + Edge el = edgeTable[active[i]]; + Edge er = edgeTable[active[i + 1]]; + int startx = (el.x + 0xff) >> 8; // ceil(x) + int endx = er.x >> 8; // floor(x) + + scans[scansIndex] = new Scanline(startx, yscan2, endx - startx); + ++scansIndex; + } + + ++yscan2; + + // Remove from the AET any edge where yscan == maxy + int k = 0; + while (k < activeCount && activeCount > 0) + { + if (edgeTable[active[k]].maxy == yscan2) + { + // remove by shifting everything down one + for (int j = k + 1; j < activeCount; ++j) + { + active[j - 1] = active[j]; + } + + --activeCount; + } + else + { + ++k; + } + } + + // Update x for each entry in AET + for (int i = 0; i < activeCount; ++i) + { + edgeTable[active[i]].x += edgeTable[active[i]].dxdy; + } + } + + return scans; + } + + public static PointF TransformOnePoint(Matrix matrix, PointF ptF) + { + PointF[] ptFs = new PointF[1] { ptF }; + matrix.TransformPoints(ptFs); + return ptFs[0]; + } + + public static PointF TransformOneVector(Matrix matrix, PointF ptF) + { + PointF[] ptFs = new PointF[1] { ptF }; + matrix.TransformVectors(ptFs); + return ptFs[0]; + } + + public static PointF NormalizeVector(PointF vecF) + { + float magnitude = Magnitude(vecF); + vecF.X /= magnitude; + vecF.Y /= magnitude; + return vecF; + } + + public static PointF NormalizeVector2(PointF vecF) + { + float magnitude = Magnitude(vecF); + + if (magnitude == 0) + { + vecF.X = 0; + vecF.Y = 0; + } + else + { + vecF.X /= magnitude; + vecF.Y /= magnitude; + } + + return vecF; + } + + public static void NormalizeVectors(PointF[] vecsF) + { + for (int i = 0; i < vecsF.Length; ++i) + { + vecsF[i] = NormalizeVector(vecsF[i]); + } + } + + public static PointF RotateVector(PointF vecF, float angleDelta) + { + angleDelta *= (float)( Math.PI / 180.0); + float vecFLen = Magnitude(vecF); + float vecFAngle = angleDelta + (float)Math.Atan2(vecF.Y, vecF.X); + vecF.X = (float)Math.Cos(vecFAngle); + vecF.Y = (float)Math.Sin(vecFAngle); + return vecF; + } + + public static void RotateVectors(PointF[] vecFs, float angleDelta) + { + for (int i = 0; i < vecFs.Length; ++i) + { + vecFs[i] = RotateVector(vecFs[i], angleDelta); + } + } + + public static PointF MultiplyVector(PointF vecF, float scalar) + { + return new PointF(vecF.X * scalar, vecF.Y * scalar); + } + + public static PointF AddVectors(PointF a, PointF b) + { + return new PointF(a.X + b.X, a.Y + b.Y); + } + + public static PointF SubtractVectors(PointF lhs, PointF rhs) + { + return new PointF(lhs.X - rhs.X, lhs.Y - rhs.Y); + } + + public static PointF NegateVector(PointF v) + { + return new PointF(-v.X, -v.Y); + } + + public static float GetAngleOfTransform(Matrix matrix) + { + PointF[] pts = new PointF[] { new PointF(1.0f, 0.0f) }; + matrix.TransformVectors(pts); + double atan2 = Math.Atan2(pts[0].Y, pts[0].X); + double angle = atan2 * (180.0f / Math.PI); + + return (float)angle; + } + + public static bool IsTransformFlipped(Matrix matrix) + { + PointF ptX = new PointF(1.0f, 0.0f); + PointF ptXT = Utility.TransformOneVector(matrix, ptX); + double atan2X = Math.Atan2(ptXT.Y, ptXT.X); + double angleX = atan2X * (180.0 / Math.PI); + + PointF ptY = new PointF(0.0f, 1.0f); + PointF ptYT = Utility.TransformOneVector(matrix, ptY); + double atan2Y = Math.Atan2(ptYT.Y, ptYT.X); + double angleY = (atan2Y * (180.0 / Math.PI)) - 90.0; + + while (angleX < 0) + { + angleX += 360; + } + + while (angleY < 0) + { + angleY += 360; + } + + double angleDelta = Math.Abs(angleX - angleY); + + return angleDelta > 1.0 && angleDelta < 359.0; + } + + /// + /// Calculates the dot product of two vectors. + /// + public static float DotProduct(PointF lhs, PointF rhs) + { + return lhs.X * rhs.X + lhs.Y * rhs.Y; + } + + /// + /// Calculates the orthogonal projection of y on to u. + /// yhat = u * ((y dot u) / (u dot u)) + /// z = y - yhat + /// Section 6.2 (pg. 381) of Linear Algebra and its Applications, Second Edition, by David C. Lay + /// + /// The vector to decompose + /// The non-zero vector to project y on to + /// The orthogonal projection of y onto u + /// The length of yhat such that yhat = yhatLen * u + /// The component of y orthogonal to u + /// + /// As a special case, if u=(0,0) the results are all zero. + /// + public static void GetProjection(PointF y, PointF u, out PointF yhat, out float yhatLen, out PointF z) + { + if (u.X == 0 && u.Y == 0) + { + yhat = new PointF(0, 0); + yhatLen = 0; + z = new PointF(0, 0); + } + else + { + float yDotU = DotProduct(y, u); + float uDotU = DotProduct(u, u); + yhatLen = yDotU / uDotU; + yhat = MultiplyVector(u, yhatLen); + z = SubtractVectors(y, yhat); + } + } + + public static int GreatestCommonDivisor(int a, int b) + { + int r; + + if (a < b) + { + r = a; + a = b; + b = r; + } + + do + { + r = a % b; + a = b; + b = r; + } while (r != 0); + + return a; + } + + public static void Swap(ref int a, ref int b) + { + int t; + + t = a; + a = b; + b = t; + } + + public static void Swap(ref T a, ref T b) + { + T t; + + t = a; + a = b; + b = t; + } + + private static byte[] DownloadSmallFile(Uri uri, WebProxy proxy) + { + WebRequest request = WebRequest.Create(uri); + + if (proxy != null) + { + request.Proxy = proxy; + } + + request.Timeout = 5000; + WebResponse response = request.GetResponse(); + Stream stream = response.GetResponseStream(); + + try + { + byte[] buffer = new byte[8192]; + int offset = 0; + + while (offset < buffer.Length) + { + int bytesRead = stream.Read(buffer, offset, buffer.Length - offset); + + if (bytesRead == 0) + { + byte[] smallerBuffer = new byte[offset + bytesRead]; + + for (int i = 0; i < offset + bytesRead; ++i) + { + smallerBuffer[i] = buffer[i]; + } + + buffer = smallerBuffer; + } + + offset += bytesRead; + } + + return buffer; + } + + finally + { + if (stream != null) + { + stream.Close(); + stream = null; + } + + if (response != null) + { + response.Close(); + response = null; + } + } + } + + public static T[] RepeatArray(T[] array, int repeatCount) + { + T[] returnArray = new T[repeatCount * array.Length]; + + for (int i = 0; i < repeatCount; ++i) + { + for (int j = 0; j < array.Length; ++j) + { + int index = (i * array.Length) + j; + returnArray[index] = array[j]; + } + } + + return returnArray; + } + + /// + /// Downloads a small file (max 8192 bytes) and returns it as a byte array. + /// + /// The contents of the file if downloaded successfully. + public static byte[] DownloadSmallFile(Uri uri) + { + byte[] bytes = null; + Exception exception = null; + WebProxy[] proxiesPre = Network.GetProxyList(); + WebProxy[] proxies = RepeatArray(proxiesPre, 2); // see bug #1942 + + foreach (WebProxy proxy in proxies) + { + try + { + bytes = DownloadSmallFile(uri, proxy); + exception = null; + } + + catch (Exception ex) + { + exception = ex; + bytes = null; + } + + if (bytes != null) + { + break; + } + } + + if (exception != null) + { + WebException we = exception as WebException; + + if (we != null) + { + throw new WebException(null, we, we.Status, we.Response); + } + else + { + throw new ApplicationException("An exception occurred while trying to download '" + uri.ToString() + "'", exception); + } + } + + return bytes; + } + + private static void DownloadFile(Uri uri, Stream output, WebProxy proxy, ProgressEventHandler progressCallback) + { + WebRequest request = WebRequest.Create(uri); + + if (proxy != null) + { + request.Proxy = proxy; + } + + request.Timeout = 5000; + WebResponse response = request.GetResponse(); + Stream stream = null; + SiphonStream siphonOutputStream = null; + + try + { + stream = response.GetResponseStream(); + siphonOutputStream = new SiphonStream(output, 1024); // monitor the completion of writes to 'output' + + siphonOutputStream.IOFinished += + delegate(object sender, IOEventArgs e) + { + if (progressCallback != null) + { + double percent = 100.0 * Utility.Clamp(((double)e.Position / (double)response.ContentLength), 0, 100); + progressCallback(uri, new ProgressEventArgs(percent)); + } + }; + + Utility.CopyStream(stream, siphonOutputStream, 128 * 1024 * 1024); // cap at 128mb + siphonOutputStream.Flush(); + } + + finally + { + if (siphonOutputStream != null) + { + siphonOutputStream.Close(); + siphonOutputStream = null; + } + + if (stream != null) + { + stream.Close(); + stream = null; + } + + if (response != null) + { + response.Close(); + response = null; + } + } + } + + /// + /// Download a file (max 128MB) and saves it to the given Stream. + /// + public static void DownloadFile(Uri uri, Stream output, ProgressEventHandler progressCallback) + { + long startPosition = output.Position; + Exception exception = null; + WebProxy[] proxies = Network.GetProxyList(); + + foreach (WebProxy proxy in proxies) + { + bool success = false; + + try + { + DownloadFile(uri, output, proxy, progressCallback); + exception = null; + success = true; + } + + catch (Exception ex) + { + exception = ex; + } + + // If the output stream was written to, then we know + // that we were either successful in downloading the + // file, or there was an error unrelated to using the + // proxy (maybe they unplugged the network cable, who + // knows!) + if (output.Position != startPosition || success) + { + break; + } + } + + if (exception != null) + { + WebException we = exception as WebException; + + if (we != null) + { + throw new WebException(null, we, we.Status, we.Response); + } + else + { + throw new ApplicationException("An exception occurred while trying to download '" + uri.ToString() + "'", exception); + } + } + } + + public static byte FastScaleByteByByte(byte a, byte b) + { + int r1 = a * b + 0x80; + int r2 = ((r1 >> 8) + r1) >> 8; + return (byte)r2; + } + + public static int FastDivideShortByByte(ushort n, byte d) + { + int i = d * 3; + uint m = masTable[i]; + uint a = masTable[i + 1]; + uint s = masTable[i + 2]; + + uint nTimesMPlusA = unchecked((n * m) + a); + uint shifted = nTimesMPlusA >> (int)s; + int r = (int)shifted; + + return r; + } + + // i = z * 3; + // (x / z) = ((x * masTable[i]) + masTable[i + 1]) >> masTable[i + 2) + private static readonly uint[] masTable = + { + 0x00000000, 0x00000000, 0, // 0 + 0x00000001, 0x00000000, 0, // 1 + 0x00000001, 0x00000000, 1, // 2 + 0xAAAAAAAB, 0x00000000, 33, // 3 + 0x00000001, 0x00000000, 2, // 4 + 0xCCCCCCCD, 0x00000000, 34, // 5 + 0xAAAAAAAB, 0x00000000, 34, // 6 + 0x49249249, 0x49249249, 33, // 7 + 0x00000001, 0x00000000, 3, // 8 + 0x38E38E39, 0x00000000, 33, // 9 + 0xCCCCCCCD, 0x00000000, 35, // 10 + 0xBA2E8BA3, 0x00000000, 35, // 11 + 0xAAAAAAAB, 0x00000000, 35, // 12 + 0x4EC4EC4F, 0x00000000, 34, // 13 + 0x49249249, 0x49249249, 34, // 14 + 0x88888889, 0x00000000, 35, // 15 + 0x00000001, 0x00000000, 4, // 16 + 0xF0F0F0F1, 0x00000000, 36, // 17 + 0x38E38E39, 0x00000000, 34, // 18 + 0xD79435E5, 0xD79435E5, 36, // 19 + 0xCCCCCCCD, 0x00000000, 36, // 20 + 0xC30C30C3, 0xC30C30C3, 36, // 21 + 0xBA2E8BA3, 0x00000000, 36, // 22 + 0xB21642C9, 0x00000000, 36, // 23 + 0xAAAAAAAB, 0x00000000, 36, // 24 + 0x51EB851F, 0x00000000, 35, // 25 + 0x4EC4EC4F, 0x00000000, 35, // 26 + 0x97B425ED, 0x97B425ED, 36, // 27 + 0x49249249, 0x49249249, 35, // 28 + 0x8D3DCB09, 0x00000000, 36, // 29 + 0x88888889, 0x00000000, 36, // 30 + 0x42108421, 0x42108421, 35, // 31 + 0x00000001, 0x00000000, 5, // 32 + 0x3E0F83E1, 0x00000000, 35, // 33 + 0xF0F0F0F1, 0x00000000, 37, // 34 + 0x75075075, 0x75075075, 36, // 35 + 0x38E38E39, 0x00000000, 35, // 36 + 0x6EB3E453, 0x6EB3E453, 36, // 37 + 0xD79435E5, 0xD79435E5, 37, // 38 + 0x69069069, 0x69069069, 36, // 39 + 0xCCCCCCCD, 0x00000000, 37, // 40 + 0xC7CE0C7D, 0x00000000, 37, // 41 + 0xC30C30C3, 0xC30C30C3, 37, // 42 + 0x2FA0BE83, 0x00000000, 35, // 43 + 0xBA2E8BA3, 0x00000000, 37, // 44 + 0x5B05B05B, 0x5B05B05B, 36, // 45 + 0xB21642C9, 0x00000000, 37, // 46 + 0xAE4C415D, 0x00000000, 37, // 47 + 0xAAAAAAAB, 0x00000000, 37, // 48 + 0x5397829D, 0x00000000, 36, // 49 + 0x51EB851F, 0x00000000, 36, // 50 + 0xA0A0A0A1, 0x00000000, 37, // 51 + 0x4EC4EC4F, 0x00000000, 36, // 52 + 0x9A90E7D9, 0x9A90E7D9, 37, // 53 + 0x97B425ED, 0x97B425ED, 37, // 54 + 0x94F2094F, 0x94F2094F, 37, // 55 + 0x49249249, 0x49249249, 36, // 56 + 0x47DC11F7, 0x47DC11F7, 36, // 57 + 0x8D3DCB09, 0x00000000, 37, // 58 + 0x22B63CBF, 0x00000000, 35, // 59 + 0x88888889, 0x00000000, 37, // 60 + 0x4325C53F, 0x00000000, 36, // 61 + 0x42108421, 0x42108421, 36, // 62 + 0x41041041, 0x41041041, 36, // 63 + 0x00000001, 0x00000000, 6, // 64 + 0xFC0FC0FD, 0x00000000, 38, // 65 + 0x3E0F83E1, 0x00000000, 36, // 66 + 0x07A44C6B, 0x00000000, 33, // 67 + 0xF0F0F0F1, 0x00000000, 38, // 68 + 0x76B981DB, 0x00000000, 37, // 69 + 0x75075075, 0x75075075, 37, // 70 + 0xE6C2B449, 0x00000000, 38, // 71 + 0x38E38E39, 0x00000000, 36, // 72 + 0x381C0E07, 0x381C0E07, 36, // 73 + 0x6EB3E453, 0x6EB3E453, 37, // 74 + 0x1B4E81B5, 0x00000000, 35, // 75 + 0xD79435E5, 0xD79435E5, 38, // 76 + 0x3531DEC1, 0x00000000, 36, // 77 + 0x69069069, 0x69069069, 37, // 78 + 0xCF6474A9, 0x00000000, 38, // 79 + 0xCCCCCCCD, 0x00000000, 38, // 80 + 0xCA4587E7, 0x00000000, 38, // 81 + 0xC7CE0C7D, 0x00000000, 38, // 82 + 0x3159721F, 0x00000000, 36, // 83 + 0xC30C30C3, 0xC30C30C3, 38, // 84 + 0xC0C0C0C1, 0x00000000, 38, // 85 + 0x2FA0BE83, 0x00000000, 36, // 86 + 0x2F149903, 0x00000000, 36, // 87 + 0xBA2E8BA3, 0x00000000, 38, // 88 + 0xB81702E1, 0x00000000, 38, // 89 + 0x5B05B05B, 0x5B05B05B, 37, // 90 + 0x2D02D02D, 0x2D02D02D, 36, // 91 + 0xB21642C9, 0x00000000, 38, // 92 + 0xB02C0B03, 0x00000000, 38, // 93 + 0xAE4C415D, 0x00000000, 38, // 94 + 0x2B1DA461, 0x2B1DA461, 36, // 95 + 0xAAAAAAAB, 0x00000000, 38, // 96 + 0xA8E83F57, 0xA8E83F57, 38, // 97 + 0x5397829D, 0x00000000, 37, // 98 + 0xA57EB503, 0x00000000, 38, // 99 + 0x51EB851F, 0x00000000, 37, // 100 + 0xA237C32B, 0xA237C32B, 38, // 101 + 0xA0A0A0A1, 0x00000000, 38, // 102 + 0x9F1165E7, 0x9F1165E7, 38, // 103 + 0x4EC4EC4F, 0x00000000, 37, // 104 + 0x27027027, 0x27027027, 36, // 105 + 0x9A90E7D9, 0x9A90E7D9, 38, // 106 + 0x991F1A51, 0x991F1A51, 38, // 107 + 0x97B425ED, 0x97B425ED, 38, // 108 + 0x2593F69B, 0x2593F69B, 36, // 109 + 0x94F2094F, 0x94F2094F, 38, // 110 + 0x24E6A171, 0x24E6A171, 36, // 111 + 0x49249249, 0x49249249, 37, // 112 + 0x90FDBC09, 0x90FDBC09, 38, // 113 + 0x47DC11F7, 0x47DC11F7, 37, // 114 + 0x8E78356D, 0x8E78356D, 38, // 115 + 0x8D3DCB09, 0x00000000, 38, // 116 + 0x23023023, 0x23023023, 36, // 117 + 0x22B63CBF, 0x00000000, 36, // 118 + 0x44D72045, 0x00000000, 37, // 119 + 0x88888889, 0x00000000, 38, // 120 + 0x8767AB5F, 0x8767AB5F, 38, // 121 + 0x4325C53F, 0x00000000, 37, // 122 + 0x85340853, 0x85340853, 38, // 123 + 0x42108421, 0x42108421, 37, // 124 + 0x10624DD3, 0x00000000, 35, // 125 + 0x41041041, 0x41041041, 37, // 126 + 0x10204081, 0x10204081, 35, // 127 + 0x00000001, 0x00000000, 7, // 128 + 0x0FE03F81, 0x00000000, 35, // 129 + 0xFC0FC0FD, 0x00000000, 39, // 130 + 0xFA232CF3, 0x00000000, 39, // 131 + 0x3E0F83E1, 0x00000000, 37, // 132 + 0xF6603D99, 0x00000000, 39, // 133 + 0x07A44C6B, 0x00000000, 34, // 134 + 0xF2B9D649, 0x00000000, 39, // 135 + 0xF0F0F0F1, 0x00000000, 39, // 136 + 0x077975B9, 0x00000000, 34, // 137 + 0x76B981DB, 0x00000000, 38, // 138 + 0x75DED953, 0x00000000, 38, // 139 + 0x75075075, 0x75075075, 38, // 140 + 0x3A196B1F, 0x00000000, 37, // 141 + 0xE6C2B449, 0x00000000, 39, // 142 + 0xE525982B, 0x00000000, 39, // 143 + 0x38E38E39, 0x00000000, 37, // 144 + 0xE1FC780F, 0x00000000, 39, // 145 + 0x381C0E07, 0x381C0E07, 37, // 146 + 0xDEE95C4D, 0x00000000, 39, // 147 + 0x6EB3E453, 0x6EB3E453, 38, // 148 + 0xDBEB61EF, 0x00000000, 39, // 149 + 0x1B4E81B5, 0x00000000, 36, // 150 + 0x36406C81, 0x00000000, 37, // 151 + 0xD79435E5, 0xD79435E5, 39, // 152 + 0xD62B80D7, 0x00000000, 39, // 153 + 0x3531DEC1, 0x00000000, 37, // 154 + 0xD3680D37, 0x00000000, 39, // 155 + 0x69069069, 0x69069069, 38, // 156 + 0x342DA7F3, 0x00000000, 37, // 157 + 0xCF6474A9, 0x00000000, 39, // 158 + 0xCE168A77, 0xCE168A77, 39, // 159 + 0xCCCCCCCD, 0x00000000, 39, // 160 + 0xCB8727C1, 0x00000000, 39, // 161 + 0xCA4587E7, 0x00000000, 39, // 162 + 0xC907DA4F, 0x00000000, 39, // 163 + 0xC7CE0C7D, 0x00000000, 39, // 164 + 0x634C0635, 0x00000000, 38, // 165 + 0x3159721F, 0x00000000, 37, // 166 + 0x621B97C3, 0x00000000, 38, // 167 + 0xC30C30C3, 0xC30C30C3, 39, // 168 + 0x60F25DEB, 0x00000000, 38, // 169 + 0xC0C0C0C1, 0x00000000, 39, // 170 + 0x17F405FD, 0x17F405FD, 36, // 171 + 0x2FA0BE83, 0x00000000, 37, // 172 + 0xBD691047, 0xBD691047, 39, // 173 + 0x2F149903, 0x00000000, 37, // 174 + 0x5D9F7391, 0x00000000, 38, // 175 + 0xBA2E8BA3, 0x00000000, 39, // 176 + 0x5C90A1FD, 0x5C90A1FD, 38, // 177 + 0xB81702E1, 0x00000000, 39, // 178 + 0x5B87DDAD, 0x5B87DDAD, 38, // 179 + 0x5B05B05B, 0x5B05B05B, 38, // 180 + 0xB509E68B, 0x00000000, 39, // 181 + 0x2D02D02D, 0x2D02D02D, 37, // 182 + 0xB30F6353, 0x00000000, 39, // 183 + 0xB21642C9, 0x00000000, 39, // 184 + 0x1623FA77, 0x1623FA77, 36, // 185 + 0xB02C0B03, 0x00000000, 39, // 186 + 0xAF3ADDC7, 0x00000000, 39, // 187 + 0xAE4C415D, 0x00000000, 39, // 188 + 0x15AC056B, 0x15AC056B, 36, // 189 + 0x2B1DA461, 0x2B1DA461, 37, // 190 + 0xAB8F69E3, 0x00000000, 39, // 191 + 0xAAAAAAAB, 0x00000000, 39, // 192 + 0x15390949, 0x00000000, 36, // 193 + 0xA8E83F57, 0xA8E83F57, 39, // 194 + 0x15015015, 0x15015015, 36, // 195 + 0x5397829D, 0x00000000, 38, // 196 + 0xA655C439, 0xA655C439, 39, // 197 + 0xA57EB503, 0x00000000, 39, // 198 + 0x5254E78F, 0x00000000, 38, // 199 + 0x51EB851F, 0x00000000, 38, // 200 + 0x028C1979, 0x00000000, 33, // 201 + 0xA237C32B, 0xA237C32B, 39, // 202 + 0xA16B312F, 0x00000000, 39, // 203 + 0xA0A0A0A1, 0x00000000, 39, // 204 + 0x4FEC04FF, 0x00000000, 38, // 205 + 0x9F1165E7, 0x9F1165E7, 39, // 206 + 0x27932B49, 0x00000000, 37, // 207 + 0x4EC4EC4F, 0x00000000, 38, // 208 + 0x9CC8E161, 0x00000000, 39, // 209 + 0x27027027, 0x27027027, 37, // 210 + 0x9B4C6F9F, 0x00000000, 39, // 211 + 0x9A90E7D9, 0x9A90E7D9, 39, // 212 + 0x99D722DB, 0x00000000, 39, // 213 + 0x991F1A51, 0x991F1A51, 39, // 214 + 0x4C346405, 0x00000000, 38, // 215 + 0x97B425ED, 0x97B425ED, 39, // 216 + 0x4B809701, 0x4B809701, 38, // 217 + 0x2593F69B, 0x2593F69B, 37, // 218 + 0x12B404AD, 0x12B404AD, 36, // 219 + 0x94F2094F, 0x94F2094F, 39, // 220 + 0x25116025, 0x25116025, 37, // 221 + 0x24E6A171, 0x24E6A171, 37, // 222 + 0x24BC44E1, 0x24BC44E1, 37, // 223 + 0x49249249, 0x49249249, 38, // 224 + 0x91A2B3C5, 0x00000000, 39, // 225 + 0x90FDBC09, 0x90FDBC09, 39, // 226 + 0x905A3863, 0x905A3863, 39, // 227 + 0x47DC11F7, 0x47DC11F7, 38, // 228 + 0x478BBCED, 0x00000000, 38, // 229 + 0x8E78356D, 0x8E78356D, 39, // 230 + 0x46ED2901, 0x46ED2901, 38, // 231 + 0x8D3DCB09, 0x00000000, 39, // 232 + 0x2328A701, 0x2328A701, 37, // 233 + 0x23023023, 0x23023023, 37, // 234 + 0x45B81A25, 0x45B81A25, 38, // 235 + 0x22B63CBF, 0x00000000, 37, // 236 + 0x08A42F87, 0x08A42F87, 35, // 237 + 0x44D72045, 0x00000000, 38, // 238 + 0x891AC73B, 0x00000000, 39, // 239 + 0x88888889, 0x00000000, 39, // 240 + 0x10FEF011, 0x00000000, 36, // 241 + 0x8767AB5F, 0x8767AB5F, 39, // 242 + 0x86D90545, 0x00000000, 39, // 243 + 0x4325C53F, 0x00000000, 38, // 244 + 0x85BF3761, 0x85BF3761, 39, // 245 + 0x85340853, 0x85340853, 39, // 246 + 0x10953F39, 0x10953F39, 36, // 247 + 0x42108421, 0x42108421, 38, // 248 + 0x41CC9829, 0x41CC9829, 38, // 249 + 0x10624DD3, 0x00000000, 36, // 250 + 0x828CBFBF, 0x00000000, 39, // 251 + 0x41041041, 0x41041041, 38, // 252 + 0x81848DA9, 0x00000000, 39, // 253 + 0x10204081, 0x10204081, 36, // 254 + 0x80808081, 0x00000000, 39 // 255 + }; + } +} diff --git a/src/Core/VerticalSnapEdge.cs b/src/Core/VerticalSnapEdge.cs new file mode 100644 index 0000000..96d6482 --- /dev/null +++ b/src/Core/VerticalSnapEdge.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum VerticalSnapEdge + { + Neither, + Left, + Right + } +} diff --git a/src/Core/WaitCursorChanger.cs b/src/Core/WaitCursorChanger.cs new file mode 100644 index 0000000..9e9d75d --- /dev/null +++ b/src/Core/WaitCursorChanger.cs @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Simply sets a control's Cursor to the WaitCursor (hourglass) on creation, + /// and sets it back to its original setting upon disposal. + /// + public sealed class WaitCursorChanger + : IDisposable + { + private Control control; + private Cursor oldCursor; + private static int nextID = 0; + private int id = System.Threading.Interlocked.Increment(ref nextID); + + public WaitCursorChanger(Control control) + { + this.control = control; + this.oldCursor = Cursor.Current; + Cursor.Current = Cursors.WaitCursor; + } + + ~WaitCursorChanger() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.oldCursor != null) + { + Cursor.Current = this.oldCursor; + this.oldCursor = null; + } + } + } + } +} diff --git a/src/Core/WorkerThreadException.cs b/src/Core/WorkerThreadException.cs new file mode 100644 index 0000000..3d505be --- /dev/null +++ b/src/Core/WorkerThreadException.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// This exception is thrown by a foreground thread when a background worker thread + /// had an exception. This allows all exceptions to be handled by the foreground thread. + /// + public class WorkerThreadException + : PdnException + { + private const string defaultMessage = "Worker thread threw an exception"; + + public WorkerThreadException(Exception innerException) + : this(defaultMessage, innerException) + { + } + + public WorkerThreadException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/Data/AssemblyInfo.cs b/src/Data/AssemblyInfo.cs new file mode 100644 index 0000000..9d9ef01 --- /dev/null +++ b/src/Data/AssemblyInfo.cs @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Paint.NET Data")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Always)] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: ComVisibleAttribute(false)] diff --git a/src/Data/BitmapLayer.cs b/src/Data/BitmapLayer.cs new file mode 100644 index 0000000..c0296b4 --- /dev/null +++ b/src/Data/BitmapLayer.cs @@ -0,0 +1,399 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Runtime.Serialization; +using System.Threading; + +namespace PaintDotNet +{ + [Serializable] + public class BitmapLayer + : Layer, + IDeserializationCallback + { + public override Surface RenderThumbnail(int maxEdgeLength) + { + Size thumbSize = Utility.ComputeThumbnailSize(this.Size, maxEdgeLength); + Surface thumb = new Surface(thumbSize); + + thumb.SuperSamplingFitSurface(this.surface); + + Surface thumb2 = new Surface(thumbSize); + thumb2.ClearWithCheckboardPattern(); + UserBlendOps.NormalBlendOp nbop = new UserBlendOps.NormalBlendOp(); + nbop.Apply(thumb2, thumb); + + thumb.Dispose(); + thumb = null; + + return thumb2; + } + + private bool disposed = false; + protected override void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + + try + { + if (disposing) + { + if (surface != null) + { + surface.Dispose(); + surface = null; + } + } + } + + finally + { + base.Dispose(disposing); + } + } + } + + [NonSerialized] + private BinaryPixelOp compiledBlendOp = null; + + private void CompileBlendOp() + { + bool isDefaultOp = (properties.blendOp.GetType() == UserBlendOps.GetDefaultBlendOp()); + + if (this.Opacity == 255) + { + this.compiledBlendOp = properties.blendOp; + } + else + { + this.compiledBlendOp = properties.blendOp.CreateWithOpacity(this.Opacity); + } + } + + protected override void OnPropertyChanged(string propertyName) + { + compiledBlendOp = null; + base.OnPropertyChanged (propertyName); + } + + [Serializable] + internal sealed class BitmapLayerProperties + : ICloneable, + ISerializable + { + public UserBlendOp blendOp; + internal int opacity; // this is ONLY used when loading older version PDN files! should normally equal -1 + + private const string blendOpTag = "blendOp"; + private const string opacityTag = "opacity"; + + public static string BlendOpName + { + get + { + return PdnResources.GetString("BitmapLayer.Properties.BlendOp.Name"); + } + } + + public BitmapLayerProperties(UserBlendOp blendOp) + { + this.blendOp = blendOp; + this.opacity = -1; + } + + public BitmapLayerProperties(BitmapLayerProperties cloneMe) + { + this.blendOp = cloneMe.blendOp; + this.opacity = -1; + } + + public object Clone() + { + return new BitmapLayerProperties(this); + } + + public BitmapLayerProperties(SerializationInfo info, StreamingContext context) + { + this.blendOp = (UserBlendOp)info.GetValue(blendOpTag, typeof(UserBlendOp)); + + // search for 'opacity' and load it if it exists + this.opacity = -1; + + foreach (SerializationEntry entry in info) + { + if (entry.Name == opacityTag) + { + this.opacity = (int)((byte)entry.Value); + break; + } + } + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(blendOpTag, this.blendOp); + } + } + + private BitmapLayerProperties properties; + private Surface surface; + + public override object SaveProperties() + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + object baseProperties = base.SaveProperties(); + return new List(properties.Clone(), new List(baseProperties, null)); + } + + public override void LoadProperties(object oldState, bool suppressEvents) + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + List list = (List)oldState; + + // Get the base class' state, and our state + LayerProperties baseState = (LayerProperties)list.Tail.Head; + BitmapLayerProperties blp = (BitmapLayerProperties)(((List)oldState).Head); + + // Opacity is only couriered for compatibility with PDN v2.0 and v1.1 + // files. It should not be present in v2.1+ files (well, it'll be + // part of the base class' serialization) + if (blp.opacity != -1) + { + baseState.opacity = (byte)blp.opacity; + blp.opacity = -1; + } + + // Have the base class load its properties + base.LoadProperties(baseState, suppressEvents); + + // Now load our properties, and announce them to the world + bool raiseBlendOp = false; + + if (blp.blendOp.GetType() != properties.blendOp.GetType()) + { + if (!suppressEvents) + { + raiseBlendOp = true; + OnPropertyChanging(BitmapLayerProperties.BlendOpName); + } + } + + this.properties = (BitmapLayerProperties)blp.Clone(); + this.compiledBlendOp = null; + + Invalidate(); + + if (raiseBlendOp) + { + OnPropertyChanged(BitmapLayerProperties.BlendOpName); + } + } + + public void SetBlendOp(UserBlendOp blendOp) + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + if (blendOp.GetType() != properties.blendOp.GetType()) + { + OnPropertyChanging(BitmapLayerProperties.BlendOpName); + properties.blendOp = blendOp; + compiledBlendOp = null; + Invalidate(); + OnPropertyChanged(BitmapLayerProperties.BlendOpName); + } + } + + public override object Clone() + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + return (object)new BitmapLayer(this); + } + + public Surface Surface + { + get + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + return surface; + } + } + + public UserBlendOp BlendOp + { + get + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + return properties.blendOp; + } + } + + public BitmapLayer(int width, int height) + : this(width, height, ColorBgra.FromBgra(255, 255, 255, 0)) + { + } + + public BitmapLayer(int width, int height, ColorBgra fillColor) + : base(width, height) + { + this.surface = new Surface(width, height); + // clear to see-through white, 0x00ffffff + this.Surface.Clear(fillColor); + this.properties = new BitmapLayerProperties(UserBlendOps.CreateDefaultBlendOp()); + } + + /// + /// Creates a new BitmapLayer of the same size as the given Surface, and copies the + /// pixels from the given Surface. + /// + /// The Surface to copy pixels from. + public BitmapLayer(Surface surface) + : this(surface, false) + { + } + + /// + /// Creates a new BitmapLayer of the same size as the given Surface, and either + /// copies the pixels of the given Surface or takes ownership of it. + /// + /// The Surface. + /// + /// true to take ownership of the surface (make sure to Dispose() it yourself), or + /// false to copy its pixels + /// + public BitmapLayer(Surface surface, bool takeOwnership) + : base(surface.Width, surface.Height) + { + if (takeOwnership) + { + this.surface = surface; + } + else + { + this.surface = surface.Clone(); + } + + this.properties = new BitmapLayerProperties(UserBlendOps.CreateDefaultBlendOp()); + } + + protected BitmapLayer(BitmapLayer copyMe) + : base(copyMe) + { + this.surface = copyMe.Surface.Clone(); + this.properties = (BitmapLayerProperties)copyMe.properties.Clone(); + } + + protected unsafe override void RenderImpl(RenderArgs args, Rectangle roi) + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + if (Opacity == 0) + { + return; + } + + if (compiledBlendOp == null) + { + CompileBlendOp(); + } + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra *dstPtr = args.Surface.GetPointAddressUnchecked(roi.Left, y); + ColorBgra *srcPtr = this.surface.GetPointAddressUnchecked(roi.Left, y); + + this.compiledBlendOp.Apply(dstPtr, srcPtr, roi.Width); + } + } + + protected unsafe override void RenderImpl(RenderArgs args, Rectangle[] rois, int startIndex, int length) + { + if (disposed) + { + throw new ObjectDisposedException("BitmapLayer"); + } + + if (Opacity == 0) + { + return; + } + + if (compiledBlendOp == null) + { + CompileBlendOp(); + } + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle roi = rois[i]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra *dstPtr = args.Surface.GetPointAddressUnchecked(roi.Left, y); + ColorBgra *srcPtr = this.surface.GetPointAddressUnchecked(roi.Left, y); + + this.compiledBlendOp.Apply(dstPtr, srcPtr, roi.Width); + } + } + } + + public override PdnBaseForm CreateConfigDialog() + { + BitmapLayerPropertiesDialog blpd = new BitmapLayerPropertiesDialog(); + blpd.Layer = this; + return blpd; + } + + public void OnDeserialization(object sender) + { + if (this.properties.opacity != -1) + { + this.PushSuppressPropertyChanged(); + base.Opacity = (byte)this.properties.opacity; + this.properties.opacity = -1; + this.PopSuppressPropertyChanged(); + } + + this.compiledBlendOp = null; + } + } +} diff --git a/src/Data/BitmapLayerPropertiesDialog.cs b/src/Data/BitmapLayerPropertiesDialog.cs new file mode 100644 index 0000000..fbb183d --- /dev/null +++ b/src/Data/BitmapLayerPropertiesDialog.cs @@ -0,0 +1,297 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class BitmapLayerPropertiesDialog + : LayerPropertiesDialog + { + private System.Windows.Forms.Label opacityLabel; + private System.Windows.Forms.Label blendModeLabel; + private System.Windows.Forms.ComboBox blendOpComboBox; + private System.Windows.Forms.NumericUpDown opacityUpDown; + private System.Windows.Forms.TrackBar opacityTrackBar; + private PaintDotNet.HeaderLabel blendingHeader; + private System.ComponentModel.IContainer components = null; + + public BitmapLayerPropertiesDialog() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + this.blendingHeader.Text = PdnResources.GetString("BitmapLayerPropertiesDialog.BlendingHeader.Text"); + this.blendModeLabel.Text = PdnResources.GetString("BitmapLayerPropertiesDialog.BlendModeLabel.Text"); + this.opacityLabel.Text = PdnResources.GetString("BitmapLayerPropertiesDialog.OpacityLabel.Text"); + + // populate the blendOpComboBox with all the blend modes they're allowed to use + foreach (Type type in UserBlendOps.GetBlendOps()) + { + blendOpComboBox.Items.Add(UserBlendOps.CreateBlendOp(type)); + } + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + } + + private void SelectOp(UserBlendOp setOp) + { + foreach (object op in blendOpComboBox.Items) + { + if (op.ToString() == setOp.ToString()) + { + blendOpComboBox.SelectedItem = op; + break; + } + } + } + + protected override void InitDialogFromLayer() + { + opacityUpDown.Value = Layer.Opacity; + SelectOp(((BitmapLayer)Layer).BlendOp); + base.InitDialogFromLayer(); + } + + protected override void InitLayerFromDialog() + { + ((BitmapLayer)Layer).Opacity = (byte)opacityUpDown.Value; + + if (blendOpComboBox.SelectedItem != null) + { + ((BitmapLayer)Layer).SetBlendOp((UserBlendOp)blendOpComboBox.SelectedItem); + } + + base.InitLayerFromDialog(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.blendModeLabel = new System.Windows.Forms.Label(); + this.blendOpComboBox = new System.Windows.Forms.ComboBox(); + this.opacityUpDown = new System.Windows.Forms.NumericUpDown(); + this.opacityTrackBar = new System.Windows.Forms.TrackBar(); + this.opacityLabel = new System.Windows.Forms.Label(); + this.blendingHeader = new PaintDotNet.HeaderLabel(); + ((System.ComponentModel.ISupportInitialize)(this.opacityUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.opacityTrackBar)).BeginInit(); + this.SuspendLayout(); + // + // visibleCheckBox + // + this.visibleCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.visibleCheckBox.Location = new System.Drawing.Point(8, 48); + this.visibleCheckBox.Name = "visibleCheckBox"; + // + // nameLabel + // + this.nameLabel.Location = new System.Drawing.Point(6, 27); + this.nameLabel.Name = "nameLabel"; + // + // nameBox + // + this.nameBox.Name = "nameBox"; + // + // cancelButton + // + this.cancelButton.Location = new System.Drawing.Point(193, 152); + this.cancelButton.Name = "cancelButton"; + // + // okButton + // + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(112, 152); + this.okButton.Name = "okButton"; + // + // blendModeLabel + // + this.blendModeLabel.Location = new System.Drawing.Point(6, 92); + this.blendModeLabel.Name = "blendModeLabel"; + this.blendModeLabel.AutoSize = true; + this.blendModeLabel.Size = new System.Drawing.Size(50, 23); + this.blendModeLabel.TabIndex = 4; + // + // blendOpComboBox + // + this.blendOpComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.blendOpComboBox.Location = new System.Drawing.Point(64, 88); + this.blendOpComboBox.Name = "blendOpComboBox"; + this.blendOpComboBox.Size = new System.Drawing.Size(121, 21); + this.blendOpComboBox.TabIndex = 4; + this.blendOpComboBox.SelectedIndexChanged += new System.EventHandler(this.blendOpComboBox_SelectedIndexChanged); + this.blendOpComboBox.MaxDropDownItems = 100; + // + // opacityUpDown + // + this.opacityUpDown.Location = new System.Drawing.Point(64, 116); + this.opacityUpDown.Maximum = new System.Decimal(new int[] { + 255, + 0, + 0, + 0}); + this.opacityUpDown.Name = "opacityUpDown"; + this.opacityUpDown.Size = new System.Drawing.Size(56, 20); + this.opacityUpDown.TabIndex = 5; + this.opacityUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.opacityUpDown.Enter += new System.EventHandler(this.opacityUpDown_Enter); + this.opacityUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.opacityUpDown_KeyUp); + this.opacityUpDown.ValueChanged += new System.EventHandler(this.opacityUpDown_ValueChanged); + this.opacityUpDown.Leave += new System.EventHandler(this.opacityUpDown_Leave); + // + // opacityTrackBar + // + this.opacityTrackBar.AutoSize = false; + this.opacityTrackBar.LargeChange = 32; + this.opacityTrackBar.Location = new System.Drawing.Point(129, 114); + this.opacityTrackBar.Maximum = 255; + this.opacityTrackBar.Name = "opacityTrackBar"; + this.opacityTrackBar.Size = new System.Drawing.Size(146, 24); + this.opacityTrackBar.TabIndex = 6; + this.opacityTrackBar.TickStyle = System.Windows.Forms.TickStyle.None; + this.opacityTrackBar.ValueChanged += new System.EventHandler(this.opacityTrackBar_ValueChanged); + // + // opacityLabel + // + this.opacityLabel.Location = new System.Drawing.Point(6, 118); + this.opacityLabel.AutoSize = true; + this.opacityLabel.Name = "opacityLabel"; + this.opacityLabel.Size = new System.Drawing.Size(48, 16); + this.opacityLabel.TabIndex = 0; + // + // blendingHeader + // + this.blendingHeader.Location = new System.Drawing.Point(6, 72); + this.blendingHeader.Name = "blendingHeader"; + this.blendingHeader.Size = new System.Drawing.Size(269, 14); + this.blendingHeader.TabIndex = 8; + this.blendingHeader.TabStop = false; + // + // BitmapLayerPropertiesDialog + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(274, 181); + this.Controls.Add(this.blendingHeader); + this.Controls.Add(this.blendOpComboBox); + this.Controls.Add(this.opacityUpDown); + this.Controls.Add(this.opacityLabel); + this.Controls.Add(this.blendModeLabel); + this.Controls.Add(this.opacityTrackBar); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "BitmapLayerPropertiesDialog"; + this.Controls.SetChildIndex(this.opacityTrackBar, 0); + this.Controls.SetChildIndex(this.blendModeLabel, 0); + this.Controls.SetChildIndex(this.opacityLabel, 0); + this.Controls.SetChildIndex(this.opacityUpDown, 0); + this.Controls.SetChildIndex(this.blendOpComboBox, 0); + this.Controls.SetChildIndex(this.nameLabel, 0); + this.Controls.SetChildIndex(this.visibleCheckBox, 0); + this.Controls.SetChildIndex(this.nameBox, 0); + this.Controls.SetChildIndex(this.blendingHeader, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.okButton, 0); + ((System.ComponentModel.ISupportInitialize)(this.opacityUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.opacityTrackBar)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + private void ChangeLayerOpacity() + { + if (((BitmapLayer)Layer).Opacity != (byte)opacityUpDown.Value) + { + Layer.PushSuppressPropertyChanged(); + ((BitmapLayer)Layer).Opacity = (byte)opacityTrackBar.Value; + Layer.PopSuppressPropertyChanged(); + } + } + + private void opacityUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (opacityTrackBar.Value != (int)opacityUpDown.Value) + { + using (new WaitCursorChanger(this)) + { + opacityTrackBar.Value = (int)opacityUpDown.Value; + ChangeLayerOpacity(); + } + } + } + + private void opacityUpDown_Enter(object sender, System.EventArgs e) + { + opacityUpDown.Select(0, opacityUpDown.Text.Length); + } + + private void opacityUpDown_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) + { + } + + private void opacityTrackBar_ValueChanged(object sender, System.EventArgs e) + { + if (opacityUpDown.Value != (decimal)opacityTrackBar.Value) + { + using (new WaitCursorChanger(this)) + { + opacityUpDown.Value = (decimal)opacityTrackBar.Value; + ChangeLayerOpacity(); + } + } + } + + private void opacityUpDown_Leave(object sender, System.EventArgs e) + { + opacityUpDown_ValueChanged(sender, e); + } + + private void blendOpComboBox_SelectedIndexChanged(object sender, System.EventArgs e) + { + using (new WaitCursorChanger(this)) + { + Layer.PushSuppressPropertyChanged(); + + if (blendOpComboBox.SelectedItem != null) + { + ((BitmapLayer)Layer).SetBlendOp((UserBlendOp)blendOpComboBox.SelectedItem); + } + + Layer.PopSuppressPropertyChanged(); + } + } + } +} \ No newline at end of file diff --git a/src/Data/BitmapLayerPropertiesDialog.resx b/src/Data/BitmapLayerPropertiesDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Data/BmpFileType.cs b/src/Data/BmpFileType.cs new file mode 100644 index 0000000..ccc8c7d --- /dev/null +++ b/src/Data/BmpFileType.cs @@ -0,0 +1,190 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.IndirectUI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace PaintDotNet +{ + public sealed class BmpFileType + : InternalFileType + { + public BmpFileType() + : base("BMP", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving, new string[] { ".bmp" }) + { + } + + public enum PropertyNames + { + BitDepth = 0, + DitherLevel = 1 + } + + public enum BmpBitDepthUIChoices + { + AutoDetect = 0, + Bpp24 = 1, + Bpp8 = 2 + } + + public override PropertyCollection OnCreateSavePropertyCollection() + { + List props = new List(); + + props.Add(StaticListChoiceProperty.CreateForEnum(PropertyNames.BitDepth, BmpBitDepthUIChoices.AutoDetect, false)); + props.Add(new Int32Property(PropertyNames.DitherLevel, 7, 0, 8)); + + List rules = new List(); + + rules.Add(new ReadOnlyBoundToValueRule(PropertyNames.DitherLevel, PropertyNames.BitDepth, BmpBitDepthUIChoices.Bpp8, true)); + + PropertyCollection pc = new PropertyCollection(props, rules); + + return pc; + } + + public override ControlInfo OnCreateSaveConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultSaveConfigUI(props); + + configUI.SetPropertyControlValue( + PropertyNames.BitDepth, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("BmpFileType.ConfigUI.BitDepth.DisplayName")); + + PropertyControlInfo bitDepthPCI = configUI.FindControlForPropertyName(PropertyNames.BitDepth); + bitDepthPCI.SetValueDisplayName(BmpBitDepthUIChoices.AutoDetect, PdnResources.GetString("BmpFileType.ConfigUI.BitDepth.AutoDetect.DisplayName")); + bitDepthPCI.SetValueDisplayName(BmpBitDepthUIChoices.Bpp24, PdnResources.GetString("BmpFileType.ConfigUI.BitDepth.Bpp24.DisplayName")); + bitDepthPCI.SetValueDisplayName(BmpBitDepthUIChoices.Bpp8, PdnResources.GetString("BmpFileType.ConfigUI.BitDepth.Bpp8.DisplayName")); + + configUI.SetPropertyControlType(PropertyNames.BitDepth, PropertyControlType.RadioButton); + + configUI.SetPropertyControlValue( + PropertyNames.DitherLevel, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("BmpFileType.ConfigUI.DitherLevel.DisplayName")); + + return configUI; + } + + protected override Document OnLoad(Stream input) + { + // This allows us to open images that were created in Explorer using New -> Bitmap Image + // which actually just creates a 0-byte file + if (input.Length == 0) + { + Document newDoc = new Document(800, 600); + + Layer layer = Layer.CreateBackgroundLayer(newDoc.Width, newDoc.Height); + + newDoc.Layers.Add(layer); + return newDoc; + } + else + { + using (Image image = PdnResources.LoadImage(input)) + { + Document document = Document.FromImage(image); + return document; + } + } + } + + internal override int GetDitherLevelFromToken(PropertyBasedSaveConfigToken token) + { + int ditherLevel = token.GetProperty(PropertyNames.DitherLevel).Value; + return ditherLevel; + } + + internal override int GetThresholdFromToken(PropertyBasedSaveConfigToken token) + { + return 0; + } + + internal override Set CreateAllowedBitDepthListFromToken(PropertyBasedSaveConfigToken token) + { + BmpBitDepthUIChoices bitDepth = (BmpBitDepthUIChoices)token.GetProperty(PropertyNames.BitDepth).Value; + + Set bitDepths = new Set(); + + switch (bitDepth) + { + case BmpBitDepthUIChoices.AutoDetect: + bitDepths.Add(SavableBitDepths.Rgb24); + bitDepths.Add(SavableBitDepths.Rgb8); + break; + + case BmpBitDepthUIChoices.Bpp24: + bitDepths.Add(SavableBitDepths.Rgb24); + break; + + case BmpBitDepthUIChoices.Bpp8: + bitDepths.Add(SavableBitDepths.Rgb8); + break; + + default: + throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(BmpBitDepthUIChoices)); + } + + return bitDepths; + } + + internal override void FinalSave( + Document input, + Stream output, + Surface scratchSurface, + int ditherLevel, + SavableBitDepths bitDepth, + PropertyBasedSaveConfigToken token, + ProgressEventHandler progressCallback) + { + // finally, do the save. + if (bitDepth == SavableBitDepths.Rgb24) + { + // In order to save memory, we 'squish' the 32-bit bitmap down to 24-bit in-place + // instead of allocating a new bitmap and copying it over. + SquishSurfaceTo24Bpp(scratchSurface); + + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Bmp); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(Encoder.ColorDepth, 24); + parms.Param[0] = parm; + + using (Bitmap bitmap = CreateAliased24BppBitmap(scratchSurface)) + { + GdiPlusFileType.LoadProperties(bitmap, input); + bitmap.Save(output, icf, parms); + } + } + else if (bitDepth == SavableBitDepths.Rgb8) + { + using (Bitmap quantized = Quantize(scratchSurface, ditherLevel, 256, false, progressCallback)) + { + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Bmp); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(Encoder.ColorDepth, 8); + parms.Param[0] = parm; + + GdiPlusFileType.LoadProperties(quantized, input); + quantized.Save(output, icf, parms); + } + } + else + { + throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(SavableBitDepths)); + } + } + } +} diff --git a/src/Data/Data.csproj b/src/Data/Data.csproj new file mode 100644 index 0000000..6fd7fc1 --- /dev/null +++ b/src/Data/Data.csproj @@ -0,0 +1,236 @@ + + + Local + 9.0.21022 + 2.0 + {66681BB0-955D-451D-A466-94C045B1CF4A} + Debug + AnyCPU + + + + + PaintDotNet.Data + + + JScript + Grid + IE50 + false + Library + PaintDotNet + OnBuildSuccess + + + + + + + 2.0 + + + bin\Debug\ + true + 274726912 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + bin\Release\ + true + 274726912 + false + + + TRACE + + + true + 512 + false + + + true + false + false + true + 4 + pdbonly + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + + System + + + + System.Drawing + + + System.Runtime.Serialization.Formatters.Soap + + + System.Windows.Forms + + + System.XML + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + Core + {1EADE568-A866-4DD4-9898-0A151E3F0E26} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + Resources + {0B173113-1F9B-4939-A62F-A176336F13AC} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + SystemLayer + {80572820-93A5-4278-A513-D902BEA2639C} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + Code + + + Code + + + Form + + + Code + + + Code + + + UserControl + + + Code + + + Code + + + Code + + + Code + + + + Code + + + + Code + + + Code + + + Code + + + + Code + + + Code + + + Code + + + Form + + + Code + + + UserControl + + + Code + + + Code + + + + + + + UserControl + + + Code + + + Code + + + Code + + + Code + + + UserControl + + + UserControl + + + + Code + + + Code + + + + + + + + + + + + + + @rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/Data/Document.cs b/src/Data/Document.cs new file mode 100644 index 0000000..426625c --- /dev/null +++ b/src/Data/Document.cs @@ -0,0 +1,1816 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Reflection; +using System.Runtime.Remoting.Messaging; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; +using System.Windows.Forms; +using System.Xml; + +namespace PaintDotNet +{ + [Serializable] + public sealed class Document + : IDeserializationCallback, + IDisposable, + ICloneable + { + private LayerList layers; + private int width; + private int height; + private NameValueCollection userMetaData; + + [NonSerialized] + private PaintDotNet.Threading.ThreadPool threadPool = new PaintDotNet.Threading.ThreadPool(); + + [NonSerialized] + private InvalidateEventHandler layerInvalidatedDelegate; + + // TODO: the document class should not manage its own update region, its owner should + [NonSerialized] + private Vector updateRegion; + + [NonSerialized] + private bool dirty; + + private Version savedWith; + + [NonSerialized] + private Metadata metadata = null; + + [NonSerialized] + private XmlDocument headerXml; + + private const string headerXmlSkeleton = ""; + + private XmlDocument HeaderXml + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("Document"); + } + + if (this.headerXml == null) + { + this.headerXml = new XmlDocument(); + this.headerXml.LoadXml(headerXmlSkeleton); + } + + return this.headerXml; + } + } + + public string Header + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("Document"); + } + + return this.HeaderXml.OuterXml; + } + } + + public string CustomHeaders + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("Document"); + } + + return this.HeaderXml.SelectSingleNode("/pdnImage/custom").InnerXml; + } + + set + { + if (this.disposed) + { + throw new ObjectDisposedException("Document"); + } + + this.HeaderXml.SelectSingleNode("/pdnImage/custom").InnerXml = value; + Dirty = true; + } + } + + /// + /// Gets or sets the units that are used for measuring the document's physical (printed) size. + /// + /// + /// If this property is set to MeasurementUnit.Pixel, then Dpu will be reset to 1. + /// If this property has not been set in the image's metadata, its default value + /// will be MeasurementUnit.Inch. + /// If the EXIF data for the image is invalid (such as "ResolutionUnit = 0" or something), + /// then the default DpuUnit will be returned. + /// + public MeasurementUnit DpuUnit + { + get + { + PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.ResolutionUnit); + + if (pis.Length == 0) + { + this.DpuUnit = DefaultDpuUnit; + return DefaultDpuUnit; + } + else + { + try + { + ushort unit = Exif.DecodeShortValue(pis[0]); + + // Guard against bad data in the EXIF store + switch ((MeasurementUnit)unit) + { + case MeasurementUnit.Centimeter: + case MeasurementUnit.Inch: + case MeasurementUnit.Pixel: + return (MeasurementUnit)unit; + + default: + this.Metadata.RemoveExifValues(ExifTagID.ResolutionUnit); + return this.DpuUnit; // recursive call + } + } + + catch (Exception) + { + this.Metadata.RemoveExifValues(ExifTagID.ResolutionUnit); + return this.DpuUnit; // recursive call + } + } + } + + set + { + PropertyItem pi = Exif.CreateShort(ExifTagID.ResolutionUnit, (ushort)value); + this.Metadata.ReplaceExifValues(ExifTagID.ResolutionUnit, new PropertyItem[1] { pi }); + + if (value == MeasurementUnit.Pixel) + { + this.DpuX = 1.0; + this.DpuY = 1.0; + } + + Dirty = true; + } + } + + public static MeasurementUnit DefaultDpuUnit + { + get + { + return MeasurementUnit.Inch; + } + } + +#if false + [Obsolete("Use DefaultDpuUnit property instead.")] + public static MeasurementUnit GetDefaultDpuUnit() + { + return DefaultDpuUnit; + } +#endif + + private const double defaultDpi = 96.0; + + public static double DefaultDpi + { + get + { + return defaultDpi; + } + } + + public const double CmPerInch = 2.54; + private const double defaultDpcm = defaultDpi / CmPerInch; + + public static double DefaultDpcm + { + get + { + return defaultDpcm; + } + } + + public const double MinimumDpu = 0.01; + public const double MaximumDpu = 32767.0; + + public static double InchesToCentimeters(double inches) + { + return inches * CmPerInch; + } + + public static double CentimetersToInches(double centimeters) + { + return centimeters / CmPerInch; + } + + public static double DotsPerInchToDotsPerCm(double dpi) + { + return dpi / CmPerInch; + } + + public static double DotsPerCmToDotsPerInch(double dpcm) + { + return dpcm * CmPerInch; + } + + public static double GetDefaultDpu(MeasurementUnit units) + { + double dpu; + + switch (units) + { + case MeasurementUnit.Inch: + dpu = defaultDpi; + break; + + case MeasurementUnit.Centimeter: + dpu = defaultDpcm; + break; + + case MeasurementUnit.Pixel: + dpu = 1.0; + break; + + default: + throw new InvalidEnumArgumentException("DpuUnit", (int)units, typeof(MeasurementUnit)); + } + + return dpu; + } + + /// + /// Ensures that the document's DpuX, DpuY, and DpuUnits properties are set. + /// If they are not already set, they are initialized to their default values (96, 96 , inches). + /// + private void InitializeDpu() + { + this.DpuUnit = this.DpuUnit; + this.DpuX = this.DpuX; + this.DpuY = this.DpuY; + } + + private byte[] GetDoubleAsRationalExifData(double value) + { + uint numerator; + uint denominator; + + if (Math.IEEERemainder(value, 1.0) == 0) + { + numerator = (uint)value; + denominator = 1; + } + else + { + double s = value * 1000.0; + numerator = (uint)Math.Floor(s); + denominator = 1000; + } + + return Exif.EncodeRationalValue(numerator, denominator); + } + + /// + /// Gets or sets the Document's dots-per-unit scale in the X direction. + /// + /// + /// If DpuUnit is equal to MeasurementUnit.Pixel, then this property may not be set + /// to any value other than 1.0. Setting DpuUnit to MeasurementUnit.Pixel will reset + /// this property to 1.0. This property may only be set to a value greater than 0. + /// One dot is always equal to one pixel. This property will not return a value less + /// than MinimumDpu, nor a value larger than MaximumDpu. + /// + public double DpuX + { + get + { + PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.XResolution); + + if (pis.Length == 0) + { + double defaultDpu = GetDefaultDpu(this.DpuUnit); + this.DpuX = defaultDpu; + return defaultDpu; + } + else + { + try + { + uint numerator; + uint denominator; + + Exif.DecodeRationalValue(pis[0], out numerator, out denominator); + + if (denominator == 0) + { + throw new DivideByZeroException(); // will be caught by the below catch{} + } + else + { + return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator)); + } + } + + catch + { + this.Metadata.RemoveExifValues(ExifTagID.XResolution); + return this.DpuX; // recursive call; + } + } + } + + set + { + if (value <= 0.0) + { + throw new ArgumentOutOfRangeException("value", value, "must be > 0.0"); + } + + if (this.DpuUnit == MeasurementUnit.Pixel && value != 1.0) + { + throw new ArgumentOutOfRangeException("value", value, "if DpuUnit == Pixel, then value must equal 1.0"); + } + + byte[] data = GetDoubleAsRationalExifData(value); + + PropertyItem pi = Exif.CreatePropertyItem(ExifTagID.XResolution, ExifTagType.Rational, data); + this.Metadata.ReplaceExifValues(ExifTagID.XResolution, new PropertyItem[1] { pi }); + Dirty = true; + } + } + + /// + /// Gets or sets the Document's dots-per-unit scale in the Y direction. + /// + /// + /// If DpuUnit is equal to MeasurementUnit.Pixel, then this property may not be set + /// to any value other than 1.0. Setting DpuUnit to MeasurementUnit.Pixel will reset + /// this property to 1.0. This property may only be set to a value greater than 0. + /// One dot is always equal to one pixel. This property will not return a value less + /// than MinimumDpu, nor a value larger than MaximumDpu. + /// + public double DpuY + { + get + { + PropertyItem[] pis = this.Metadata.GetExifValues(ExifTagID.YResolution); + + if (pis.Length == 0) + { + // If there's no DpuY setting, default to the DpuX setting + double dpu = this.DpuX; + this.DpuY = dpu; + return dpu; + } + else + { + try + { + uint numerator; + uint denominator; + + Exif.DecodeRationalValue(pis[0], out numerator, out denominator); + + if (denominator == 0) + { + throw new DivideByZeroException(); // will be caught by the below catch{} + } + else + { + return Math.Min(MaximumDpu, Math.Max(MinimumDpu, (double)numerator / (double)denominator)); + } + } + + catch + { + this.Metadata.RemoveExifValues(ExifTagID.YResolution); + return this.DpuY; // recursive call; + } + } + } + + set + { + if (value <= 0.0) + { + throw new ArgumentOutOfRangeException("value", value, "must be > 0.0"); + } + + if (this.DpuUnit == MeasurementUnit.Pixel && value != 1.0) + { + throw new ArgumentOutOfRangeException("value", value, "if DpuUnit == Pixel, then value must equal 1.0"); + } + + byte[] data = GetDoubleAsRationalExifData(value); + + PropertyItem pi = Exif.CreatePropertyItem(ExifTagID.YResolution, ExifTagType.Rational, data); + this.Metadata.ReplaceExifValues(ExifTagID.YResolution, new PropertyItem[1] { pi }); + Dirty = true; + } + } + + /// + /// Gets the Document's measured physical width based on the DpuUnit and DpuX properties. + /// + public double PhysicalWidth + { + get + { + return (double)this.Width / (double)this.DpuX; + } + } + + /// + /// Gets the Document's measured physical height based on the DpuUnit and DpuY properties. + /// + public double PhysicalHeight + { + get + { + return (double)this.Height / (double)this.DpuY; + } + } + + // + // Conversion Matrix: + // + // GetPhysical[X|Y](x, unit), where dpu = this.dpuX or dpuY + // + // dpu | px | in | cm | + // unit | | | | + // -------------+------+------+------------+ + // px | x | x | x | + // -------------+------+------+------------+ + // in | x / | x / | x / | + // | 96 | dpuX | (dpuX*2.54)| + // -------------+------+------+------------+ + // cm | x / |x*2.54| x / dpuX | + // | 37.8| /dpuX| | + // -------------+------+------+------------+ + + public static double PixelToPhysical(double pixel, MeasurementUnit resultUnit, MeasurementUnit dpuUnit, double dpu) + { + double result; + + if (resultUnit == MeasurementUnit.Pixel) + { + result = pixel; + } + else + { + if (resultUnit == dpuUnit) + { + result = pixel / dpu; + } + else if (dpuUnit == MeasurementUnit.Pixel) + { + double defaultDpu = GetDefaultDpu(dpuUnit); + result = pixel / defaultDpu; + } + else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch) + { + result = pixel / (CmPerInch * dpu); + } + else // if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter) + { + result = (pixel * CmPerInch) / dpu; + } + } + + return result; + } + + public double PixelToPhysicalX(double pixel, MeasurementUnit resultUnit) + { + double result; + + if (resultUnit == MeasurementUnit.Pixel) + { + result = pixel; + } + else + { + MeasurementUnit dpuUnit = this.DpuUnit; + + if (resultUnit == dpuUnit) + { + result = pixel / this.DpuX; + } + else if (dpuUnit == MeasurementUnit.Pixel) + { + double defaultDpuX = GetDefaultDpu(dpuUnit); + result = pixel / defaultDpuX; + } + else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch) + { + result = pixel / (CmPerInch * this.DpuX); + } + else //if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter) + { + result = (pixel * CmPerInch) / this.DpuX; + } + } + + return result; + } + + public double PixelToPhysicalY(double pixel, MeasurementUnit resultUnit) + { + double result; + + if (resultUnit == MeasurementUnit.Pixel) + { + result = pixel; + } + else + { + MeasurementUnit dpuUnit = this.DpuUnit; + + if (resultUnit == dpuUnit) + { + result = pixel / this.DpuY; + } + else if (dpuUnit == MeasurementUnit.Pixel) + { + double defaultDpuY = GetDefaultDpu(dpuUnit); + result = pixel / defaultDpuY; + } + else if (dpuUnit == MeasurementUnit.Centimeter && resultUnit == MeasurementUnit.Inch) + { + result = pixel / (CmPerInch * this.DpuY); + } + else //if (dpuUnit == MeasurementUnit.Inch && resultUnit == MeasurementUnit.Centimeter) + { + result = (pixel * CmPerInch) / this.DpuY; + } + } + + return result; + } + + private static bool IsValidMeasurementUnit(MeasurementUnit unit) + { + switch (unit) + { + case MeasurementUnit.Pixel: + case MeasurementUnit.Centimeter: + case MeasurementUnit.Inch: + return true; + + default: + return false; + } + } + + public static double ConvertMeasurement( + double sourceLength, + MeasurementUnit sourceUnits, + MeasurementUnit basisDpuUnits, + double basisDpu, + MeasurementUnit resultDpuUnits) + { + // Validation + if (!IsValidMeasurementUnit(sourceUnits)) + { + throw new InvalidEnumArgumentException("sourceUnits", (int)sourceUnits, typeof(MeasurementUnit)); + } + + if (!IsValidMeasurementUnit(basisDpuUnits)) + { + throw new InvalidEnumArgumentException("basisDpuUnits", (int)basisDpuUnits, typeof(MeasurementUnit)); + } + + if (!IsValidMeasurementUnit(resultDpuUnits)) + { + throw new InvalidEnumArgumentException("resultDpuUnits", (int)resultDpuUnits, typeof(MeasurementUnit)); + } + + if (basisDpuUnits == MeasurementUnit.Pixel && basisDpu != 1.0) + { + throw new ArgumentOutOfRangeException("basisDpuUnits, basisDpu", "if basisDpuUnits is Pixel, then basisDpu must equal 1.0"); + } + + // Case 1. No conversion is necessary if they want the same units out. + if (sourceUnits == resultDpuUnits) + { + return sourceLength; + } + + // Case 2. Simple inches -> centimeters + if (sourceUnits == MeasurementUnit.Inch && resultDpuUnits == MeasurementUnit.Centimeter) + { + return InchesToCentimeters(sourceLength); + } + + // Case 3. Simple centimeters -> inches. + if (sourceUnits == MeasurementUnit.Centimeter && resultDpuUnits == MeasurementUnit.Inch) + { + return CentimetersToInches(sourceLength); + } + + // At this point we know we are converting from non-pixels to pixels, or from pixels + // to non-pixels. + // Cases 4 through 8 cover conversion from non-pixels to pixels. + // Cases 9 through 11 cover conversion from pixels to non-pixels. + + // Case 4. Conversion from pixels to inches/centimeters when basis is in pixels too. + // This means we must use the default DPU for the desired result measurement. + // No need to compare lengthUnits != resultDpuUnits, since we already know this to + // be true from case 1. + if (sourceUnits == MeasurementUnit.Pixel && basisDpuUnits == MeasurementUnit.Pixel) + { + double dpu = GetDefaultDpu(resultDpuUnits); + double lengthInOrCm = sourceLength / dpu; + return lengthInOrCm; + } + + // Case 5. Conversion from inches/centimeters to pixels when basis is in pixels too. + // This means we must use the default DPU for the given input measurement. + if (sourceUnits != MeasurementUnit.Pixel && basisDpuUnits == MeasurementUnit.Pixel) + { + double dpu = GetDefaultDpu(sourceUnits); + double resultPx = sourceLength * dpu; + return resultPx; + } + + // Case 6. Conversion from inches/centimeters to pixels, when basis is in same units as input. + if (sourceUnits == basisDpuUnits && resultDpuUnits == MeasurementUnit.Pixel) + { + double resultPx = sourceLength * basisDpu; + return resultPx; + } + + // Case 7. Conversion from inches to pixels, when basis is in centimeters. + if (sourceUnits == MeasurementUnit.Inch && basisDpuUnits == MeasurementUnit.Centimeter) + { + double dpi = DotsPerCmToDotsPerInch(basisDpu); + double resultPx = sourceLength * dpi; + return resultPx; + } + + // Case 8. Conversion from centimeters to pixels, when basis is in inches. + if (sourceUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Inch) + { + double dpcm = DotsPerInchToDotsPerCm(basisDpu); + double resultPx = sourceLength * dpcm; + return resultPx; + } + + // Case 9. Converting from pixels to inches/centimeters, when the basis and result + // units are the same. + if (basisDpuUnits == resultDpuUnits) + { + double resultInOrCm = sourceLength / basisDpu; + return resultInOrCm; + } + + // Case 10. Converting from pixels to centimeters, when the basis is in inches. + if (resultDpuUnits == MeasurementUnit.Centimeter && basisDpuUnits == MeasurementUnit.Inch) + { + double dpcm = DotsPerInchToDotsPerCm(basisDpu); + double resultCm = sourceLength / dpcm; + return resultCm; + } + + // Case 11. Converting from pixels to inches, when the basis is in centimeters. + if (resultDpuUnits == MeasurementUnit.Inch && basisDpuUnits == MeasurementUnit.Centimeter) + { + double dpi = DotsPerCmToDotsPerInch(basisDpu); + double resultIn = sourceLength / dpi; + return resultIn; + } + + // Should not be possible to get here, but must appease the compiler. + throw new InvalidOperationException(); + } + + public double PixelAreaToPhysicalArea(double area, MeasurementUnit resultUnit) + { + double xScale = PixelToPhysicalX(1.0, resultUnit); + double yScale = PixelToPhysicalY(1.0, resultUnit); + + return area * xScale * yScale; + } + + private static string GetUnitsAbbreviation(MeasurementUnit units) + { + string result; + + switch (units) + { + case MeasurementUnit.Pixel: + result = string.Empty; + break; + + case MeasurementUnit.Centimeter: + result = PdnResources.GetString("MeasurementUnit.Centimeter.Abbreviation"); + break; + + case MeasurementUnit.Inch: + result = PdnResources.GetString("MeasurementUnit.Inch.Abbreviation"); + break; + + default: + throw new InvalidEnumArgumentException("MeasurementUnit was invalid"); + } + + return result; + } + + public void CoordinatesToStrings(MeasurementUnit units, int x, int y, out string xString, out string yString, out string unitsString) + { + string unitsAbbreviation = GetUnitsAbbreviation(units); + + unitsString = GetUnitsAbbreviation(units); + + if (units == MeasurementUnit.Pixel) + { + xString = x.ToString(); + yString = y.ToString(); + } + else + { + double physicalX = PixelToPhysicalX(x, units); + xString = physicalX.ToString("F2"); + + double physicalY = PixelToPhysicalY(y, units); + yString = physicalY.ToString("F2"); + } + } + + /// + /// This is provided for future use. + /// If you want to add new stuff that must be serialized, create a new class, + /// then point 'tag' to a new instance of this class that is initialized + /// during construction. Make sure the new class has a 'tag' variable as well. + /// We effectively set up a 'linked list' where new versions of the code + /// can open old versions of the document, as .NET serialization is fickle in + /// certain areas. You might also add a new property to simplify using + /// this stuff... + /// public DocumentVersion2Data DocV2Data { get { return (DocumentVersion2Data)tag; } } + /// + // In practice, this has never been used, and .NET 2.0+ has better facilities for adding + // new data to a serialization schema. Therefore, marking as obsolete. + [Obsolete] + private object tag = null; + + /// + /// Reports the version of Paint.NET that this file was saved with. + /// This is reset when SaveToStream is used. This can be used to + /// determine file format compatibility if necessary. + /// + public Version SavedWithVersion + { + get + { + if (disposed) + { + throw new ObjectDisposedException("Document"); + } + + if (savedWith == null) + { + savedWith = PdnInfo.GetVersion(); + } + + return savedWith; + } + } + + [field: NonSerialized] + public event EventHandler DirtyChanged; + + private void OnDirtyChanged() + { + if (DirtyChanged != null) + { + DirtyChanged(this, EventArgs.Empty); + } + } + + /// + /// Keeps track of whether the document has changed at all since it was last opened + /// or saved. This is something that is not reset to true by any method in the Document + /// class, but is set to false anytime anything is changed. + /// This way we can prompt the user to save a changed document when they go to quit. + /// + public bool Dirty + { + get + { + if (this.disposed) + { + throw new ObjectDisposedException("Document"); + } + + return this.dirty; + } + + set + { + if (this.disposed) + { + throw new ObjectDisposedException("Document"); + } + + if (this.dirty != value) + { + this.dirty = value; + OnDirtyChanged(); + } + } + } + + /// + /// Exposes a collection for access to the layers, and for manipulation of + /// the way the document contains the layers (add/remove/move). + /// + public LayerList Layers + { + get + { + if (disposed) + { + throw new ObjectDisposedException("Document"); + } + + return layers; + } + } + + /// + /// Width of the document, in pixels. All contained layers must be this wide as well. + /// + public int Width + { + get + { + return width; + } + } + + /// + /// Height of the document, in pixels. All contained layers must be this tall as well. + /// + public int Height + { + get + { + return height; + } + } + + /// + /// The size of the document, in pixels. This is a convenience property that wraps up + /// the Width and Height properties in one Size structure. + /// + public Size Size + { + get + { + return new Size(Width, Height); + } + } + + public Rectangle Bounds + { + get + { + return new Rectangle(0, 0, Width, Height); + } + } + + public Metadata Metadata + { + get + { + if (metadata == null) + { + metadata = new Metadata(userMetaData); + } + + return metadata; + } + } + + public void ReplaceMetaDataFrom(Document other) + { + this.Metadata.ReplaceWithDataFrom(other.Metadata); + } + + public void ClearMetaData() + { + this.Metadata.Clear(); + } + + [Obsolete("don't use this property; implementors should expose type-safe properties instead", false)] + // Note, we can not remove this property because then the compiler complains that 'tag' is unused. + public object Tag + { + get + { + return this.tag; + } + + set + { + this.tag = value; + } + } + + /// + /// Clears a portion of a surface to transparent. + /// + /// The surface to partially clear + /// The rectangle to clear + private unsafe void ClearBackground(Surface surface, Rectangle roi) + { + roi.Intersect(surface.Bounds); + + for (int y = roi.Top; y < roi.Bottom; y++) + { + ColorBgra *ptr = surface.GetPointAddressUnchecked(roi.Left, y); + Memory.SetToZero(ptr, (ulong)roi.Width * ColorBgra.SizeOf); + } + } + + /// + /// Clears a portion of a surface to transparent. + /// + /// The surface to partially clear + /// The array of Rectangles designating the areas to clear + /// The start index within the rois array to clear + /// The number of Rectangles in the rois array (staring with startIndex) to clear + private void ClearBackground(Surface surface, Rectangle[] rois, int startIndex, int length) + { + for (int i = startIndex; i < startIndex + length; i++) + { + ClearBackground(surface, rois[i]); + } + } + + public void Render(RenderArgs args) + { + Render(args, args.Surface.Bounds); + } + + public void Render(RenderArgs args, Rectangle roi) + { + Render(args, roi, false); + } + + public void Render(RenderArgs args, bool clearBackground) + { + Render(args, args.Surface.Bounds, clearBackground); + } + + /// + /// Renders a requested region of the document. Will clear the background of the input + /// before rendering if requested. + /// + /// Contains information used to control where rendering occurs. + /// The rectangular region to render. + /// If true, 'args' will be cleared to zero before rendering. + public void Render(RenderArgs args, Rectangle roi, bool clearBackground) + { + int startIndex; + + if (clearBackground) + { + BitmapLayer layer0; + layer0 = this.layers[0] as BitmapLayer; + + // Special case: if the first layer is a visible BitmapLayer with full opacity using + // the default blend op, we can just copy the pixels straight over + if (layer0 != null && + layer0.Visible && + layer0.Opacity == 255 && + layer0.BlendOp.GetType() == UserBlendOps.GetDefaultBlendOp()) + { + args.Surface.CopySurface(layer0.Surface); + startIndex = 1; + } + else + { + ClearBackground(args.Surface, roi); + startIndex = 0; + } + } + else + { + startIndex = 0; + } + + for (int i = startIndex; i < this.layers.Count; ++i) + { + Layer layer = (Layer)this.layers[i]; + + if (layer.Visible) + { + layer.Render(args, roi); + } + } + } + + public void Render(RenderArgs args, Rectangle[] roi, bool clearBackground) + { + this.Render(args, roi, 0, roi.Length, clearBackground); + } + + public void Render(RenderArgs args, Rectangle[] roi, int startIndex, int length, bool clearBackground) + { + int startLayerIndex; + + if (clearBackground) + { + BitmapLayer layer0; + layer0 = this.layers[0] as BitmapLayer; + + // Special case: if the first layer is a visible BitmapLayer with full opacity using + // the default blend op, we can just copy the pixels straight over + if (layer0 != null && + layer0.Visible && + layer0.Opacity == 255 && + layer0.BlendOp.GetType() == UserBlendOps.GetDefaultBlendOp()) + { + args.Surface.CopySurface(layer0.Surface, roi, startIndex, length); + startLayerIndex = 1; + } + else + { + ClearBackground(args.Surface, roi, startIndex, length); + startLayerIndex = 0; + } + } + else + { + startLayerIndex = 0; + } + + for (int i = startLayerIndex; i < this.layers.Count; ++i) + { + Layer layer = (Layer)this.layers[i]; + + if (layer.Visible) + { + layer.RenderUnchecked(args, roi, startIndex, length); + } + } + } + + private sealed class UpdateScansContext + { + private Document document; + private RenderArgs dst; + private Rectangle[] scans; + private int startIndex; + private int length; + + public void UpdateScans(object context) + { + document.Render(dst, scans, startIndex, length, true); + } + + public UpdateScansContext(Document document, RenderArgs dst, Rectangle[] scans, int startIndex, int length) + { + this.document = document; + this.dst = dst; + this.scans = scans; + this.startIndex = startIndex; + this.length = length; + } + } + + /// + /// Renders only the portions of the document that have changed (been Invalidated) since + /// the last call to this function. + /// + /// Contains information used to control where rendering occurs. + /// true if any rendering was done (the update list was non-empty), false otherwise + public bool Update(RenderArgs dst) + { + if (disposed) + { + throw new ObjectDisposedException("Document"); + } + + Rectangle[] updateRects; + int updateRectsLength; + updateRegion.GetArrayReadOnly(out updateRects, out updateRectsLength); + + if (updateRectsLength == 0) + { + return false; + } + + PdnRegion region = Utility.RectanglesToRegion(updateRects, 0, updateRectsLength); + Rectangle[] rectsOriginal = region.GetRegionScansReadOnlyInt(); + Rectangle[] rectsToUse; + + // Special case where we're drawing 1 big rectangle: split it up! + // This case happens quite frequently, but we don't want to spend a lot of + // time analyzing any other case that is more complicated. + if (rectsOriginal.Length == 1 && rectsOriginal[0].Height > 1) + { + Rectangle[] rectsNew = new Rectangle[Processor.LogicalCpuCount]; + Utility.SplitRectangle(rectsOriginal[0], rectsNew); + rectsToUse = rectsNew; + } + else + { + rectsToUse = rectsOriginal; + } + + int cpuCount = Processor.LogicalCpuCount; + for (int i = 0; i < cpuCount; ++i) + { + int start = (i * rectsToUse.Length) / cpuCount; + int end = ((i + 1) * rectsToUse.Length) / cpuCount; + + UpdateScansContext usc = new UpdateScansContext(this, dst, rectsToUse, start, end - start); + + if (i == cpuCount - 1) + { + // Reuse this thread for the last job -- no sense creating a new thread. + usc.UpdateScans(usc); + } + else + { + threadPool.QueueUserWorkItem(new WaitCallback(usc.UpdateScans), usc); + } + } + + this.threadPool.Drain(); + Validate(); + return true; + } + + /// + /// Constructs a blank document (zero layers) of the given width and height. + /// + /// + /// + public Document(int width, int height) + { + this.width = width; + this.height = height; + this.Dirty = true; + this.updateRegion = new Vector(); + layers = new LayerList(this); + SetupEvents(); + userMetaData = new NameValueCollection(); + Invalidate(); + } + + public Document(Size size) + : this(size.Width, size.Height) + { + } + + /// + /// Sets up event handling for contained objects. + /// + private void SetupEvents() + { + layers.Changed += new EventHandler(LayerListChangedHandler); + layers.Changing += new EventHandler(LayerListChangingHandler); + layerInvalidatedDelegate = new InvalidateEventHandler(LayerInvalidatedHandler); + + foreach (Layer layer in layers) + { + layer.Invalidated += layerInvalidatedDelegate; + } + } + + /// + /// Called after deserialization occurs so that certain things that are non-serializable + /// can be set up. + /// + /// + public void OnDeserialization(object sender) + { + this.updateRegion = new Vector(); + this.updateRegion.Add(this.Bounds); + this.threadPool = new PaintDotNet.Threading.ThreadPool(); + SetupEvents(); + Dirty = true; + } + + [field: NonSerialized] + public event InvalidateEventHandler Invalidated; + + /// + /// Raises the Invalidated event. + /// + /// + private void OnInvalidated(InvalidateEventArgs e) + { + if (Invalidated != null) + { + Invalidated(this, e); + } + } + + /// + /// Handles the Changing event that is raised from the contained LayerList. + /// + /// + /// + private void LayerListChangingHandler(object sender, EventArgs e) + { + if (disposed) + { + throw new ObjectDisposedException("Document"); + } + + foreach (Layer layer in Layers) + { + layer.Invalidated -= layerInvalidatedDelegate; + } + } + + /// + /// Handles the Changed event that is raised from the contained LayerList. + /// + /// + /// + private void LayerListChangedHandler(object sender, EventArgs e) + { + foreach (Layer layer in Layers) + { + layer.Invalidated += layerInvalidatedDelegate; + } + + Invalidate(); + } + + /// + /// Handles the Invalidated event that is raised from any contained Layer. + /// + /// + /// + private void LayerInvalidatedHandler(object sender, InvalidateEventArgs e) + { + Invalidate(e.InvalidRect); + } + + /// + /// Causes the whole document to be invalidated, forcing a full rerender on + /// the next call to Update. + /// + public void Invalidate() + { + Dirty = true; + Rectangle rect = new Rectangle(0, 0, Width, Height); + updateRegion.Clear(); + updateRegion.Add(rect); + OnInvalidated(new InvalidateEventArgs(rect)); + } + + /// + /// Invalidates a portion of the document. The given region is then tagged + /// for rerendering during the next call to Update. + /// + /// The region of interest to be invalidated. + public void Invalidate(PdnRegion roi) + { + Dirty = true; + + foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt()) + { + rect.Intersect(this.Bounds); + updateRegion.Add(rect); + + if (!rect.IsEmpty) + { + InvalidateEventArgs iea = new InvalidateEventArgs(rect); + OnInvalidated(iea); + } + } + } + + public void Invalidate(RectangleF[] roi) + { + foreach (RectangleF rectF in roi) + { + Invalidate(Rectangle.Truncate(rectF)); + } + } + + public void Invalidate(RectangleF roi) + { + Invalidate(Rectangle.Truncate(roi)); + } + + public void Invalidate(Rectangle[] roi) + { + foreach (Rectangle rect in roi) + { + Invalidate(rect); + } + } + + /// + /// Invalidates a portion of the document. The given region is then tagged + /// for rerendering during the next call to Update. + /// + /// The region of interest to be invalidated. + public void Invalidate(Rectangle roi) + { + Dirty = true; + Rectangle rect = Rectangle.Intersect(roi, this.Bounds); + updateRegion.Add(rect); + OnInvalidated(new InvalidateEventArgs(rect)); + } + + /// + /// Clears the document's update region. This is called at the end of the + /// Update method. + /// + private void Validate() + { + updateRegion.Clear(); + } + + /// + /// Creates a document that consists of one BitmapLayer. + /// + /// The Image to make a copy of that will be the first layer ("Background") in the document. + public static Document FromImage(Image image) + { + if (image == null) + { + throw new ArgumentNullException("image"); + } + + Document document = new Document(image.Width, image.Height); + BitmapLayer layer = Layer.CreateBackgroundLayer(image.Width, image.Height); + layer.Surface.Clear(ColorBgra.FromBgra(0, 0, 0, 0)); + + Bitmap asBitmap = image as Bitmap; + + // Copy pixels + if (asBitmap != null && asBitmap.PixelFormat == PixelFormat.Format32bppArgb) + { + unsafe + { + BitmapData bData = asBitmap.LockBits(new Rectangle(0, 0, asBitmap.Width, asBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + + try + { + for (int y = 0; y < bData.Height; ++y) + { + uint* srcPtr = (uint*)((byte*)bData.Scan0.ToPointer() + (y * bData.Stride)); + ColorBgra* dstPtr = layer.Surface.GetRowAddress(y); + + for (int x = 0; x < bData.Width; ++x) + { + dstPtr->Bgra = *srcPtr; + ++srcPtr; + ++dstPtr; + } + } + } + + finally + { + asBitmap.UnlockBits(bData); + bData = null; + } + } + } + else if (asBitmap != null && asBitmap.PixelFormat == PixelFormat.Format24bppRgb) + { + unsafe + { + BitmapData bData = asBitmap.LockBits(new Rectangle(0, 0, asBitmap.Width, asBitmap.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); + + try + { + for (int y = 0; y < bData.Height; ++y) + { + byte* srcPtr = (byte*)bData.Scan0.ToPointer() + (y * bData.Stride); + ColorBgra* dstPtr = layer.Surface.GetRowAddress(y); + + for (int x = 0; x < bData.Width; ++x) + { + byte b = *srcPtr; + byte g = *(srcPtr + 1); + byte r = *(srcPtr + 2); + byte a = 255; + + *dstPtr = ColorBgra.FromBgra(b, g, r, a); + + srcPtr += 3; + ++dstPtr; + } + } + } + + finally + { + asBitmap.UnlockBits(bData); + bData = null; + } + } + } + else + { + using (RenderArgs args = new RenderArgs(layer.Surface)) + { + args.Graphics.CompositingMode = CompositingMode.SourceCopy; + args.Graphics.SmoothingMode = SmoothingMode.None; + args.Graphics.DrawImage(image, args.Bounds, args.Bounds, GraphicsUnit.Pixel); + } + } + + // Transfer metadata + + // Sometimes GDI+ does not honor the resolution tags that we + // put in manually via the EXIF properties. + document.DpuUnit = MeasurementUnit.Inch; + document.DpuX = image.HorizontalResolution; + document.DpuY = image.VerticalResolution; + + PropertyItem[] pis; + + try + { + pis = image.PropertyItems; + } + + catch (Exception ex) + { + Tracing.Ping("Exception while retreiving image's PropertyItems: " + ex.ToString()); + pis = null; + // ignore the error and continue on + } + + if (pis != null) + { + for (int i = 0; i < pis.Length; ++i) + { + document.Metadata.AddExifValues(new PropertyItem[] { pis[i] }); + } + } + + // Finish up + document.Layers.Add(layer); + document.Invalidate(); + return document; + } + + public static byte[] MagicBytes + { + get + { + return Encoding.UTF8.GetBytes("PDN3"); + } + } + + /// + /// Deserializes a Document from a stream. + /// + /// The stream to deserialize from. This stream must be seekable. + /// The Document that was stored in stream. + /// + /// This is the only supported way to deserialize a Document instance from disk. + /// + public static Document FromStream(Stream stream) + { + long oldPosition = stream.Position; + bool pdn21Format = true; + + // Version 2.1+ file format: + // Starts with bytes as defined by MagicBytes + // Next three bytes are 24-bit unsigned int 'N' (first byte is low-word, second byte is middle-word, third byte is high word) + // The next N bytes are a string, this is the document header (it is XML, UTF-8 encoded) + // Important: 'N' indicates a byte count, not a character count. 'N' bytes may result in less than 'N' characters, + // depending on how the characters decode as per UTF8 + // If the next 2 bytes are 0x00, 0x01: This signifies that non-compressed .NET serialized data follows. + // If the next 2 bytes are 0x1f, 0x8b: This signifies the start of the gzip compressed .NET serialized data + // + // Version 2.0 and previous file format: + // Starts with 0x1f, 0x8b: this signifies the start of the gzip compressed .NET serialized data. + + // Read in the 'magic' bytes + for (int i = 0; i < MagicBytes.Length; ++i) + { + int theByte = stream.ReadByte(); + + if (theByte == -1) + { + throw new EndOfStreamException(); + } + + if (theByte != MagicBytes[i]) + { + pdn21Format = false; + break; + } + } + + // Read in the header if we found the 'magic' bytes identifying a PDN 2.1 file + XmlDocument headerXml = null; + if (pdn21Format) + { + // This is a Paint.NET v2.1+ file. + int low = stream.ReadByte(); + + if (low == -1) + { + throw new EndOfStreamException(); + } + + int mid = stream.ReadByte(); + + if (mid == -1) + { + throw new EndOfStreamException(); + } + + int high = stream.ReadByte(); + + if (high == -1) + { + throw new EndOfStreamException(); + } + + int byteCount = low + (mid << 8) + (high << 16); + byte[] bytes = new byte[byteCount]; + int bytesRead = Utility.ReadFromStream(stream, bytes, 0, byteCount); + + if (bytesRead != byteCount) + { + throw new EndOfStreamException("expected " + byteCount + " bytes, but only got " + bytesRead); + } + + string xml = Encoding.UTF8.GetString(bytes); + headerXml = new XmlDocument(); + headerXml.LoadXml(xml); + } + else + { + stream.Position = oldPosition; // rewind and try as v2.0-or-earlier file + } + + // Start reading the data section of the file. Determine if it's gzip or regular + long oldPosition2 = stream.Position; + int first = stream.ReadByte(); + + if (first == -1) + { + throw new EndOfStreamException(); + } + + int second = stream.ReadByte(); + + if (second == -1) + { + throw new EndOfStreamException(); + } + + Document document; + object docObject; + BinaryFormatter formatter = new BinaryFormatter(); + SerializationFallbackBinder sfb = new SerializationFallbackBinder(); + + sfb.AddAssembly(Assembly.GetExecutingAssembly()); // first try PaintDotNet.Data.dll + sfb.AddAssembly(typeof(Utility).Assembly); // second, try PaintDotNet.Core.dll + sfb.AddAssembly(typeof(SystemLayer.Memory).Assembly); // third, try PaintDotNet.SystemLayer.dll + formatter.Binder = sfb; + + if (first == 0 && second == 1) + { + DeferredFormatter deferred = new DeferredFormatter(); + formatter.Context = new StreamingContext(formatter.Context.State, deferred); + docObject = formatter.UnsafeDeserialize(stream, null); + deferred.FinishDeserialization(stream); + } + else if (first == 0x1f && second == 0x8b) + { + stream.Position = oldPosition2; // rewind to the start of 0x1f, 0x8b + GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress, true); + docObject = formatter.UnsafeDeserialize(gZipStream, null); + } + else + { + throw new FormatException("file is not a valid Paint.NET document"); + } + + document = (Document)docObject; + document.Dirty = true; + document.headerXml = headerXml; + document.Invalidate(); + return document; + } + + /// + /// Saves the Document to the given Stream with only the default headers and no + /// IO completion callback. + /// + /// The Stream to serialize the Document to. + public void SaveToStream(Stream stream) + { + SaveToStream(stream, null); + } + + /// + /// Saves the Document to the given Stream with the default and given headers, and + /// using the given IO completion callback. + /// + /// The Stream to serialize the Document to. + /// + /// This can be used to keep track of the number of uncompressed bytes that are written. The + /// values reported through the IOEventArgs.Count+Offset will vary from 1 to approximately + /// Layers.Count*Width*Height*sizeof(ColorBgra). The final number will actually be higher + /// because of hierarchical overhead, so make sure to cap any progress reports to 100%. This + /// callback will be wired to the IOFinished event of a SiphonStream. Events may be raised + /// from any thread. May be null. + /// + public void SaveToStream(Stream stream, IOEventHandler callback) + { + InitializeDpu(); + + PrepareHeader(); + string headerText = this.HeaderXml.OuterXml; + + // Write the header + byte[] magicBytes = Document.MagicBytes; + stream.Write(magicBytes, 0, magicBytes.Length); + byte[] headerBytes = Encoding.UTF8.GetBytes(headerText); + stream.WriteByte((byte)(headerBytes.Length & 0xff)); + stream.WriteByte((byte)((headerBytes.Length & 0xff00) >> 8)); + stream.WriteByte((byte)((headerBytes.Length & 0xff0000) >> 16)); + stream.Write(headerBytes, 0, headerBytes.Length); + stream.Flush(); + + // Copy version info + this.savedWith = PdnInfo.GetVersion(); + + // Write 0x00, 0x01 to indicate normal .NET serialized data + stream.WriteByte(0x00); + stream.WriteByte(0x01); + + // Write the remainder of the file (gzip compressed) + SiphonStream siphonStream = new SiphonStream(stream); + BinaryFormatter formatter = new BinaryFormatter(); + DeferredFormatter deferred = new DeferredFormatter(true, null); + SaveProgressRelay relay = new SaveProgressRelay(deferred, callback); + formatter.Context = new StreamingContext(formatter.Context.State, deferred); + formatter.Serialize(siphonStream, this); + deferred.FinishSerialization(siphonStream); + + stream.Flush(); + } + + private class SaveProgressRelay + { + private DeferredFormatter formatter; + private IOEventHandler ioCallback; + private long lastReportedBytes; + + public SaveProgressRelay(DeferredFormatter formatter, IOEventHandler ioCallback) + { + this.formatter = formatter; + this.ioCallback = ioCallback; + this.formatter.ReportedBytesChanged += new EventHandler(Formatter_ReportedBytesChanged); + } + + private void Formatter_ReportedBytesChanged(object sender, EventArgs e) + { + long reportedBytes = formatter.ReportedBytes; + bool raiseEvent; + long length = 0; + + lock (this) + { + raiseEvent = (reportedBytes > lastReportedBytes); + + if (raiseEvent) + { + length = reportedBytes - this.lastReportedBytes; + this.lastReportedBytes = reportedBytes; + } + } + + if (raiseEvent && ioCallback != null) + { + ioCallback(this, new IOEventArgs(IOOperationType.Write, reportedBytes - length, (int)length)); + } + } + } + + private void PrepareHeader() + { + XmlDocument xd = this.HeaderXml; + XmlElement pdnImage = (XmlElement)xd.SelectSingleNode("/pdnImage"); + pdnImage.SetAttribute("width", this.Width.ToString()); + pdnImage.SetAttribute("height", this.Height.ToString()); + pdnImage.SetAttribute("layers", this.Layers.Count.ToString()); + pdnImage.SetAttribute("savedWithVersion", this.SavedWithVersion.ToString(4)); + } + + public void Flatten(Surface dst) + { + if (dst.Size != this.Size) + { + throw new ArgumentOutOfRangeException("dst.Size must match this.Size"); + } + + dst.Clear(ColorBgra.White.NewAlpha(0)); + + using (RenderArgs renderArgs = new RenderArgs(dst)) + { + Render(renderArgs, true); + } + } + + /// + /// Returns a new Document that is a flattened version of this one + /// "Flattened" means it is one layer that is simply a bitmap of + /// the compositied image. + /// + /// + public Document Flatten() + { + Document newDocument = new Document(width, height); + newDocument.ReplaceMetaDataFrom(this); + BitmapLayer layer = Layer.CreateBackgroundLayer(width, height); + newDocument.Layers.Add(layer); + Flatten(layer.Surface); + return newDocument; + } + + ~Document() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool disposed = false; + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + foreach (Layer layer in layers) + { + layer.Dispose(); + } + } + + disposed = true; + } + } + + public Document Clone() + { + // I cheat. + MemoryStream stream = new MemoryStream(); + SaveToStream(stream); + stream.Seek(0, SeekOrigin.Begin); + return (Document)Document.FromStream(stream); + } + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/Data/DocumentView.cs b/src/Data/DocumentView.cs new file mode 100644 index 0000000..880f560 --- /dev/null +++ b/src/Data/DocumentView.cs @@ -0,0 +1,1804 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Encapsulates rendering the document by itself, including rulers and + /// scrollbar decorators. It also raises events for mouse movement that + /// are properly translated to (x,y) pixel coordinates within the document + /// (DocumentMouse* events). + /// + public class DocumentView + : UserControl2, + IInkHooks + { + // rulers really are on by default, so 'true' was set to show this. + private bool rulersEnabled = true; + + private bool raiseFirstInputAfterGotFocus = false; + private bool inkAvailable = true; + private int refreshSuspended = 0; + private bool hookedMouseEvents = false; + + private Document document; + private Surface compositionSurface; + private Ruler leftRuler; + private PanelEx panel; + private Ruler topRuler; + private SurfaceBox surfaceBox; + private SurfaceBoxGridRenderer gridRenderer; + private IContainer components = null; + private ControlShadow controlShadow; + + Graphics IInkHooks.CreateGraphics() + { + return this.CreateGraphics(); + } + + public SurfaceBoxRendererList RendererList + { + get + { + return this.surfaceBox.RendererList; + } + } + + public void IncrementJustPaintWhite() + { + this.surfaceBox.IncrementJustPaintWhite(); + } + + protected void RenderCompositionTo(Surface dst, bool highQuality, bool forceUpToDate) + { + if (forceUpToDate) + { + UpdateComposition(false); + } + + if (dst.Width == this.compositionSurface.Width && + dst.Height == this.compositionSurface.Height) + { + dst.ClearWithCheckboardPattern(); + new UserBlendOps.NormalBlendOp().Apply(dst, this.compositionSurface); + } + else if (highQuality) + { + Surface thumb = new Surface(dst.Size); + thumb.SuperSamplingFitSurface(this.compositionSurface); + + dst.ClearWithCheckboardPattern(); + + new UserBlendOps.NormalBlendOp().Apply(dst, thumb); + + thumb.Dispose(); + } + else + { + this.surfaceBox.RenderTo(dst); + } + } + + public event EventHandler CompositionUpdated; + private void OnCompositionUpdated() + { + if (CompositionUpdated != null) + { + CompositionUpdated(this, EventArgs.Empty); + } + } + + public MeasurementUnit Units + { + get + { + return this.leftRuler.MeasurementUnit; + } + + set + { + OnUnitsChanging(); + this.leftRuler.MeasurementUnit = value; + this.topRuler.MeasurementUnit = value; + DocumentMetaDataChangedHandler(this, EventArgs.Empty); + OnUnitsChanged(); + } + } + + protected virtual void OnUnitsChanging() + { + } + + protected virtual void OnUnitsChanged() + { + } + + private void InitRenderSurface() + { + if (this.compositionSurface == null && Document != null) + { + this.compositionSurface = new Surface(Document.Size); + } + } + + public bool DrawGrid + { + get + { + return this.gridRenderer.Visible; + } + + set + { + if (this.gridRenderer.Visible != value) + { + this.gridRenderer.Visible = value; + OnDrawGridChanged(); + } + } + } + + [Browsable(false)] + public override bool Focused + { + get + { + return base.Focused || panel.Focused || surfaceBox.Focused || controlShadow.Focused || leftRuler.Focused || topRuler.Focused; + } + } + + public new BorderStyle BorderStyle + { + get + { + return this.panel.BorderStyle; + } + + set + { + this.panel.BorderStyle = value; + } + } + + /// + /// Initializes an instance of the DocumentView class. + /// + public DocumentView() + { + InitializeComponent(); + + this.document = null; + this.compositionSurface = null; + + this.controlShadow = new ControlShadow(); + this.controlShadow.OccludingControl = surfaceBox; + this.controlShadow.Paint += new PaintEventHandler(ControlShadow_Paint); + this.panel.Controls.Add(controlShadow); + this.panel.Controls.SetChildIndex(controlShadow, panel.Controls.Count - 1); + + this.gridRenderer = new SurfaceBoxGridRenderer(this.surfaceBox.RendererList); + this.gridRenderer.Visible = false; + this.surfaceBox.RendererList.Add(this.gridRenderer, true); + + this.surfaceBox.RendererList.Invalidated += new InvalidateEventHandler(Renderers_Invalidated); + } + + private void Renderers_Invalidated(object sender, InvalidateEventArgs e) + { + if (this.document != null) + { + RectangleF rectF = this.surfaceBox.RendererList.SourceToDestination(e.InvalidRect); + Rectangle rect = Utility.RoundRectangle(rectF); + InvalidateControlShadow(rect); + } + } + + private void ControlShadow_Paint(object sender, PaintEventArgs e) + { + SurfaceBoxRenderer[][] renderers = this.surfaceBox.RendererList.Renderers; + + Rectangle csScreenRect = this.RectangleToScreen(this.controlShadow.Bounds); + Rectangle sbScreenRect = this.RectangleToScreen(this.surfaceBox.Bounds); + Point offset = new Point(sbScreenRect.X - csScreenRect.X, sbScreenRect.Y - csScreenRect.Y); + + foreach (SurfaceBoxRenderer[] renderList in renderers) + { + foreach (SurfaceBoxRenderer renderer in renderList) + { + if (renderer.Visible) + { + SurfaceBoxGraphicsRenderer sbgr = renderer as SurfaceBoxGraphicsRenderer; + + if (sbgr != null) + { + Matrix oldMatrix = e.Graphics.Transform; + sbgr.RenderToGraphics(e.Graphics, new Point(-offset.X, -offset.Y)); + e.Graphics.Transform = oldMatrix; + } + } + } + } + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + InitRenderSurface(); + inkAvailable = Ink.IsAvailable(); + + // Sometimes OnLoad() gets called *twice* for some reason. + // See bug #1415 for the symptoms. + if (!this.hookedMouseEvents) + { + this.hookedMouseEvents = true; + foreach (Control c in Controls) + { + HookMouseEvents(c); + } + } + + this.panel.Select(); + } + + public void PerformMouseWheel(Control sender, MouseEventArgs e) + { + HandleMouseWheel(sender, e); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + HandleMouseWheel(this, e); + base.OnMouseWheel(e); + } + + protected virtual void HandleMouseWheel(Control sender, MouseEventArgs e) + { + // scroll by e.Delta pixels, in screen coordinates + double docDelta = (double)e.Delta / this.ScaleFactor.Ratio; + double oldX = this.DocumentScrollPositionF.X; + double oldY = this.DocumentScrollPositionF.Y; + double newX; + double newY; + + if (Control.ModifierKeys == Keys.Shift) + { + // scroll horizontally + newX = this.DocumentScrollPositionF.X - docDelta; + newY = this.DocumentScrollPositionF.Y; + } + else if (Control.ModifierKeys == Keys.None) + { + // scroll vertically + newX = this.DocumentScrollPositionF.X; + newY = this.DocumentScrollPositionF.Y - docDelta; + } + else + { + // no change + newX = this.DocumentScrollPositionF.X; + newY = this.DocumentScrollPositionF.Y; + } + + if (newX != oldX || newY != oldY) + { + this.DocumentScrollPositionF = new PointF((float)newX, (float)newY); + UpdateRulerOffsets(); + } + } + + public override bool IsMouseCaptured() + { + return this.Capture || panel.Capture || surfaceBox.Capture || controlShadow.Capture || leftRuler.Capture || topRuler.Capture; + } + + /// + /// Get or set upper left of scroll location in document coordinates. + /// + [Browsable(false)] + public PointF DocumentScrollPositionF + { + get + { + if (this.panel == null || this.surfaceBox == null) + { + return PointF.Empty; + } + else + { + return VisibleDocumentRectangleF.Location; + } + } + + set + { + if (panel == null) + { + return; + } + + PointF sbClientF = this.surfaceBox.SurfaceToClient(value); + Point sbClient = Point.Round(sbClientF); + + if (this.panel.AutoScrollPosition != new Point(-sbClient.X, -sbClient.Y)) + { + this.panel.AutoScrollPosition = sbClient; + UpdateRulerOffsets(); + this.topRuler.Invalidate(); + this.leftRuler.Invalidate(); + } + } + } + + [Browsable(false)] + public PointF DocumentCenterPointF + { + get + { + RectangleF vsb = VisibleDocumentRectangleF; + PointF centerPt = new PointF((vsb.Left + vsb.Right) / 2, (vsb.Top + vsb.Bottom) / 2); + return centerPt; + } + + set + { + RectangleF vsb = VisibleDocumentRectangleF; + PointF newCornerPt = new PointF(value.X - (vsb.Width / 2), value.Y - (vsb.Height / 2)); + this.DocumentScrollPositionF = newCornerPt; + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.components != null) + { + this.components.Dispose(); + this.components = null; + } + + if (this.compositionSurface != null) + { + this.compositionSurface.Dispose(); + this.compositionSurface = null; + } + } + + base.Dispose(disposing); + } + + public event EventHandler ScaleFactorChanged; + protected virtual void OnScaleFactorChanged() + { + if (ScaleFactorChanged != null) + { + ScaleFactorChanged(this, EventArgs.Empty); + } + } + + public event EventHandler DrawGridChanged; + protected virtual void OnDrawGridChanged() + { + if (DrawGridChanged != null) + { + DrawGridChanged(this, EventArgs.Empty); + } + } + + public void ZoomToWindow() + { + if (this.document != null) + { + Rectangle max = ClientRectangleMax; + + ScaleFactor zoom = ScaleFactor.Min(max.Width - 10, + document.Width, + max.Height - 10, + document.Height, + ScaleFactor.MinValue); + + ScaleFactor min = ScaleFactor.Min(zoom, ScaleFactor.OneToOne); + this.ScaleFactor = min; + } + } + + private double GetZoomInFactorEpsilon() + { + // Increase ratio by 1 percentage point + double currentRatio = this.ScaleFactor.Ratio; + double factor1 = (currentRatio + 0.01) / currentRatio; + + // Increase ratio so that we increase our view by 1 pixel + double ratioW = (double)(surfaceBox.Width + 1) / (double)surfaceBox.Surface.Width; + double ratioH = (double)(surfaceBox.Height + 1) / (double)surfaceBox.Surface.Height; + double ratio = Math.Max(ratioW, ratioH); + double factor2 = ratio / currentRatio; + + double factor = Math.Max(factor1, factor2); + + return factor; + } + + private double GetZoomOutFactorEpsilon() + { + double ratio = this.ScaleFactor.Ratio; + return (ratio - 0.01) / ratio; + } + + public virtual void ZoomIn(double factor) + { + Do.TryBool(() => ZoomInImpl(factor)); + } + + private void ZoomInImpl(double factor) + { + PointF centerPt = this.DocumentCenterPointF; + + ScaleFactor oldSF = this.ScaleFactor; + ScaleFactor newSF = this.ScaleFactor; + int countdown = 3; + + // At a minimum we want to increase the size of visible document by 1 pixel + // Figure out what the ratio of ourSize : ourSize+1 is, and start out with that + double zoomInEps = GetZoomInFactorEpsilon(); + double desiredFactor = Math.Max(factor, zoomInEps); + double newFactor = desiredFactor; + + // Keep setting the ScaleFactor until it actually 'sticks' + // Important for certain image sizes where not all zoom levels create distinct + // screen sizes + do + { + newSF = ScaleFactor.FromDouble(newSF.Ratio * newFactor); + this.ScaleFactor = newSF; + --countdown; + newFactor *= 1.10; + } while (this.ScaleFactor == oldSF && countdown > 0); + + this.DocumentCenterPointF = centerPt; + } + + public virtual void ZoomIn() + { + Do.TryBool(ZoomInImpl); + } + + private void ZoomInImpl() + { + PointF centerPt = this.DocumentCenterPointF; + + ScaleFactor oldSF = this.ScaleFactor; + ScaleFactor newSF = this.ScaleFactor; + int countdown = ScaleFactor.PresetValues.Length; + + // Keep setting the ScaleFactor until it actually 'sticks' + // Important for certain image sizes where not all zoom levels create distinct + // screen sizes + do + { + newSF = newSF.GetNextLarger(); + this.ScaleFactor = newSF; + --countdown; + } while (this.ScaleFactor == oldSF && countdown > 0); + + this.DocumentCenterPointF = centerPt; + } + + public virtual void ZoomOut(double factor) + { + Do.TryBool(() => ZoomOutImpl(factor)); + } + + private void ZoomOutImpl(double factor) + { + PointF centerPt = this.DocumentCenterPointF; + + ScaleFactor oldSF = this.ScaleFactor; + ScaleFactor newSF = this.ScaleFactor; + int countdown = 3; + + // At a minimum we want to decrease the size of visible document by 1 pixel (without dividing by zero of course) + // Figure out what the ratio of ourSize : ourSize-1 is, and start out with that + double zoomOutEps = GetZoomOutFactorEpsilon(); + double factorRecip = 1.0 / factor; + double desiredFactor = Math.Min(factorRecip, zoomOutEps); + double newFactor = desiredFactor; + + // Keep setting the ScaleFactor until it actually 'sticks' + // Important for certain image sizes where not all zoom levels create distinct + // screen sizes + do + { + newSF = ScaleFactor.FromDouble(newSF.Ratio * newFactor); + this.ScaleFactor = newSF; + --countdown; + newFactor *= 0.9; + } while (this.ScaleFactor == oldSF && countdown > 0); + + this.DocumentCenterPointF = centerPt; + } + + public virtual void ZoomOut() + { + Do.TryBool(ZoomOutImpl); + } + + private void ZoomOutImpl() + { + PointF centerPt = this.DocumentCenterPointF; + + ScaleFactor oldSF = this.ScaleFactor; + ScaleFactor newSF = this.ScaleFactor; + int countdown = ScaleFactor.PresetValues.Length; + + // Keep setting the ScaleFactor until it actually 'sticks' + // Important for certain image sizes where not all zoom levels create distinct + // screen sizes + do + { + newSF = newSF.GetNextSmaller(); + this.ScaleFactor = newSF; + --countdown; + } while (this.ScaleFactor == oldSF && countdown > 0); + + this.DocumentCenterPointF = centerPt; + } + + private ScaleFactor scaleFactor = new ScaleFactor(1, 1); + + /// + /// Gets the maximum scale factor that the current document may be displayed at. + /// + public ScaleFactor MaxScaleFactor + { + get + { + ScaleFactor maxSF; + + if (this.document.Width == 0 || this.document.Height == 0) + { + maxSF = ScaleFactor.MaxValue; + } + else + { + double maxHScale = (double)SurfaceBox.MaxSideLength / this.document.Width; + double maxVScale = (double)SurfaceBox.MaxSideLength / this.document.Height; + double maxScale = Math.Min(maxHScale, maxVScale); + maxSF = ScaleFactor.FromDouble(maxScale); + } + + return maxSF; + } + } + + [Browsable(false)] + public ScaleFactor ScaleFactor + { + get + { + return this.scaleFactor; + } + + set + { + UI.SuspendControlPainting(this); + + ScaleFactor newValue = ScaleFactor.Min(value, MaxScaleFactor); + + if (newValue == this.scaleFactor && + this.scaleFactor == ScaleFactor.OneToOne) + { + // this space intentionally left blank + } + else + { + RectangleF visibleRect = this.VisibleDocumentRectangleF; + ScaleFactor oldSF = scaleFactor; + scaleFactor = newValue; + + // This value is used later below to re-center the document on screen + PointF centerPt = new PointF(visibleRect.X + visibleRect.Width / 2, + visibleRect.Y + visibleRect.Height / 2); + + if (surfaceBox != null && compositionSurface != null) + { + surfaceBox.Size = Size.Truncate((SizeF)scaleFactor.ScaleSize(compositionSurface.Bounds.Size)); + scaleFactor = surfaceBox.ScaleFactor; + + if (leftRuler != null) + { + this.leftRuler.ScaleFactor = scaleFactor; + } + + if (topRuler != null) + { + this.topRuler.ScaleFactor = scaleFactor; + } + } + + // re center ourself + RectangleF visibleRect2 = this.VisibleDocumentRectangleF; + RecenterView(centerPt); + } + + this.OnResize(EventArgs.Empty); + this.OnScaleFactorChanged(); + + UI.ResumeControlPainting(this); + Invalidate(true); + } + } + + /// + /// Returns a rectangle for the bounding rectangle of what is currently visible on screen, + /// in document coordinates. + /// + [Browsable(false)] + public RectangleF VisibleDocumentRectangleF + { + get + { + Rectangle panelRect = panel.RectangleToScreen(panel.ClientRectangle); // screen coords + Rectangle surfaceBoxRect = surfaceBox.RectangleToScreen(surfaceBox.ClientRectangle); // screen coords + Rectangle docScreenRect = Rectangle.Intersect(panelRect, surfaceBoxRect); // screen coords + Rectangle docClientRect = RectangleToClient(docScreenRect); + RectangleF docDocRectF = ClientToDocument(docClientRect); + return docDocRectF; + } + } + + /// + /// Returns a rectangle in screen coordinates that represents the space taken up + /// by the document that is visible on screen. + /// + [Browsable(false)] + public Rectangle VisibleDocumentBounds + { + get + { + // convert coordinates: document -> client -> screen + return RectangleToScreen(Utility.RoundRectangle(DocumentToClient(VisibleDocumentRectangleF))); + } + } + + /// + /// Returns a rectangle in client coordinates that denotes the space that the document + /// may take up. This is essentially the ClientRectangle converted to screen coordinates + /// and then with the rulers and scrollbars subtracted out. + /// + public Rectangle VisibleViewRectangle + { + get + { + Rectangle clientRect = this.panel.ClientRectangle; + Rectangle screenRect = this.panel.RectangleToScreen(clientRect); + Rectangle ourClientRect = RectangleToClient(screenRect); + return ourClientRect; + } + } + + public bool ScrollBarsVisible + { + get + { + return this.HScroll || this.VScroll; + } + } + + public Rectangle ClientRectangleMax + { + get + { + return RectangleToClient(this.panel.RectangleToScreen(this.panel.Bounds)); + } + } + + public Rectangle ClientRectangleMin + { + get + { + Rectangle bounds = ClientRectangleMax; + bounds.Width -= SystemInformation.VerticalScrollBarWidth; + bounds.Height -= SystemInformation.HorizontalScrollBarHeight; + return bounds; + } + } + + public void SetHighlightRectangle(RectangleF rectF) + { + if (rectF.Width == 0 || rectF.Height == 0) + { + this.leftRuler.HighlightEnabled = false; + this.topRuler.HighlightEnabled = false; + } + else + { + if (this.topRuler != null) + { + this.topRuler.HighlightEnabled = true; + this.topRuler.HighlightStart = rectF.Left; + this.topRuler.HighlightLength = rectF.Width; + } + + if (this.leftRuler != null) + { + this.leftRuler.HighlightEnabled = true; + this.leftRuler.HighlightStart = rectF.Top; + this.leftRuler.HighlightLength = rectF.Height; + } + } + } + + public event EventHandler> DocumentChanging; + protected virtual void OnDocumentChanging(Document newDocument) + { + if (DocumentChanging != null) + { + DocumentChanging(this, new EventArgs(newDocument)); + } + } + + public event EventHandler DocumentChanged; + protected virtual void OnDocumentChanged() + { + if (DocumentChanged != null) + { + DocumentChanged(this, EventArgs.Empty); + } + } + + /// + /// Gets or sets the Document that is shown through this instance of DocumentView. + /// + /// + /// This property is thread safe and may be called from a non-UI thread. However, + /// if the setter is called from a non-UI thread, then that thread will block as + /// the call is marshaled to the UI thread. + /// + [Browsable(false)] + public Document Document + { + get + { + return document; + } + + set + { + if (InvokeRequired) + { + this.Invoke(new Procedure(DocumentSetImpl), new object[1] { value }); + } + else + { + DocumentSetImpl(value); + } + } + } + + private void DocumentSetImpl(Document value) + { + PointF dspf = DocumentScrollPositionF; + + OnDocumentChanging(value); + SuspendRefresh(); + + try + { + if (this.document != null) + { + this.document.Invalidated -= Document_Invalidated; + this.document.Metadata.Changed -= DocumentMetaDataChangedHandler; + } + + this.document = value; + + if (document != null) + { + if (this.compositionSurface != null && + this.compositionSurface.Size != document.Size) + { + this.compositionSurface.Dispose(); + this.compositionSurface = null; + } + + if (this.compositionSurface == null) + { + this.compositionSurface = new Surface(Document.Size); + } + + this.compositionSurface.Clear(ColorBgra.White); + + if (this.surfaceBox.Surface != this.compositionSurface) + { + this.surfaceBox.Surface = this.compositionSurface; + } + + if (this.ScaleFactor != this.surfaceBox.ScaleFactor) + { + this.ScaleFactor = this.surfaceBox.ScaleFactor; + } + + this.document.Invalidated += Document_Invalidated; + this.document.Metadata.Changed += DocumentMetaDataChangedHandler; + } + + Invalidate(true); + DocumentMetaDataChangedHandler(this, EventArgs.Empty); + this.OnResize(EventArgs.Empty); + OnDocumentChanged(); + } + + finally + { + ResumeRefresh(); + } + + DocumentScrollPositionF = dspf; + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.topRuler = new PaintDotNet.Ruler(); + this.leftRuler = new PaintDotNet.Ruler(); + this.panel = new PaintDotNet.PanelEx(); + this.surfaceBox = new PaintDotNet.SurfaceBox(); + this.panel.SuspendLayout(); + this.SuspendLayout(); + // + // topRuler + // + this.topRuler.BackColor = System.Drawing.Color.White; + this.topRuler.Dock = System.Windows.Forms.DockStyle.Top; + this.topRuler.Location = new System.Drawing.Point(0, 0); + this.topRuler.Name = "topRuler"; + this.topRuler.Offset = -16; + this.topRuler.Size = UI.ScaleSize(new Size(384, 16)); + this.topRuler.TabIndex = 3; + // + // leftRuler + // + this.leftRuler.BackColor = System.Drawing.Color.White; + this.leftRuler.Dock = System.Windows.Forms.DockStyle.Left; + this.leftRuler.Location = new System.Drawing.Point(0, 16); + this.leftRuler.Name = "leftRuler"; + this.leftRuler.Orientation = System.Windows.Forms.Orientation.Vertical; + this.leftRuler.Size = UI.ScaleSize(new Size(16, 304)); + this.leftRuler.TabIndex = 4; + // + // panel + // + this.panel.AutoScroll = true; + this.panel.Controls.Add(this.surfaceBox); + this.panel.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel.Location = new System.Drawing.Point(16, 16); + this.panel.Name = "panel"; + this.panel.ScrollPosition = new System.Drawing.Point(0, 0); + this.panel.Size = new System.Drawing.Size(368, 304); + this.panel.TabIndex = 5; + this.panel.Scroll += new System.Windows.Forms.ScrollEventHandler(this.Panel_Scroll); + this.panel.KeyDown += new KeyEventHandler(Panel_KeyDown); + this.panel.KeyUp += new KeyEventHandler(Panel_KeyUp); + this.panel.KeyPress += new KeyPressEventHandler(Panel_KeyPress); + this.panel.GotFocus += new EventHandler(Panel_GotFocus); + this.panel.LostFocus += new EventHandler(Panel_LostFocus); + // + // surfaceBox + // + this.surfaceBox.Location = new System.Drawing.Point(0, 0); + this.surfaceBox.Name = "surfaceBox"; + this.surfaceBox.Surface = null; + this.surfaceBox.TabIndex = 0; + this.surfaceBox.PrePaint += new PaintDotNet.PaintEventHandler2(this.SurfaceBox_PrePaint); + // + // DocumentView + // + this.Controls.Add(this.panel); + this.Controls.Add(this.leftRuler); + this.Controls.Add(this.topRuler); + this.Name = "DocumentView"; + this.Size = new System.Drawing.Size(384, 320); + this.panel.ResumeLayout(false); + this.ResumeLayout(false); + + } + + private void Panel_LostFocus(object sender, EventArgs e) + { + this.raiseFirstInputAfterGotFocus = false; + } + + private void Panel_GotFocus(object sender, EventArgs e) + { + this.raiseFirstInputAfterGotFocus = true; + } + + /// + /// Used to enable or disable the rulers. + /// + public bool RulersEnabled + { + get + { + return rulersEnabled; + } + + set + { + if (rulersEnabled != value) + { + rulersEnabled = value; + + if (topRuler != null) + { + topRuler.Enabled = value; + topRuler.Visible = value; + } + + if (leftRuler != null) + { + leftRuler.Enabled = value; + leftRuler.Visible = value; + } + + this.OnResize(EventArgs.Empty); + OnRulersEnabledChanged(); + } + } + } + + public event EventHandler RulersEnabledChanged; + protected void OnRulersEnabledChanged() + { + if (RulersEnabledChanged != null) + { + RulersEnabledChanged(this, EventArgs.Empty); + } + } + + public bool PanelAutoScroll + { + get + { + return panel.AutoScroll; + } + + set + { + if (panel.AutoScroll != value) + { + panel.AutoScroll = value; + } + } + } + + /// + /// Converts a point from the Windows Forms "client" coordinate space (wrt the DocumentView) + /// into the Document coordinate space. + /// + /// A Point that is in our client coordinates. + /// A Point that is in Document coordinates. + public PointF ClientToDocument(Point clientPt) + { + Point screen = PointToScreen(clientPt); + Point sbClient = surfaceBox.PointToClient(screen); + return surfaceBox.ClientToSurface(sbClient); + } + + /// + /// Converts a point from screen coordinates to document coordinates + /// + /// The point in screen coordinates to convert to document coordinates + public PointF ScreenToDocument(PointF screen) + { + Point offset = surfaceBox.PointToClient(new Point(0, 0)); + return surfaceBox.ClientToSurface(new PointF(screen.X + (float)offset.X, screen.Y + (float)offset.Y)); + } + + /// + /// Converts a point from screen coordinates to document coordinates + /// + /// The point in screen coordinates to convert to document coordinates + public Point ScreenToDocument(Point screen) + { + Point offset = surfaceBox.PointToClient(new Point(0, 0)); + return surfaceBox.ClientToSurface(new Point(screen.X + offset.X, screen.Y + offset.Y)); + } + + /// + /// Converts a PointF from the RealTimeStylus coordinate space + /// into the Document coordinate space. + /// + /// A Point that is in RealTimeStylus coordinate space. + /// A Point that is in Document coordinates. + public PointF ClientToSurface(PointF clientPt) + { + return surfaceBox.ClientToSurface(clientPt); + } + + /// + /// Converts a point from Document coordinate space into the Windows Forms "client" + /// coordinate space. + /// + /// A Point that is in Document coordinates. + /// A Point that is in client coordinates. + public PointF DocumentToClient(PointF documentPt) + { + PointF sbClient = surfaceBox.SurfaceToClient(documentPt); + Point screen = surfaceBox.PointToScreen(Point.Round(sbClient)); + return PointToClient(screen); + } + + /// + /// Converts a rectangle from the Windows Forms "client" coordinate space into the Document + /// coordinate space. + /// + /// A Rectangle that is in client coordinates. + /// A Rectangle that is in Document coordinates. + public RectangleF ClientToDocument(Rectangle clientRect) + { + Rectangle screen = RectangleToScreen(clientRect); + Rectangle sbClient = surfaceBox.RectangleToClient(screen); + return surfaceBox.ClientToSurface((RectangleF)sbClient); + } + + /// + /// Converts a rectangle from Document coordinate space into the Windows Forms "client" + /// coordinate space. + /// + /// A Rectangle that is in Document coordinates. + /// A Rectangle that is in client coordinates. + public RectangleF DocumentToClient(RectangleF documentRect) + { + RectangleF sbClient = surfaceBox.SurfaceToClient(documentRect); + Rectangle screen = surfaceBox.RectangleToScreen(Utility.RoundRectangle(sbClient)); + return RectangleToClient(screen); + } + + private void HookMouseEvents(Control c) + { + if (this.inkAvailable) + { + // This must be in a separate function, otherwise we will throw an exception when JITting + // because MS.Ink.dll won't be available + // This is to support systems that don't have ink installed + + try + { + Ink.HookInk(this, c); + } + + catch (InvalidOperationException ioex) + { + Tracing.Ping("Exception while initializing ink hooks: " + ioex.ToString()); + this.inkAvailable = false; + } + } + + c.MouseEnter += new EventHandler(this.MouseEnterHandler); + c.MouseLeave += new EventHandler(this.MouseLeaveHandler); + c.MouseUp += new MouseEventHandler(this.MouseUpHandler); + c.MouseMove += new MouseEventHandler(this.MouseMoveHandler); + c.MouseDown += new MouseEventHandler(this.MouseDownHandler); + c.Click += new EventHandler(this.ClickHandler); + + foreach (Control c2 in c.Controls) + { + HookMouseEvents(c2); + } + } + + // these events will report mouse coordinates in document space + // i.e. if the image is zoomed at 200% then the mouse coordinates will be divided in half + + /// + /// Occurs when the mouse enters an element of the UI that is considered to be part of + /// the document space. + /// + public event EventHandler DocumentMouseEnter; + protected virtual void OnDocumentMouseEnter(EventArgs e) + { + if (DocumentMouseEnter != null) + { + DocumentMouseEnter(this, e); + } + } + + /// + /// Occurs when the mouse leaves an element of the UI that is considered to be part of + /// the document space. + /// + /// + /// This event being raised does not necessarily correpond to the mouse leaving + /// document space, only that it has left the screen space of an element of the UI + /// that is part of document space. For example, if the mouse leaves the canvas and + /// then enters the rulers, you will see a DocumentMouseLeave event raised which is + /// then immediately followed by a DocumentMouseEnter event. + /// + public event EventHandler DocumentMouseLeave; + protected virtual void OnDocumentMouseLeave(EventArgs e) + { + if (DocumentMouseLeave != null) + { + DocumentMouseLeave(this, e); + } + } + + /// + /// Occurs when the mouse or stylus point is moved over the document. + /// + /// + /// Note: This event will always be raised twice in succession. One will provide a + /// MouseEventArgs, and the other will provide a StylusEventArgs. It is up to consumers + /// of this event to decide which one is pertinent and to then filter out the other + /// type of event. + /// + public event MouseEventHandler DocumentMouseMove; + protected virtual void OnDocumentMouseMove(MouseEventArgs e) + { + if (!inkAvailable) + { + if (DocumentMouseMove != null) + { + DocumentMouseMove(this, new StylusEventArgs(e)); + } + } + + if (DocumentMouseMove != null) + { + DocumentMouseMove(this, e); + } + } + + public void PerformDocumentMouseMove(MouseEventArgs e) + { + OnDocumentMouseMove(e); + } + + void IInkHooks.PerformDocumentMouseMove(MouseButtons button, int clicks, float x, float y, int delta, float pressure) + { + PerformDocumentMouseMove(new StylusEventArgs(button, clicks, x, y, delta, pressure)); + } + + /// + /// Occurs when the mouse or stylus point is over the document and a mouse button is released + /// or the stylus is lifted. + /// + /// + /// Note: This event will always be raised twice in succession. One will provide a + /// MouseEventArgs, and the other will provide a StylusEventArgs. It is up to consumers + /// of this event to decide which one is pertinent and to then filter out the other + /// type of event. + /// + public event MouseEventHandler DocumentMouseUp; + + protected virtual void OnDocumentMouseUp(MouseEventArgs e) + { + CheckForFirstInputAfterGotFocus(); + + if (!inkAvailable) + { + if (DocumentMouseUp != null) + { + DocumentMouseUp(this, new StylusEventArgs(e)); + } + } + + if (DocumentMouseUp != null) + { + DocumentMouseUp(this, e); + } + } + + public void PerformDocumentMouseUp(MouseEventArgs e) + { + OnDocumentMouseUp(e); + } + + void IInkHooks.PerformDocumentMouseUp(MouseButtons button, int clicks, float x, float y, int delta, float pressure) + { + PerformDocumentMouseUp(new StylusEventArgs(button, clicks, x, y, delta, pressure)); + } + + /// + /// Occurs when the mouse or stylus point is over the document and a mouse button or + /// stylus is pressed. + /// + /// + /// Note: This event will always be raised twice in succession. One will provide a + /// MouseEventArgs, and the other will provide a StylusEventArgs. It is up to consumers + /// of this event to decide which one is pertinent and to then filter out the other + /// type of event. + /// + public event MouseEventHandler DocumentMouseDown; + + protected virtual void OnDocumentMouseDown(MouseEventArgs e) + { + CheckForFirstInputAfterGotFocus(); + + if (!inkAvailable) + { + if (DocumentMouseDown != null) + { + DocumentMouseDown(this, new StylusEventArgs(e)); + } + } + + if (DocumentMouseDown != null) + { + DocumentMouseDown(this, e); + } + } + + public void PerformDocumentMouseDown(MouseEventArgs e) + { + OnDocumentMouseDown(e); + } + + void IInkHooks.PerformDocumentMouseDown(MouseButtons button, int clicks, float x, float y, int delta, float pressure) + { + PerformDocumentMouseDown(new StylusEventArgs(button, clicks, x, y, delta, pressure)); + } + + public event EventHandler DocumentClick; + protected void OnDocumentClick() + { + CheckForFirstInputAfterGotFocus(); + + if (DocumentClick != null) + { + DocumentClick(this, EventArgs.Empty); + } + } + + public event KeyPressEventHandler DocumentKeyPress; + protected void OnDocumentKeyPress(KeyPressEventArgs e) + { + CheckForFirstInputAfterGotFocus(); + + if (DocumentKeyPress != null) + { + DocumentKeyPress(this, e); + } + } + + private void Panel_KeyPress(object sender, KeyPressEventArgs e) + { + OnDocumentKeyPress(e); + } + + public event KeyEventHandler DocumentKeyDown; + protected void OnDocumentKeyDown(KeyEventArgs e) + { + CheckForFirstInputAfterGotFocus(); + + if (DocumentKeyDown != null) + { + DocumentKeyDown(this, e); + } + } + + private void Panel_KeyDown(object sender, KeyEventArgs e) + { + CheckForFirstInputAfterGotFocus(); + + OnDocumentKeyDown(e); + + if (!e.Handled) + { + PointF oldPt = this.DocumentScrollPositionF; + PointF newPt = oldPt; + RectangleF vdr = VisibleDocumentRectangleF; + + switch (e.KeyData) + { + case Keys.Next: + newPt.Y += vdr.Height; + break; + + case (Keys.Next | Keys.Shift): + newPt.X += vdr.Width; + break; + + case Keys.Prior: + newPt.Y -= vdr.Height; + break; + + case (Keys.Prior | Keys.Shift): + newPt.X -= vdr.Width; + break; + + case Keys.Home: + if (oldPt.X == 0) + { + newPt.Y = 0; + } + else + { + newPt.X = 0; + } + break; + + case Keys.End: + if (vdr.Right < this.document.Width - 1) + { + newPt.X = this.document.Width; + } + else + { + newPt.Y = this.document.Height; + } + break; + + default: + break; + } + + if (newPt != oldPt) + { + DocumentScrollPositionF = newPt; + e.Handled = true; + } + } + } + + public event KeyEventHandler DocumentKeyUp; + protected void OnDocumentKeyUp(KeyEventArgs e) + { + CheckForFirstInputAfterGotFocus(); + + if (DocumentKeyUp != null) + { + DocumentKeyUp(this, e); + } + } + + private void Panel_KeyUp(object sender, KeyEventArgs e) + { + OnDocumentKeyUp(e); + } + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + Keys keyCode = keyData & Keys.KeyCode; + + if (Utility.IsArrowKey(keyData) || + keyCode == Keys.Delete || + keyCode == Keys.Tab) + { + KeyEventArgs kea = new KeyEventArgs(keyData); + + // We only intercept WM_KEYDOWN because WM_KEYUP is not sent! + switch (msg.Msg) + { + case 0x100: //NativeMethods.WmConstants.WM_KEYDOWN: + if (this.ContainsFocus) + { + OnDocumentKeyDown(kea); + //OnDocumentKeyUp(kea); + + if (Utility.IsArrowKey(keyData)) + { + kea.Handled = true; + } + } + + if (kea.Handled) + { + return true; + } + + break; + + /* + case 0x101: //NativeMethods.WmConstants.WM_KEYUP: + if (this.ContainsFocus) + { + OnDocumentKeyUp(kea); + } + + return kea.Handled; + */ + } + } + + return base.ProcessCmdKey(ref msg, keyData); + } + + private void UpdateRulerOffsets() + { + // TODO: cleanse magic numbers + this.topRuler.Offset = ScaleFactor.UnscaleScalar(UI.ScaleWidth(-16.0f) - surfaceBox.Location.X); + this.topRuler.Update(); + this.leftRuler.Offset = ScaleFactor.UnscaleScalar(0.0f - surfaceBox.Location.Y); + this.leftRuler.Update(); + } + + public void InvalidateSurface(Rectangle rect) + { + this.surfaceBox.Invalidate(rect); + InvalidateControlShadow(rect); + } + + public void InvalidateSurface() + { + surfaceBox.Invalidate(); + controlShadow.Invalidate(); + } + + private void InvalidateControlShadowNoClipping(Rectangle rect) + { + if (rect.Width > 0 && rect.Height > 0) + { + Rectangle csRect = SurfaceBoxToControlShadow(rect); + this.controlShadow.Invalidate(csRect); + } + } + + private void InvalidateControlShadow(Rectangle surfaceBoxRect) + { + if (this.document == null) + { + return; + } + + Rectangle maxRect = SurfaceBoxRenderer.MaxBounds; + Size surfaceBoxSize = this.surfaceBox.Size; + + Rectangle leftRect = Rectangle.FromLTRB(maxRect.Left, 0, 0, surfaceBoxSize.Height); + Rectangle topRect = Rectangle.FromLTRB(maxRect.Left, maxRect.Top, maxRect.Right, 0); + Rectangle rightRect = Rectangle.FromLTRB(surfaceBoxSize.Width, 0, maxRect.Right, surfaceBoxSize.Height); + Rectangle bottomRect = Rectangle.FromLTRB(maxRect.Left, surfaceBoxSize.Height, maxRect.Right, maxRect.Bottom); + + leftRect.Intersect(surfaceBoxRect); + topRect.Intersect(surfaceBoxRect); + rightRect.Intersect(surfaceBoxRect); + bottomRect.Intersect(surfaceBoxRect); + + InvalidateControlShadowNoClipping(leftRect); + InvalidateControlShadowNoClipping(topRect); + InvalidateControlShadowNoClipping(rightRect); + InvalidateControlShadowNoClipping(bottomRect); + } + + private Rectangle SurfaceBoxToControlShadow(Rectangle rect) + { + Rectangle screenRect = this.surfaceBox.RectangleToScreen(rect); + Rectangle csRect = this.controlShadow.RectangleToClient(screenRect); + return csRect; + } + + protected override void OnLayout(LayoutEventArgs e) + { + DoLayout(); + base.OnLayout(e); + } + + private void DoLayout() + { + // Ensure that the document is centered. + if (panel.ClientRectangle != new Rectangle(0, 0, 0, 0)) + { + // If the client area is bigger than the area used to display the image, center it + int newX = panel.AutoScrollPosition.X; + int newY = panel.AutoScrollPosition.Y; + + if (panel.ClientRectangle.Width > surfaceBox.Width) + { + newX = panel.AutoScrollPosition.X + ((panel.ClientRectangle.Width - surfaceBox.Width) / 2); + } + + if (panel.ClientRectangle.Height > surfaceBox.Height) + { + newY = panel.AutoScrollPosition.Y + ((panel.ClientRectangle.Height - surfaceBox.Height) / 2); + } + + Point newPoint = new Point(newX, newY); + + if (surfaceBox.Location != newPoint) + { + surfaceBox.Location = newPoint; + } + } + + this.UpdateRulerOffsets(); + } + + private FormWindowState oldWindowState = FormWindowState.Minimized; + protected override void OnResize(EventArgs e) + { + // enable or disable timer: no sense drawing selection if we're minimized + Form parentForm = ParentForm; + + if (parentForm != null) + { + if (parentForm.WindowState != this.oldWindowState) + { + PerformLayout(); + } + + this.oldWindowState = parentForm.WindowState; + } + + base.OnResize(e); + DoLayout(); + } + + public PointF MouseToDocumentF(Control sender, Point mouse) + { + Point screenPoint = sender.PointToScreen(mouse); + Point sbClient = surfaceBox.PointToClient(screenPoint); + + PointF docPoint = surfaceBox.ClientToSurface(new PointF(sbClient.X, sbClient.Y)); + + return docPoint; + } + + public Point MouseToDocument(Control sender, Point mouse) + { + Point screenPoint = sender.PointToScreen(mouse); + Point sbClient = surfaceBox.PointToClient(screenPoint); + + // Note: We're intentionally making this truncate instead of rounding so that + // when the image is zoomed in, the proper pixel is affected + Point docPoint = Point.Truncate(surfaceBox.ClientToSurface(sbClient)); + + return docPoint; + } + + private void MouseEnterHandler(object sender, EventArgs e) + { + OnDocumentMouseEnter(EventArgs.Empty); + } + + private void MouseLeaveHandler(object sender, EventArgs e) + { + OnDocumentMouseLeave(EventArgs.Empty); + } + + private void MouseMoveHandler(object sender, MouseEventArgs e) + { + Point docPoint = MouseToDocument((Control)sender, new Point(e.X, e.Y)); + PointF docPointF = MouseToDocumentF((Control)sender, new Point(e.X, e.Y)); + + if (RulersEnabled) + { + int x; + + if (docPointF.X > 0) + { + x = (int)Math.Truncate(docPointF.X); + } + else if (docPointF.X < 0) + { + x = (int)Math.Truncate(docPointF.X - 1); + } + else // if (docPointF.X == 0) + { + x = 0; + } + + int y; + + if (docPointF.Y > 0) + { + y = (int)Math.Truncate(docPointF.Y); + } + else if (docPointF.Y < 0) + { + y = (int)Math.Truncate(docPointF.Y - 1); + } + else // if (docPointF.Y == 0) + { + y = 0; + } + + topRuler.Value = x; + leftRuler.Value = y; + + UpdateRulerOffsets(); + } + + OnDocumentMouseMove(new MouseEventArgs(e.Button, e.Clicks, docPoint.X, docPoint.Y, e.Delta)); + } + + private void MouseUpHandler(object sender, MouseEventArgs e) + { + if (sender is Ruler) + { + return; + } + + Point docPoint = MouseToDocument((Control)sender, new Point(e.X, e.Y)); + Point pt = panel.AutoScrollPosition; + panel.Focus(); + + OnDocumentMouseUp(new MouseEventArgs(e.Button, e.Clicks, docPoint.X, docPoint.Y, e.Delta)); + } + + private void MouseDownHandler(object sender, MouseEventArgs e) + { + if (sender is Ruler) + { + return; + } + + Point docPoint = MouseToDocument((Control)sender, new Point(e.X, e.Y)); + Point pt = panel.AutoScrollPosition; + panel.Focus(); + + OnDocumentMouseDown(new MouseEventArgs(e.Button, e.Clicks, docPoint.X, docPoint.Y, e.Delta)); + } + + private void ClickHandler(object sender, EventArgs e) + { + Point pt = panel.AutoScrollPosition; + panel.Focus(); + OnDocumentClick(); + } + + public event EventHandler FirstInputAfterGotFocus; + protected virtual void OnFirstInputAfterGotFocus() + { + if (FirstInputAfterGotFocus != null) + { + FirstInputAfterGotFocus(this, EventArgs.Empty); + } + } + + private void CheckForFirstInputAfterGotFocus() + { + if (this.raiseFirstInputAfterGotFocus) + { + this.raiseFirstInputAfterGotFocus = false; + OnFirstInputAfterGotFocus(); + } + } + + private void Document_Invalidated(object sender, InvalidateEventArgs e) + { + // Note: We don't need to convert this rectangle to controlShadow coordinates and invalidate it + // because, by definition, any invalidation on the document should be within the document's + // bounds and thus within the surfaceBox's bounds and thus outside the controlShadow's clipping + // region. + + if (this.ScaleFactor == ScaleFactor.OneToOne) + { + this.surfaceBox.Invalidate(e.InvalidRect); + } + else + { + Rectangle inflatedInvalidRect = Rectangle.Inflate(e.InvalidRect, 1, 1); + Rectangle clientRect = surfaceBox.SurfaceToClient(inflatedInvalidRect); + Rectangle inflatedClientRect = Rectangle.Inflate(clientRect, 1, 1); + this.surfaceBox.Invalidate(inflatedClientRect); + } + } + + private void Panel_Scroll(object sender, System.Windows.Forms.ScrollEventArgs e) + { + OnScroll(e); + UpdateRulerOffsets(); + } + + /// + /// Before the SurfaceBox paints itself, we need to make sure that the document's composition is up to date + /// + /// + /// + private void SurfaceBox_PrePaint(object sender, PaintEventArgs2 e) + { + try + { + UpdateComposition(true); + } + + catch (ObjectDisposedException ex) + { + Tracing.Ping(ex.ToString()); + } + } + + private int withheldCompositionUpdatedCount = 0; + protected void UpdateComposition(bool raiseEvent) + { + lock (this) + { + using (RenderArgs ra = new RenderArgs(this.compositionSurface)) + { + bool result = this.document.Update(ra); + + if (raiseEvent && (result || this.withheldCompositionUpdatedCount > 0)) + { + OnCompositionUpdated(); + + if (!result && this.withheldCompositionUpdatedCount > 0) + { + --this.withheldCompositionUpdatedCount; + } + } + else if (!raiseEvent && result) + { + // If they want to not raise the event, we must keep track so that + // the next time UpdateComposition() is called we still raise this + // event even if Update() returned false (which indicates there + // was nothing to update) + ++this.withheldCompositionUpdatedCount; + } + + } + } + } + + // Note: You use the Suspend/Resume pattern to suspend and resume refreshing (it hides the controls for a brief moment) + // This is used by set_Document to avoid twitching/flickering in certain cases. + // However, you should use Resume followed by Suspend to bypass the set_Document's use of that. + // Interestingly, SaveConfigDialog does this to avoid 'blinking' when the save parameters are changed. + public void SuspendRefresh() + { + ++this.refreshSuspended; + + this.surfaceBox.Visible + = this.controlShadow.Visible = (refreshSuspended <= 0); + } + + public void ResumeRefresh() + { + --this.refreshSuspended; + + this.surfaceBox.Visible + = this.controlShadow.Visible = (refreshSuspended <= 0); + } + + public void RecenterView(PointF newCenter) + { + RectangleF visibleRect = VisibleDocumentRectangleF; + + PointF cornerPt = new PointF( + newCenter.X - (visibleRect.Width / 2), + newCenter.Y - (visibleRect.Height / 2)); + + this.DocumentScrollPositionF = cornerPt; + } + + public new void Focus() + { + this.panel.Focus(); + } + + private void DocumentMetaDataChangedHandler(object sender, EventArgs e) + { + if (this.document != null) + { + this.leftRuler.Dpu = 1 / document.PixelToPhysicalY(1, this.leftRuler.MeasurementUnit); + this.topRuler.Dpu = 1 / document.PixelToPhysicalY(1, this.topRuler.MeasurementUnit); + } + } + } +} diff --git a/src/Data/DocumentView.resx b/src/Data/DocumentView.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Data/Exif.cs b/src/Data/Exif.cs new file mode 100644 index 0000000..f69d1fe --- /dev/null +++ b/src/Data/Exif.cs @@ -0,0 +1,348 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing.Imaging; + +namespace PaintDotNet +{ + public sealed class Exif + { + private Exif() + { + } + + public static PropertyItem CreatePropertyItem(ExifTagID id, ExifTagType type, byte[] data) + { + return CreatePropertyItem((short)id, type, data); + } + + public static PropertyItem CreatePropertyItem(short id, ExifTagType type, byte[] data) + { + PropertyItem pi = PdnGraphics.CreatePropertyItem(); + + pi.Id = id; + pi.Type = (short)type; + pi.Len = data.Length; + pi.Value = (byte[])data.Clone(); + + return pi; + } + + public static string DecodeAsciiValue(PropertyItem pi) + { + if (pi.Type != (short)ExifTagType.Ascii) + { + throw new ArgumentException("pi.Type != ExifTagType.Ascii"); + } + + string data = System.Text.Encoding.ASCII.GetString(pi.Value); + if (data[data.Length - 1] == '\0') + { + data = data.Substring(0, data.Length - 1); + } + + return data; + } + + public static PropertyItem CreateAscii(ExifTagID id, string value) + { + return CreateAscii((short)id, value); + } + + public static PropertyItem CreateAscii(short id, string value) + { + return CreatePropertyItem(id, ExifTagType.Ascii, EncodeAsciiValue(value + "\0")); + } + + public static byte[] EncodeAsciiValue(string value) + { + return System.Text.Encoding.ASCII.GetBytes(value); + } + + public static byte DecodeByteValue(PropertyItem pi) + { + if (pi.Type != (short)ExifTagType.Byte) + { + throw new ArgumentException("pi.Type != ExifTagType.Byte"); + } + + if (pi.Value.Length != 1) + { + throw new ArgumentException("pi.Value.Length != 1"); + } + + if (pi.Len != 1) + { + throw new ArgumentException("pi.Length != 1"); + } + + return pi.Value[0]; + } + + public static PropertyItem CreateByte(ExifTagID id, byte value) + { + return CreateByte((short)id, value); + } + + public static PropertyItem CreateByte(short id, byte value) + { + return CreatePropertyItem(id, ExifTagType.Byte, EncodeByteValue(value)); + } + + public static byte[] EncodeByteValue(byte value) + { + return new byte[] { value }; + } + + public static ushort DecodeShortValue(PropertyItem pi) + { + if (pi.Type != (short)ExifTagType.Short) + { + throw new ArgumentException("pi.Type != ExifTagType.Short"); + } + + if (pi.Value.Length != 2) + { + throw new ArgumentException("pi.Value.Length != 2"); + } + + if (pi.Len != 2) + { + throw new ArgumentException("pi.Length != 2"); + } + + return (ushort)(pi.Value[0] + (pi.Value[1] << 8)); + } + + public static PropertyItem CreateShort(ExifTagID id, ushort value) + { + return CreateShort((short)id, value); + } + + public static PropertyItem CreateShort(short id, ushort value) + { + return CreatePropertyItem(id, ExifTagType.Short, EncodeShortValue(value)); + } + + public static byte[] EncodeShortValue(ushort value) + { + return new byte[] { + (byte)(value & 0xff), + (byte)((value >> 8) & 0xff) + }; + } + + public static uint DecodeLongValue(PropertyItem pi) + { + if (pi.Type != (short)ExifTagType.Long) + { + throw new ArgumentException("pi.Type != ExifTagType.Long"); + } + + if (pi.Value.Length != 4) + { + throw new ArgumentException("pi.Value.Length != 4"); + } + + if (pi.Len != 4) + { + throw new ArgumentException("pi.Length != 4"); + } + + return (uint)pi.Value[0] + ((uint)pi.Value[1] << 8) + ((uint)pi.Value[2] << 16) + ((uint)pi.Value[3] << 24); + } + + public static PropertyItem CreateLong(ExifTagID id, uint value) + { + return CreateLong((short)id, value); + } + + public static PropertyItem CreateLong(short id, uint value) + { + return CreatePropertyItem(id, ExifTagType.Long, EncodeLongValue(value)); + } + + public static byte[] EncodeLongValue(uint value) + { + return new byte[] { + (byte)(value & 0xff), + (byte)((value >> 8) & 0xff), + (byte)((value >> 16) & 0xff), + (byte)((value >> 24) & 0xff) + }; + } + + public static void DecodeRationalValue(PropertyItem pi, out uint numerator, out uint denominator) + { + if (pi.Type != (short)ExifTagType.Rational) + { + throw new ArgumentException("pi.Type != ExifTagType.Rational"); + } + + if (pi.Value.Length != 8) + { + throw new ArgumentException("pi.Value.Length != 8"); + } + + if (pi.Len != 8) + { + throw new ArgumentException("pi.Length != 8"); + } + + numerator = (uint)pi.Value[0] + ((uint)pi.Value[1] << 8) + ((uint)pi.Value[2] << 16) + ((uint)pi.Value[3] << 24); + denominator = (uint)pi.Value[4] + ((uint)pi.Value[5] << 8) + ((uint)pi.Value[6] << 16) + ((uint)pi.Value[7] << 24); + } + + public static PropertyItem CreateRational(ExifTagID id, uint numerator, uint denominator) + { + return CreateRational((short)id, numerator, denominator); + } + + public static PropertyItem CreateRational(short id, uint numerator, uint denominator) + { + return CreatePropertyItem(id, ExifTagType.Rational, EncodeRationalValue(numerator, denominator)); + } + + public static byte[] EncodeRationalValue(uint numerator, uint denominator) + { + return new byte[] { + (byte)(numerator & 0xff), + (byte)((numerator >> 8) & 0xff), + (byte)((numerator >> 16) & 0xff), + (byte)((numerator >> 24) & 0xff), + (byte)(denominator & 0xff), + (byte)((denominator >> 8) & 0xff), + (byte)((denominator >> 16) & 0xff), + (byte)((denominator >> 24) & 0xff) + }; + } + + public static byte DecodeUndefinedValue(PropertyItem pi) + { + if (pi.Type != (short)ExifTagType.Undefined) + { + throw new ArgumentException("pi.Type != ExifTagType.Undefined"); + } + + if (pi.Value.Length != 1) + { + throw new ArgumentException("pi.Value.Length != 1"); + } + + if (pi.Len != 1) + { + throw new ArgumentException("pi.Length != 1"); + } + + return pi.Value[0]; + } + + public static PropertyItem CreateUndefined(ExifTagID id, byte value) + { + return CreateUndefined((short)id, value); + } + + public static PropertyItem CreateUndefined(short id, byte value) + { + return CreatePropertyItem(id, ExifTagType.Undefined, EncodeUndefinedValue(value)); + } + + public static byte[] EncodeUndefinedValue(byte value) + { + return new byte[] { value }; + } + + public static int DecodeSLongValue(PropertyItem pi) + { + if (pi.Type != (short)ExifTagType.SLong) + { + throw new ArgumentException("pi.Type != ExifTagType.SLong"); + } + + if (pi.Value.Length != 4) + { + throw new ArgumentException("pi.Value.Length != 4"); + } + + if (pi.Len != 4) + { + throw new ArgumentException("pi.Length != 4"); + } + + return pi.Value[0] + (pi.Value[1] << 8) + (pi.Value[2] << 16) + (pi.Value[3] << 24); + } + + public static PropertyItem CreateSLong(ExifTagID id, int value) + { + return CreateSLong((short)id, value); + } + + public static PropertyItem CreateSLong(short id, int value) + { + return CreatePropertyItem(id, ExifTagType.SLong, EncodeSLongValue(value)); + } + + public static byte[] EncodeSLongValue(int value) + { + return new byte[] { + (byte)(value & 0xff), + (byte)((value >> 8) & 0xff), + (byte)((value >> 16) & 0xff), + (byte)((value >> 24) & 0xff) + }; + } + + public static void DecodeRationalValue(PropertyItem pi, out int numerator, out int denominator) + { + if (pi.Type != (short)ExifTagType.SRational) + { + throw new ArgumentException("pi.Type != ExifTagType.SRational"); + } + + if (pi.Value.Length != 8) + { + throw new ArgumentException("pi.Value.Length != 8"); + } + + if (pi.Len != 8) + { + throw new ArgumentException("pi.Length != 8"); + } + + numerator = pi.Value[0] + (pi.Value[1] << 8) + (pi.Value[2] << 16) + (pi.Value[3] << 24); + denominator = pi.Value[4] + (pi.Value[5] << 8) + (pi.Value[6] << 16) + (pi.Value[7] << 24); + } + + public static PropertyItem CreateSRational(ExifTagID id, int numerator, int denominator) + { + return CreateSRational((short)id, numerator, denominator); + } + + public static PropertyItem CreateSRational(short id, int numerator, int denominator) + { + return CreatePropertyItem(id, ExifTagType.SRational, EncodeSRationalValue(numerator, denominator)); + } + + public static byte[] EncodeSRationalValue(int numerator, int denominator) + { + return new byte[] { + (byte)(numerator & 0xff), + (byte)((numerator >> 8) & 0xff), + (byte)((numerator >> 16) & 0xff), + (byte)((numerator >> 24) & 0xff), + (byte)(denominator & 0xff), + (byte)((denominator >> 8) & 0xff), + (byte)((denominator >> 16) & 0xff), + (byte)((denominator >> 24) & 0xff) + }; + } + } +} diff --git a/src/Data/ExifTagID.cs b/src/Data/ExifTagID.cs new file mode 100644 index 0000000..04217b3 --- /dev/null +++ b/src/Data/ExifTagID.cs @@ -0,0 +1,55 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum ExifTagID + : short + { + // Tags relating to image data structure + ImageWidth = 256, + ImageLength = 257, + BitsPerSample = 258, + Compression = 259, + PhotometricInterpretation = 262, + Orientation = 274, + SamplesPerPixel = 277, + PlanarConfiguration = 284, + YCbCrSubSampling = 530, + YCbCrPositioning = 531, + XResolution = 282, + YResolution = 283, + ResolutionUnit = 296, + + // Tags relating to recording offset + StripOffsets = 273, + RowsPerStrip = 278, + StripByteCounts = 279, + JPEGInterchangeFormat = 513, + JPEGInterchangeFormatLength = 514, + + // Tags relating to image data characteristics + TransferFunction = 301, + WhitePoint = 318, + PrimaryChromaticities = 319, + YCbCrCoefficients = 529, + ReferenceBlackWhite = 532, + + // Other tags + DateTime = 306, + ImageDescription = 270, + Make = 271, + Model = 272, + Software = 305, + Artist = 315, + Copyright = unchecked((short)33432) + } +} diff --git a/src/Data/ExifTagType.cs b/src/Data/ExifTagType.cs new file mode 100644 index 0000000..5d9213c --- /dev/null +++ b/src/Data/ExifTagType.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum ExifTagType + : ushort + { + Byte = 1, + Ascii = 2, + Short = 3, + Long = 4, + Rational = 5, + Undefined = 7, + SLong = 9, + SRational = 10 + } +} diff --git a/src/Data/FileType.cs b/src/Data/FileType.cs new file mode 100644 index 0000000..8539960 --- /dev/null +++ b/src/Data/FileType.cs @@ -0,0 +1,561 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Data.Quantize; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Runtime; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; + +namespace PaintDotNet +{ + /// + /// Represents one type of file that PaintDotNet can load or save. + /// + public abstract class FileType + { + private string[] extensions; + private string name; + private FileTypeFlags flags; + + // should be of the format ".ext" ... like ".bmp" or ".jpg" + // The first extension in this list is the default extension (".jpg" for JPEG, + // for instance, as ".jfif" etc. are not seen very often) + public string[] Extensions + { + get + { + return (string[])this.extensions.Clone(); + } + } + + /// + /// Gets the default extension for the FileType. + /// + /// + /// This is always the first extension that is supported + /// + public string DefaultExtension + { + get + { + return this.extensions[0]; + } + } + + /// + /// Returns the friendly name of the file type, such as "Bitmap" or "JPEG". + /// + public string Name + { + get + { + return this.name; + } + } + + public FileTypeFlags Flags + { + get + { + return this.flags; + } + } + + /// + /// Gets a flag indicating whether this FileType supports layers. + /// + /// + /// If a FileType is asked to save a Document that has more than one layer, + /// it will flatten it before it saves it. + /// + public bool SupportsLayers + { + get + { + return (this.flags & FileTypeFlags.SupportsLayers) != 0; + } + } + + /// + /// Gets a flag indicating whether this FileType supports custom headers. + /// + /// + /// If this returns false, then the Document's CustomHeaders will be discarded + /// on saving. + /// + public bool SupportsCustomHeaders + { + get + { + return (this.flags & FileTypeFlags.SupportsCustomHeaders) != 0; + } + } + + /// + /// Gets a flag indicating whether this FileType supports the Save() method. + /// + /// + /// If this property returns false, calling Save() will throw a NotSupportedException. + /// + public bool SupportsSaving + { + get + { + return (this.flags & FileTypeFlags.SupportsSaving) != 0; + } + } + + /// + /// Gets a flag indicating whether this FileType supports the Load() method. + /// + /// + /// If this property returns false, calling Load() will throw a NotSupportedException. + /// + public bool SupportsLoading + { + get + { + return (this.flags & FileTypeFlags.SupportsLoading) != 0; + } + } + + /// + /// Gets a flag indicating whether this FileType reports progress while saving. + /// + /// + /// If false, then the callback delegate passed to Save() will be ignored. + /// + public bool SavesWithProgress + { + get + { + return (this.flags & FileTypeFlags.SavesWithProgress) != 0; + } + } + + [Obsolete("Use the FileType(string, FileTypeFlags, string[]) overload instead", true)] + public FileType(string name, bool supportsLayers, bool supportsCustomHeaders, string[] extensions) + : this(name, supportsLayers, supportsCustomHeaders, true, true, false, extensions) + { + } + + [Obsolete("Use the FileType(string, FileTypeFlags, string[]) overload instead", true)] + public FileType(string name, bool supportsLayers, bool supportsCustomHeaders, bool supportsSaving, + bool supportsLoading, bool savesWithProgress, string[] extensions) + : this(name, + (supportsLayers ? FileTypeFlags.SupportsLayers : 0) | + (supportsCustomHeaders ? FileTypeFlags.SupportsCustomHeaders : 0) | + (supportsSaving ? FileTypeFlags.SupportsSaving : 0) | + (supportsLoading ? FileTypeFlags.SupportsLoading : 0) | + (savesWithProgress ? FileTypeFlags.SavesWithProgress : 0), + extensions) + { + } + + public FileType(string name, FileTypeFlags flags, string[] extensions) + { + this.name = name; + this.flags = flags; + this.extensions = (string[])extensions.Clone(); + } + + public bool SupportsExtension(string ext) + { + foreach (string ext2 in extensions) + { + if (0 == string.Compare(ext2, ext, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + + return false; + } + + [Obsolete("This method is retained for compatibility with older plugins. Please use the other overload of Quantize().")] + protected Bitmap Quantize(Surface quantizeMe, int ditherAmount, int maxColors, ProgressEventHandler progressCallback) + { + // This is the old version of the function took maxColors=255 to mean 255 colors + 1 slot for transparency. + // The new version expects maxColors=256 and enableTransparency=true for this. + + if (maxColors < 2 || maxColors > 255) + { + throw new ArgumentOutOfRangeException( + "maxColors", + maxColors, + "Out of bounds. Must be in the range [2, 255]"); + } + + return Quantize(quantizeMe, ditherAmount, maxColors + 1, true, progressCallback); + } + + /// + /// Takes a Surface and quantizes it down to an 8-bit bitmap. + /// + /// The Surface to quantize. + /// How strong should dithering be applied. 0 for no dithering, 8 for full dithering. 7 is generally a good default to use. + /// The maximum number of colors to use. This may range from 2 to 256. + /// If true, then one color slot will be reserved for transparency. Any color with an alpha value less than 255 will be transparent in the output. + /// The progress callback delegate. + /// An 8-bit Bitmap that is the same size as quantizeMe. + protected Bitmap Quantize(Surface quantizeMe, int ditherAmount, int maxColors, bool enableTransparency, ProgressEventHandler progressCallback) + { + if (ditherAmount < 0 || ditherAmount > 8) + { + throw new ArgumentOutOfRangeException( + "ditherAmount", + ditherAmount, + "Out of bounds. Must be in the range [0, 8]"); + } + + if (maxColors < 2 || maxColors > 256) + { + throw new ArgumentOutOfRangeException( + "maxColors", + maxColors, + "Out of bounds. Must be in the range [2, 256]"); + } + + // TODO: detect if transparency is needed? or take another argument + + using (Bitmap bitmap = quantizeMe.CreateAliasedBitmap(quantizeMe.Bounds, true)) + { + OctreeQuantizer quantizer = new OctreeQuantizer(maxColors, enableTransparency); + quantizer.DitherLevel = ditherAmount; + Bitmap quantized = quantizer.Quantize(bitmap, progressCallback); + return quantized; + } + } + + [Obsolete("Use the other Save() overload instead", true)] + public void Save(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback, bool rememberToken) + { + using (Surface scratch = new Surface(input.Width, input.Height)) + { + Save(input, output, token, callback, rememberToken); + } + } + + public void Save( + Document input, + Stream output, + SaveConfigToken token, + Surface scratchSurface, + ProgressEventHandler callback, + bool rememberToken) + { + Tracing.LogFeature("Save(" + GetType().FullName + ")"); + + if (!this.SupportsSaving) + { + throw new NotImplementedException("Saving is not supported by this FileType"); + } + else + { + Surface disposeMe = null; + + if (scratchSurface == null) + { + disposeMe = new Surface(input.Size); + scratchSurface = disposeMe; + } + else if (scratchSurface.Size != input.Size) + { + throw new ArgumentException("scratchSurface.Size must equal input.Size"); + } + + if (rememberToken) + { + Type ourType = this.GetType(); + string savedTokenName = "SaveConfigToken." + ourType.Namespace + "." + ourType.Name + ".BinaryFormatter"; + + MemoryStream ms = new MemoryStream(); + + BinaryFormatter formatter = new BinaryFormatter(); + DeferredFormatter deferredFormatter = new DeferredFormatter(false, null); + StreamingContext streamingContext = new StreamingContext(formatter.Context.State, deferredFormatter); + formatter.Context = streamingContext; + + object tokenSubset = GetSerializablePortionOfSaveConfigToken(token); + + formatter.Serialize(ms, tokenSubset); + deferredFormatter.FinishSerialization(ms); + + byte[] bytes = ms.GetBuffer(); + string base64Bytes = Convert.ToBase64String(bytes); + + Settings.CurrentUser.SetString(savedTokenName, base64Bytes); + } + + try + { + OnSave(input, output, token, scratchSurface, callback); + } + + catch (OnSaveNotImplementedException) + { + OldOnSaveTrampoline(input, output, token, callback); + } + + if (disposeMe != null) + { + disposeMe.Dispose(); + disposeMe = null; + } + } + } + + protected virtual SaveConfigToken GetSaveConfigTokenFromSerializablePortion(object portion) + { + return (SaveConfigToken)portion; + } + + protected virtual object GetSerializablePortionOfSaveConfigToken(SaveConfigToken token) + { + return token; + } + + private sealed class OnSaveNotImplementedException + : Exception + { + public OnSaveNotImplementedException(string message) + : base(message) + { + } + } + + /// + /// Because the old OnSave() method is obsolete, we must use reflection to call it. + /// This is important for legacy FileType plugins. It allows us to ensure that no + /// new plugins can be compiled using the old OnSave() overload. + /// + private void OldOnSaveTrampoline(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback) + { + MethodInfo onSave = GetType().GetMethod( + "OnSave", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy, + Type.DefaultBinder, + new Type[] + { + typeof(Document), + typeof(Stream), + typeof(SaveConfigToken), + typeof(ProgressEventHandler) + }, + null); + + onSave.Invoke( + this, + new object[] + { + input, + output, + token, + callback + }); + } + + [Obsolete("Use the other OnSave() overload. It provides a scratch rendering surface that may enable your plugin to conserve memory usage.")] + protected virtual void OnSave(Document input, Stream output, SaveConfigToken token, ProgressEventHandler callback) + { + } + + protected virtual void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback) + { + throw new OnSaveNotImplementedException("Derived classes must implement this method. It is virtual instead of abstract in order to maintain compatibility with legacy plugins."); + } + + /// + /// Determines if saving with a given SaveConfigToken would alter the image + /// in any way. Put another way, if the document is saved with these settings + /// and then immediately loaded, would it have exactly the same pixel values? + /// Any lossy codec should return 'false'. + /// This value is used to optimizing preview rendering memory usage, and as such + /// flattening should not be taken in to consideration. For example, the codec + /// for PNG returns true, even though it flattens the image. + /// + /// The SaveConfigToken to determine reflexiveness for. + /// true if the save would be reflexive, false if not + /// If the SaveConfigToken is for another FileType, the result is undefined. + public virtual bool IsReflexive(SaveConfigToken token) + { + return false; + } + + public virtual SaveConfigWidget CreateSaveConfigWidget() + { + return new NoSaveConfigWidget(); + } + + [Serializable] + private sealed class NoSaveConfigToken + : SaveConfigToken + { + } + + /// + /// Gets a flag indicating whether or not the file type supports configuration + /// via a SaveConfigToken and SaveConfigWidget. + /// + /// + /// Implementers of FileType derived classes don't need to do anything special + /// for this property to be accurate. If your FileType implements + /// CreateDefaultSaveConfigToken, this will correctly return true. + /// + public bool SupportsConfiguration + { + get + { + SaveConfigToken token = CreateDefaultSaveConfigToken(); + return !(token is NoSaveConfigToken); + } + } + + public SaveConfigToken GetLastSaveConfigToken() + { + Type ourType = this.GetType(); + string savedTokenName = "SaveConfigToken." + ourType.Namespace + "." + ourType.Name + ".BinaryFormatter"; + string savedToken = Settings.CurrentUser.GetString(savedTokenName, null); + SaveConfigToken saveConfigToken = null; + + if (savedToken != null) + { + try + { + byte[] bytes = Convert.FromBase64String(savedToken); + + MemoryStream ms = new MemoryStream(bytes); + + BinaryFormatter formatter = new BinaryFormatter(); + DeferredFormatter deferred = new DeferredFormatter(); + StreamingContext streamingContext = new StreamingContext(formatter.Context.State, deferred); + formatter.Context = streamingContext; + + SerializationFallbackBinder sfb = new SerializationFallbackBinder(); + sfb.AddAssembly(this.GetType().Assembly); + sfb.AddAssembly(typeof(FileType).Assembly); + formatter.Binder = sfb; + + object obj = formatter.Deserialize(ms); + deferred.FinishDeserialization(ms); + + ms.Close(); + ms = null; + + //SaveConfigToken sct = new SaveConfigToken(); + //saveConfigToken = (SaveConfigToken)obj; + saveConfigToken = GetSaveConfigTokenFromSerializablePortion(obj); + } + + catch (Exception) + { + // Ignore erros and revert to default + saveConfigToken = null; + } + } + + if (saveConfigToken == null) + { + saveConfigToken = CreateDefaultSaveConfigToken(); + } + + return saveConfigToken; + } + + public SaveConfigToken CreateDefaultSaveConfigToken() + { + return OnCreateDefaultSaveConfigToken(); + } + + /// + /// Creates a SaveConfigToken for this FileType with the default values. + /// + protected virtual SaveConfigToken OnCreateDefaultSaveConfigToken() + { + return new NoSaveConfigToken(); + } + + public Document Load(Stream input) + { + Tracing.LogFeature("Load(" + GetType().FullName + ")"); + + if (!this.SupportsLoading) + { + throw new NotSupportedException("Loading not supported for this FileType"); + } + else + { + return OnLoad(input); + } + } + + protected abstract Document OnLoad(Stream input); + + public override bool Equals(object obj) + { + if (obj == null || !(obj is FileType)) + { + return false; + } + + return this.name.Equals(((FileType)obj).Name); + } + + public override int GetHashCode() + { + return this.name.GetHashCode(); + } + + /// + /// Returns a string that can be used for populating a *FileDialog common dialog. + /// + public override string ToString() + { + StringBuilder sb = new StringBuilder(name); + sb.Append(" ("); + + for (int i = 0; i < extensions.Length; ++i) + { + sb.Append("*"); + sb.Append(extensions[i]); + + if (i != extensions.Length - 1) + { + sb.Append("; "); + } + else + { + sb.Append(")"); + } + } + + sb.Append("|"); + + for (int i = 0; i < extensions.Length; ++i) + { + sb.Append("*"); + sb.Append(extensions[i]); + + if (i != extensions.Length - 1) + { + sb.Append(";"); + } + } + + return sb.ToString(); + } + } +} diff --git a/src/Data/FileTypeCollection.cs b/src/Data/FileTypeCollection.cs new file mode 100644 index 0000000..ce175ed --- /dev/null +++ b/src/Data/FileTypeCollection.cs @@ -0,0 +1,245 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + /// + /// Represents a collection of FileType instances. + /// + [Serializable] + public class FileTypeCollection + { + private FileType[] fileTypes; + public FileType[] FileTypes + { + get + { + return (FileType[])fileTypes.Clone(); + } + } + + public int Length + { + get + { + return fileTypes.Length; + } + } + + public FileType this[int index] + { + get + { + return fileTypes[index]; + } + } + + public string[] AllExtensions + { + get + { + List exts = new List(); + + foreach (FileType fileType in this.fileTypes) + { + foreach (string ext in fileType.Extensions) + { + exts.Add(ext); + } + } + + return exts.ToArray(); + } + } + + internal FileTypeCollection(FileType[] fileTypes) + { + this.fileTypes = fileTypes; + } + + public FileTypeCollection(ICollection fileTypes) + { + this.fileTypes = new FileType[fileTypes.Count]; + int dstIndex = 0; + + foreach (FileType ft in fileTypes) + { + this.fileTypes[dstIndex] = ft; + ++dstIndex; + } + } + + public static FileType[] FilterFileTypeList(FileType[] input, bool excludeCantSave, bool excludeCantLoad) + { + List filtered = new List(); + + foreach (FileType fileType in input) + { + if (excludeCantSave && !fileType.SupportsSaving) + { + continue; + } + + if (excludeCantLoad && !fileType.SupportsLoading) + { + continue; + } + + filtered.Add(fileType); + } + + return filtered.ToArray(); + } + + public string ToString(bool excludeCantSave, bool excludeCantLoad) + { + FileType[] filtered = FilterFileTypeList(this.fileTypes, excludeCantSave, excludeCantLoad); + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < filtered.Length; ++i) + { + FileType fileType = filtered[i]; + sb.Append(fileType.ToString()); + + if (i != filtered.Length - 1) + { + sb.Append("|"); + } + } + + return sb.ToString(); + } + + /// + /// Allows you to include an "All" type at the top that includes all the filetypes + /// "All images (*.bmp, *.gif, ...)" for instance. + /// + /// Whether or not to include the 'all' file type at the top + /// The name of the 'all' type (example: "All images"). If this is null + /// but includeAll is true, then this defaults to the string "All image types" + public string ToString(bool includeAll, string allName, bool excludeCantSave, bool excludeCantLoad) + { + if (allName == null) + { + allName = PdnResources.GetString("FileTypeCollection.AllImageTypes"); + } + + if (includeAll) + { + StringBuilder description = new StringBuilder(allName); + StringBuilder formats = new StringBuilder(); + bool didFirst = false; + FileType[] filtered = FilterFileTypeList(this.fileTypes, excludeCantSave, excludeCantLoad); + + for (int i = 0; i < filtered.Length; ++i) + { + if (!didFirst) + { + didFirst = true; + description.Append(" ("); + } + + string[] extensions = (filtered[i]).Extensions; + + for (int j = 0; j < extensions.Length; ++j) + { + description.Append("*"); + description.Append(extensions[j]); + formats.Append("*"); + formats.Append(extensions[j]); + + // if this is NOT the last extension in the whole list ... + if (!(j == extensions.Length - 1 && i == filtered.Length - 1)) + { + description.Append(", "); + formats.Append(";"); + } + } + + } + + if (didFirst) + { + description.Append(")"); + } + + string ret = description.ToString() + "|" + formats.ToString(); + + if (filtered.Length != 0) + { + ret += "|" + ToString(excludeCantSave, excludeCantLoad); + } + + return ret; + } + else + { + return ToString(excludeCantSave, excludeCantLoad); + } + } + + public int IndexOfFileType(FileType fileType) + { + if (fileType == null) + { + return -1; + } + + for (int i = 0; i < fileTypes.Length; ++i) + { + if (fileTypes[i].Name == fileType.Name) + { + return i; + } + } + + return -1; + } + + public int IndexOfExtension(string findMeExt) + { + if (findMeExt == null) + { + return -1; + } + + for (int i = 0; i < fileTypes.Length; ++i) + { + foreach (string ext in fileTypes[i].Extensions) + { + if (ext.ToLower() == findMeExt.ToLower()) + { + return i; + } + } + } + + return -1; + } + + public int IndexOfName(string name) + { + for (int i = 0; i < fileTypes.Length; ++i) + { + if (fileTypes[i].Name == name) + { + return i; + } + } + + return -1; + } + } +} diff --git a/src/Data/FileTypeFlags.cs b/src/Data/FileTypeFlags.cs new file mode 100644 index 0000000..8b41743 --- /dev/null +++ b/src/Data/FileTypeFlags.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + [Flags] + public enum FileTypeFlags + : long + { + None = 0, + SupportsLayers = 1, + SupportsCustomHeaders = 2, + SupportsSaving = 4, + SupportsLoading = 8, + SavesWithProgress = 16 + } +} + diff --git a/src/Data/FileType`2.cs b/src/Data/FileType`2.cs new file mode 100644 index 0000000..987e257 --- /dev/null +++ b/src/Data/FileType`2.cs @@ -0,0 +1,89 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; + +namespace PaintDotNet +{ + /// + /// A strongly typed version of FileType. + /// + public abstract class FileType + : FileType + where TToken : SaveConfigToken + where TWidget : SaveConfigWidget + { + public FileType(string name, FileTypeFlags flags, string[] extensions) + : base(name, flags, extensions) + { + } + + protected virtual bool IsReflexive(TToken token) + { + return false; + } + + public override sealed bool IsReflexive(SaveConfigToken token) + { + return IsReflexive((TToken)token); + } + + protected abstract void OnSaveT(Document input, Stream output, TToken token, Surface scratchSurface, ProgressEventHandler progressCallback); + + protected override sealed void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback) + { + OnSaveT(input, output, (TToken)token, scratchSurface, callback); + } + + public void Save(Document input, Stream output, TToken token, Surface scratchSurface, ProgressEventHandler callback, bool rememberToken) + { + base.Save(input, output, token, scratchSurface, callback, rememberToken); + } + + protected abstract TWidget OnCreateSaveConfigWidgetT(); + + public override sealed SaveConfigWidget CreateSaveConfigWidget() + { + return OnCreateSaveConfigWidgetT(); + } + + protected abstract TToken OnCreateDefaultSaveConfigTokenT(); + + protected override sealed SaveConfigToken OnCreateDefaultSaveConfigToken() + { + return OnCreateDefaultSaveConfigTokenT(); + } + + public new TToken CreateDefaultSaveConfigToken() + { + return (TToken)base.CreateDefaultSaveConfigToken(); + } + + protected virtual TToken GetSaveConfigTokenFromSerializablePortionT(object portion) + { + return (TToken)portion; + } + + protected override sealed SaveConfigToken GetSaveConfigTokenFromSerializablePortion(object portion) + { + return GetSaveConfigTokenFromSerializablePortionT(portion); + } + + protected virtual object GetSerializablePortionOfSaveConfigToken(TToken token) + { + return token; + } + + protected override sealed object GetSerializablePortionOfSaveConfigToken(SaveConfigToken token) + { + return GetSerializablePortionOfSaveConfigToken((TToken)token); + } + } +} diff --git a/src/Data/GdiPlusFileType.cs b/src/Data/GdiPlusFileType.cs new file mode 100644 index 0000000..219927a --- /dev/null +++ b/src/Data/GdiPlusFileType.cs @@ -0,0 +1,158 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace PaintDotNet +{ + /// + /// Implements FileType for generic GDI+ codecs. + /// + /// + /// GDI+ file types do not support custom headers. + /// + public class GdiPlusFileType + : FileType + { + private ImageFormat imageFormat; + public ImageFormat ImageFormat + { + get + { + return this.imageFormat; + } + } + + protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback) + { + GdiPlusFileType.Save(input, output, scratchSurface, this.ImageFormat, callback); + } + + public static void Save(Document input, Stream output, Surface scratchSurface, ImageFormat format, ProgressEventHandler callback) + { + // flatten the document + scratchSurface.Clear(ColorBgra.FromBgra(0, 0, 0, 0)); + + using (RenderArgs ra = new RenderArgs(scratchSurface)) + { + input.Render(ra, true); + } + + using (Bitmap bitmap = scratchSurface.CreateAliasedBitmap()) + { + LoadProperties(bitmap, input); + bitmap.Save(output, format); + } + } + + public static void LoadProperties(Image dstImage, Document srcDoc) + { + Bitmap asBitmap = dstImage as Bitmap; + + if (asBitmap != null) + { + // Sometimes GDI+ does not honor the resolution tags that we + // put in manually via the EXIF properties. + float dpiX; + float dpiY; + + switch (srcDoc.DpuUnit) + { + case MeasurementUnit.Centimeter: + dpiX = (float)Document.DotsPerCmToDotsPerInch(srcDoc.DpuX); + dpiY = (float)Document.DotsPerCmToDotsPerInch(srcDoc.DpuY); + break; + + case MeasurementUnit.Inch: + dpiX = (float)srcDoc.DpuX; + dpiY = (float)srcDoc.DpuY; + break; + + default: + case MeasurementUnit.Pixel: + dpiX = 1.0f; + dpiY = 1.0f; + break; + } + + try + { + asBitmap.SetResolution(dpiX, dpiY); + } + + catch (Exception) + { + // Ignore error + } + } + + Metadata metaData = srcDoc.Metadata; + + foreach (string key in metaData.GetKeys(Metadata.ExifSectionName)) + { + string blob = metaData.GetValue(Metadata.ExifSectionName, key); + PropertyItem pi = PdnGraphics.DeserializePropertyItem(blob); + + try + { + dstImage.SetPropertyItem(pi); + } + + catch (ArgumentException) + { + // Ignore error: the image does not support property items + } + } + } + + protected override Document OnLoad(Stream input) + { + using (Image image = PdnResources.LoadImage(input)) + { + Document document = Document.FromImage(image); + return document; + } + } + + public static ImageCodecInfo GetImageCodecInfo(ImageFormat format) + { + ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders(); + + foreach (ImageCodecInfo icf in encoders) + { + if (icf.FormatID == format.Guid) + { + return icf; + } + } + + return null; + } + + public GdiPlusFileType(string name, ImageFormat imageFormat, bool supportsLayers, string[] extensions) + : this(name, imageFormat, supportsLayers, extensions, false) + { + } + + public GdiPlusFileType(string name, ImageFormat imageFormat, bool supportsLayers, string[] extensions, bool savesWithProgress) + : base(name, + (supportsLayers ? FileTypeFlags.SupportsLayers : 0) | + FileTypeFlags.SupportsLoading | + FileTypeFlags.SupportsSaving | + (savesWithProgress ? FileTypeFlags.SavesWithProgress : 0), + extensions) + { + this.imageFormat = imageFormat; + } + } +} diff --git a/src/Data/GeneratedCodeWarning.h b/src/Data/GeneratedCodeWarning.h new file mode 100644 index 0000000..bbcd7e4 --- /dev/null +++ b/src/Data/GeneratedCodeWarning.h @@ -0,0 +1 @@ +// This file is generated at compile time. DO NOT MODIFY! \ No newline at end of file diff --git a/src/Data/GifFileType.cs b/src/Data/GifFileType.cs new file mode 100644 index 0000000..b58f8f1 --- /dev/null +++ b/src/Data/GifFileType.cs @@ -0,0 +1,129 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.IndirectUI; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace PaintDotNet +{ + public sealed class GifFileType + : InternalFileType + { + public enum PropertyNames + { + Threshold = 0, + DitherLevel = 1 + } + + public GifFileType() + : base("GIF", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SavesWithProgress, new string[] { ".gif" }) + { + } + + public override PropertyCollection OnCreateSavePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.DitherLevel, 7, 0, 8)); + props.Add(new Int32Property(PropertyNames.Threshold, 128, 0, 255)); + + return new PropertyCollection(props); + } + + internal override int GetThresholdFromToken(PropertyBasedSaveConfigToken token) + { + int threshold = token.GetProperty(PropertyNames.Threshold).Value; + return threshold; + } + + internal override int GetDitherLevelFromToken(PropertyBasedSaveConfigToken token) + { + int ditherLevel = token.GetProperty(PropertyNames.DitherLevel).Value; + return ditherLevel; + } + + internal override Set CreateAllowedBitDepthListFromToken(PropertyBasedSaveConfigToken token) + { + var bitDepths = new Set(); + + bitDepths.Add(SavableBitDepths.Rgb8); + bitDepths.Add(SavableBitDepths.Rgba8); + + return bitDepths; + } + + public override ControlInfo OnCreateSaveConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultSaveConfigUI(props); + + configUI.SetPropertyControlValue( + PropertyNames.DitherLevel, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("GifFileType.ConfigUI.DitherLevel.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.Threshold, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("GifFileType.ConfigUI.Threshold.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.Threshold, + ControlInfoPropertyNames.Description, + PdnResources.GetString("GifFileType.ConfigUI.Threshold.Description")); + + return configUI; + } + + protected override Document OnLoad(Stream input) + { + using (Image image = PdnResources.LoadImage(input)) + { + Document document = Document.FromImage(image); + return document; + } + } + + internal override void FinalSave( + Document input, + Stream output, + Surface scratchSurface, + int ditherLevel, + SavableBitDepths bitDepth, + PropertyBasedSaveConfigToken token, + ProgressEventHandler progressCallback) + { + bool enableAlpha; + + switch (bitDepth) + { + case SavableBitDepths.Rgb8: + enableAlpha = false; + break; + + case SavableBitDepths.Rgba8: + enableAlpha = true; + break; + + default: + throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(SavableBitDepths)); + } + + using (Bitmap quantized = Quantize(scratchSurface, ditherLevel, 256, enableAlpha, progressCallback)) + { + quantized.Save(output, ImageFormat.Gif); + } + } + } +} diff --git a/src/Data/IFileTypeFactory.cs b/src/Data/IFileTypeFactory.cs new file mode 100644 index 0000000..4a9bfa3 --- /dev/null +++ b/src/Data/IFileTypeFactory.cs @@ -0,0 +1,24 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// This interface is used to generate FileType instances. + /// The FileTypes class, when requested for a list of FileType instances, + /// will use reflection to search for classes that implement this interface + /// and then call their GetFileTypeInstances() methods. + /// + public interface IFileTypeFactory + { + FileType[] GetFileTypeInstances(); + } +} diff --git a/src/Data/InternalFileType.cs b/src/Data/InternalFileType.cs new file mode 100644 index 0000000..0e1f929 --- /dev/null +++ b/src/Data/InternalFileType.cs @@ -0,0 +1,342 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.Serialization; +using System.Text; + +namespace PaintDotNet +{ + public abstract class InternalFileType + : PropertyBasedFileType + { + /// + /// The actual bit-depths we can save with. + /// + internal enum SavableBitDepths + { + Rgba32, // 2^24 colors, plus a full 8-bit alpha channel + Rgb24, // 2^24 colors + Rgb8, // 256 colors + Rgba8 // 255 colors + 1 transparent + } + + protected unsafe void SquishSurfaceTo24Bpp(Surface surface) + { + byte* dst = (byte*)surface.GetRowAddress(0); + int byteWidth = surface.Width * 3; + int stride24bpp = ((byteWidth + 3) / 4) * 4; // round up to multiple of 4 + int delta = stride24bpp - byteWidth; + + for (int y = 0; y < surface.Height; ++y) + { + ColorBgra* src = surface.GetRowAddress(y); + ColorBgra* srcEnd = src + surface.Width; + + while (src < srcEnd) + { + dst[0] = src->B; + dst[1] = src->G; + dst[2] = src->R; + ++src; + dst += 3; + } + + dst += delta; + } + + return; + } + + private string PrintSet(Set set) + { + StringBuilder sb = new StringBuilder(); + + bool first = true; + + foreach (T item in set) + { + if (!first) + { + sb.Append(", "); + } + + first = false; + + sb.Append(item.ToString()); + } + + string sbTS = sb.ToString(); + + return sbTS; + } + + internal SavableBitDepths ChooseBitDepth( + Set allowedBitDepths, + Set losslessBitDepths, + bool allOpaque, + bool all0Or255Alpha, + int uniqueColorCount) + { + if (allowedBitDepths.Count == 0) + { + throw new ArgumentException("Count must be 1 or more", "allowedBitDepths"); + } + + Tracing.Ping("allowedBitDepths = " + PrintSet(allowedBitDepths)); + Tracing.Ping("losslessBitDepths = " + PrintSet(losslessBitDepths)); + + if (allowedBitDepths.Count == 1) + { + return allowedBitDepths.ToArray()[0]; + } + + // allowedBitDepths.Count >= 2 + + Set bestBitDepths = Set.Intersect(allowedBitDepths, losslessBitDepths); + + if (bestBitDepths.Count == 1) + { + return bestBitDepths.ToArray()[0]; + } + + Set candidates; + + if (bestBitDepths.Count == 0) + { + candidates = allowedBitDepths; + } + else + { + candidates = bestBitDepths; + } + + // candidates.Count >= 2 + + // lossless choices + + if (candidates.Contains(SavableBitDepths.Rgba8) && all0Or255Alpha && uniqueColorCount <= 255) + { + return SavableBitDepths.Rgba8; + } + + if (candidates.Contains(SavableBitDepths.Rgb8) && allOpaque && uniqueColorCount <= 256) + { + return SavableBitDepths.Rgb8; + } + + if (candidates.Contains(SavableBitDepths.Rgb24) && allOpaque) + { + return SavableBitDepths.Rgb24; + } + + if (candidates.Contains(SavableBitDepths.Rgba32)) + { + return SavableBitDepths.Rgba32; + } + + // forced choices -- we wanted Rgba32 but it was not allowed + + if (candidates.IsEqualTo(Set.Create(SavableBitDepths.Rgb8, SavableBitDepths.Rgb24))) + { + return SavableBitDepths.Rgb24; + } + + if (candidates.IsEqualTo(Set.Create(SavableBitDepths.Rgb8, SavableBitDepths.Rgba8))) + { + return SavableBitDepths.Rgba8; + } + + if (candidates.IsEqualTo(Set.Create(SavableBitDepths.Rgba8, SavableBitDepths.Rgb24))) + { + return SavableBitDepths.Rgb24; + } + + throw new ArgumentException("Could not accomodate input values -- internal error?"); + } + + protected unsafe Bitmap CreateAliased24BppBitmap(Surface surface) + { + int stride = surface.Width * 3; + int realStride = ((stride + 3) / 4) * 4; // round up to multiple of 4 + return new Bitmap(surface.Width, surface.Height, realStride, PixelFormat.Format24bppRgb, new IntPtr(surface.Scan0.VoidStar)); + } + + private unsafe void Analyze(Surface scratchSurface, out bool allOpaque, out bool all0or255Alpha, out int uniqueColorCount) + { + allOpaque = true; + all0or255Alpha = true; + Set uniqueColors = new Set(); + + for (int y = 0; y < scratchSurface.Height; ++y) + { + ColorBgra* srcPtr = scratchSurface.GetRowAddress(y); + ColorBgra* endPtr = srcPtr + scratchSurface.Width; + + while (srcPtr < endPtr) + { + ColorBgra p = *srcPtr; + + if (p.A != 255) + { + allOpaque = false; + } + + if (p.A > 0 && p.A < 255) + { + all0or255Alpha = false; + } + + if (p.A == 255 && !uniqueColors.Contains(p) && uniqueColors.Count < 300) + { + uniqueColors.Add(*srcPtr); + } + + ++srcPtr; + } + } + + uniqueColorCount = uniqueColors.Count; + } + + internal abstract Set CreateAllowedBitDepthListFromToken(PropertyBasedSaveConfigToken token); + + internal abstract int GetThresholdFromToken(PropertyBasedSaveConfigToken token); + + internal abstract int GetDitherLevelFromToken(PropertyBasedSaveConfigToken token); + + protected unsafe override sealed void OnSaveT( + Document input, + Stream output, + PropertyBasedSaveConfigToken token, + Surface scratchSurface, + ProgressEventHandler progressCallback) + { + // flatten the document -- render w/ transparent background + scratchSurface.Clear(ColorBgra.Transparent); + + using (RenderArgs ra = new RenderArgs(scratchSurface)) + { + input.Render(ra, false); + } + + // load properties from token + int thresholdFromToken = GetThresholdFromToken(token); + int ditherLevel = GetDitherLevelFromToken(token); + + Set allowedBitDepths = CreateAllowedBitDepthListFromToken(token); + + if (allowedBitDepths.Count == 0) + { + throw new ArgumentException("there must be at least 1 element returned from CreateAllowedBitDepthListFromToken()"); + } + + // allowedBitDepths.Count >= 1 + + // set to 1 unless allowedBitDepths contains only Rgb8 and Rgba8 + int threshold; + + if (allowedBitDepths.IsSubsetOf(Set.Create(SavableBitDepths.Rgb8, SavableBitDepths.Rgba8))) + { + threshold = thresholdFromToken; + } + else + { + threshold = 1; + } + + // Analyze image, try to detect what bit-depth or whatever to use, based on allowedBitDepths + bool allOpaque; + bool all0or255Alpha; + int uniqueColorCount; + + Analyze(scratchSurface, out allOpaque, out all0or255Alpha, out uniqueColorCount); + + Set losslessBitDepths = new Set(); + losslessBitDepths.Add(SavableBitDepths.Rgba32); + + if (allOpaque) + { + losslessBitDepths.Add(SavableBitDepths.Rgb24); + + if (uniqueColorCount <= 256) + { + losslessBitDepths.Add(SavableBitDepths.Rgb8); + } + } + else if (all0or255Alpha && uniqueColorCount < 256) + { + losslessBitDepths.Add(SavableBitDepths.Rgba8); + } + + SavableBitDepths bitDepth = ChooseBitDepth(allowedBitDepths, losslessBitDepths, allOpaque, all0or255Alpha, uniqueColorCount); + + if (bitDepth == SavableBitDepths.Rgba8 && threshold == 0 && allowedBitDepths.Contains(SavableBitDepths.Rgba8) && allowedBitDepths.Contains(SavableBitDepths.Rgb8)) + { + // threshold of 0 should effectively force full 256 color palette, instead of 255+1 transparent + bitDepth = SavableBitDepths.Rgb8; + } + + // if bit depth is 24 or 8, then we have to do away with the alpha channel + // for 8-bit, we must have pixels that have either 0 or 255 alpha + if (bitDepth == SavableBitDepths.Rgb8 || + bitDepth == SavableBitDepths.Rgba8 || + bitDepth == SavableBitDepths.Rgb24) + { + UserBlendOps.NormalBlendOp blendOp = new UserBlendOps.NormalBlendOp(); + + for (int y = 0; y < scratchSurface.Height; ++y) + { + for (int x = 0; x < scratchSurface.Width; ++x) + { + ColorBgra p = scratchSurface[x, y]; + + if (p.A < threshold && bitDepth == SavableBitDepths.Rgba8) + { + p = ColorBgra.FromBgra(0, 0, 0, 0); + } + else + { + p = blendOp.Apply(ColorBgra.White, p); + } + + scratchSurface[x, y] = p; + } + } + } + + Tracing.Ping("Chose " + bitDepth + ", ditherLevel=" + ditherLevel + ", threshold=" + threshold); + + // finally, do the save. + FinalSave(input, output, scratchSurface, ditherLevel, bitDepth, token, progressCallback); + } + + internal abstract void FinalSave( + Document input, + Stream output, + Surface scratchSurface, + int ditherLevel, + SavableBitDepths bitDepth, + PropertyBasedSaveConfigToken token, + ProgressEventHandler progressCallback); + + internal InternalFileType(string name, FileTypeFlags flags, string[] extensions) + : base(name, flags, extensions) + { + } + } +} diff --git a/src/Data/JpegFileType.cs b/src/Data/JpegFileType.cs new file mode 100644 index 0000000..7be977d --- /dev/null +++ b/src/Data/JpegFileType.cs @@ -0,0 +1,87 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class JpegFileType + : PropertyBasedFileType + { + public JpegFileType() + : base("JPEG", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving, new string[] { ".jpg", ".jpeg", ".jpe", ".jfif" }) + { + } + + public enum PropertyNames + { + Quality = 0 + } + + public override PropertyCollection OnCreateSavePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Quality, 95, 0, 100)); + + return new PropertyCollection(props); + } + + public override ControlInfo OnCreateSaveConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultSaveConfigUI(props); + + configUI.SetPropertyControlValue( + PropertyNames.Quality, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("JpegFileType.ConfigUI.Quality.DisplayName") ?? "??"); + + return configUI; + } + + protected override void OnSaveT(Document input, Stream output, PropertyBasedSaveConfigToken token, Surface scratchSurface, ProgressEventHandler progressCallback) + { + int quality = token.GetProperty(PropertyNames.Quality).Value; + + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Jpeg); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); + parms.Param[0] = parm; + + scratchSurface.Clear(ColorBgra.White); + + using (RenderArgs ra = new RenderArgs(scratchSurface)) + { + input.Render(ra, false); + } + + using (Bitmap bitmap = scratchSurface.CreateAliasedBitmap()) + { + GdiPlusFileType.LoadProperties(bitmap, input); + bitmap.Save(output, icf, parms); + } + } + + protected override Document OnLoad(Stream input) + { + using (Image image = PdnResources.LoadImage(input)) + { + Document document = Document.FromImage(image); + return document; + } + } + } +} \ No newline at end of file diff --git a/src/Data/Layer.cs b/src/Data/Layer.cs new file mode 100644 index 0000000..6386f06 --- /dev/null +++ b/src/Data/Layer.cs @@ -0,0 +1,660 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Runtime.Serialization; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// A layer's properties are immutable. That is, you can modify the surface + /// of a layer all you want, but to change its dimensions requires creating + /// a new layer. + /// + [Serializable] + public abstract class Layer + : ICloneable, + IDisposable, + IThumbnailProvider + { + private int width; + private int height; + + /// + /// The background layer is generally opaque although it doesn't *have* to be. For + /// example, the Canvas Size action distinguishes between background and non-background + /// layers such that it fills the background layer with opaque and the non-background + /// layers with transparency. + /// The value of this property should not be used to disallow the user from performing + /// an action. + /// + public bool IsBackground + { + get + { + return properties.isBackground; + } + + set + { + bool oldValue = properties.isBackground; + + if (oldValue != value) + { + OnPropertyChanging(LayerProperties.IsBackgroundName); + properties.isBackground = value; + OnPropertyChanged(LayerProperties.IsBackgroundName); + } + } + } + + /// + /// If this value is non-0, then the PropertyChanged event will be + /// suppressed. This is in place so that the Layer Properties dialog + /// can tweak the properties without them filling up the undo stack. + /// + [NonSerialized] + private int suppressPropertyChanges; + + /// + /// Encapsulates the mutable properties of the Layer class. + /// + [Serializable] + internal sealed class LayerProperties + : ICloneable, + ISerializable + { + public string name; + public NameValueCollection userMetaData; + public bool visible; + public bool isBackground; + public byte opacity; + + private const string nameTag = "name"; + private const string userMetaDataTag = "userMetaData"; + private const string visibleTag = "visible"; + private const string isBackgroundTag = "isBackground"; + private const string opacityTag = "opacity"; + + public static string IsBackgroundName + { + get + { + return PdnResources.GetString("Layer.Properties.IsBackground.Name"); + } + } + + public static string NameName + { + get + { + return PdnResources.GetString("Layer.Properties.Name.Name"); + } + } + + public static string VisibleName + { + get + { + return PdnResources.GetString("Layer.Properties.Visible.Name"); + } + } + + public static string OpacityName + { + get + { + return PdnResources.GetString("Layer.Properties.Opacity.Name"); + } + } + + public LayerProperties(string name, NameValueCollection userMetaData, bool visible, bool isBackground, byte opacity) + { + this.name = name; + this.userMetaData = new NameValueCollection(userMetaData); + this.visible = visible; + this.isBackground = isBackground; + this.opacity = opacity; + } + + public LayerProperties(LayerProperties copyMe) + { + this.name = copyMe.name; + this.userMetaData = new NameValueCollection(copyMe.userMetaData); + this.visible = copyMe.visible; + this.isBackground = copyMe.isBackground; + this.opacity = copyMe.opacity; + } + + public object Clone() + { + return new LayerProperties(this); + } + + public LayerProperties(SerializationInfo info, StreamingContext context) + { + this.name = info.GetString(nameTag); + this.userMetaData = (NameValueCollection)info.GetValue(userMetaDataTag, typeof(NameValueCollection)); + this.visible = info.GetBoolean(visibleTag); + this.isBackground = info.GetBoolean(isBackgroundTag); + + // This property was added with v2.1. So as to allow loading old .PDN files, + // this is an optional item. + // (Historical note: this property was actually moved from the BitmapLayer + // properties to the base class because it was found to be a rather important + // property for rendering regardless of layer "type") + try + { + this.opacity = info.GetByte(opacityTag); + } + + catch (SerializationException) + { + this.opacity = 255; + } + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameTag, name); + info.AddValue(userMetaDataTag, userMetaData); + info.AddValue(visibleTag, visible); + info.AddValue(isBackgroundTag, isBackground); + info.AddValue(opacityTag, opacity); + } + } + + private LayerProperties properties; + + public byte Opacity + { + get + { + if (disposed) + { + throw new ObjectDisposedException("Layer"); + } + + return properties.opacity; + } + + set + { + if (disposed) + { + throw new ObjectDisposedException("Layer"); + } + + if (properties.opacity != value) + { + OnPropertyChanging(LayerProperties.OpacityName); + properties.opacity = value; + OnPropertyChanged(LayerProperties.OpacityName); + Invalidate(); + } + } + } + + /// + /// Allows you to save the mutable properties of the layer so you can restore them later + /// (esp. important for undo!). Mutable properties include the layer's name, whether it's + /// visible, and the metadata. This list might expand later. + /// + /// + /// An object that can be used later in a call to LoadProperties. + /// + /// + /// It is important that derived classes call this in the correct fashion so as to 'chain' + /// the properties list together. The following is the correct pattern: + /// + /// public override object SaveProperties() + /// { + /// object baseProperties = base.SaveProperties(); + /// return new List(properties.Clone(), new List(baseProperties, null)); + /// } + /// + public virtual object SaveProperties() + { + return properties.Clone(); + } + + public void LoadProperties(object oldState) + { + LoadProperties(oldState, false); + } + + public virtual void LoadProperties(object oldState, bool suppressEvents) + { + LayerProperties lp = (LayerProperties)oldState; + List changed = new List(); + + if (!suppressEvents) + { + if (lp.name != properties.name) + { + changed.Add(LayerProperties.NameName); + } + + if (lp.isBackground != properties.isBackground) + { + changed.Add(LayerProperties.IsBackgroundName); + } + + if (lp.visible != properties.visible) + { + changed.Add(LayerProperties.VisibleName); + } + + if (lp.opacity != properties.opacity) + { + changed.Add(LayerProperties.OpacityName); + } + } + + foreach (string propertyName in changed) + { + OnPropertyChanging(propertyName); + } + + properties = (LayerProperties)((LayerProperties)oldState).Clone(); + + Invalidate(); + + foreach (string propertyName in changed) + { + OnPropertyChanged(propertyName); + } + } + + public int Width + { + get + { + return width; + } + } + + public int Height + { + get + { + return height; + } + } + + public Size Size + { + get + { + return new Size(Width, Height); + } + } + + public Rectangle Bounds + { + get + { + return new Rectangle(new Point(0, 0), Size); + } + } + + public void PushSuppressPropertyChanged() + { + Interlocked.Increment(ref suppressPropertyChanges); + } + + public void PopSuppressPropertyChanged() + { + if (0 > Interlocked.Decrement(ref suppressPropertyChanges)) + { + throw new InvalidProgramException("suppressPreviewChanged is less than zero"); + } + } + + [field: NonSerialized] + public event EventHandler PreviewChanged; + + protected virtual void OnPreviewChanged() + { + if (PreviewChanged != null) + { + PreviewChanged(this, EventArgs.Empty); + } + } + + /// + /// This event is raised before a property is changed. Note that the name given + /// in the PropertyEventArgs is for descriptive (UI) purposes only and serves no + /// programmatic purpose. When this event is raised you should not make any + /// assumptions about which property was changed based on this description. + /// + [field: NonSerialized] + public event PropertyEventHandler PropertyChanging; + + protected virtual void OnPropertyChanging(string propertyName) + { + if (this.suppressPropertyChanges == 0) + { + if (PropertyChanging != null) + { + PropertyChanging(this, new PropertyEventArgs(propertyName)); + } + } + } + + /// + /// This event is raised after a property is changed. Note that the name given + /// in the PropertyEventArgs is for descriptive (UI) purposes only and serves no + /// programmatic purpose. When this event is raised you should not make any + /// assumptions about which property was changed based on this description. + /// + [field: NonSerialized] + public event PropertyEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged(string propertyName) + { + if (this.suppressPropertyChanges == 0) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyEventArgs(propertyName)); + } + } + } + + /// + /// You can call this to raise the PropertyChanged event. Note that is will + /// raise the event with an empty string for the property name description. + /// Thus it is useful only for syncing up UI elements that require notification + /// of events but that otherwise don't really track it. + /// + public void PerformPropertyChanged() + { + OnPropertyChanged(string.Empty); + } + + /// + /// A user-definable name. + /// + public string Name + { + get + { + return properties.name; + } + + set + { + if (properties.name != value) + { + OnPropertyChanging(LayerProperties.NameName); + properties.name = value; + OnPropertyChanged(LayerProperties.NameName); + } + } + } + + [NonSerialized] + private Metadata metadata; + + public Metadata Metadata + { + get + { + if (metadata == null) + { + metadata = new Metadata(properties.userMetaData); + } + + return metadata; + } + } + + /// + /// Determines whether the layer is part of a document's composition. If this + /// property is false, the composition engine will ignore this layer. + /// + public bool Visible + { + get + { + return properties.visible; + } + + set + { + bool oldValue = properties.visible; + + if (oldValue != value) + { + OnPropertyChanging(LayerProperties.VisibleName); + properties.visible = value; + OnPropertyChanged(LayerProperties.VisibleName); + Invalidate(); + } + } + } + + /// + /// Determines whether a rectangle is fully in bounds or not. This is determined by checking + /// to make sure the left, top, right, and bottom edges are within bounds. + /// + /// + /// + private bool IsInBounds(Rectangle roi) + { + if (roi.Left < 0 || roi.Top < 0 || roi.Left >= Width || roi.Top >= Height || + roi.Right > Width || roi.Bottom > Height) + { + return false; + } + + return true; + } + + /// + /// Implements IThumbnailProvider.RenderThumbnail(). + /// + public abstract Surface RenderThumbnail(int maxEdgeLength); + + /// + /// Causes the layer to render a given rectangle of interest (roi) to the given destination surface. + /// + /// Contains information about which objects to use for rendering + /// The rectangular region to be rendered. + public void Render(RenderArgs args, Rectangle roi) + { + // the bitmap we're rendering to must match the size of the layer we're rendering from + if (args.Surface.Width != Width || args.Surface.Height != Height) + { + throw new ArgumentException(); + } + + // the region of interest can not be out of bounds! + if (!IsInBounds(roi)) + { + throw new ArgumentOutOfRangeException("roi"); + } + + RenderImpl(args, roi); + } + + /// + /// Causes the layer to render a given region of interest (roi) to the given destination surface. + /// + /// Contains information about which objects to use for rendering + /// The region to be rendered. + public void Render(RenderArgs args, PdnRegion roi) + { + Rectangle roiBounds = roi.GetBoundsInt(); + + if (!IsInBounds(roiBounds)) + { + throw new ArgumentOutOfRangeException("roi"); + } + + Rectangle[] rects = roi.GetRegionScansReadOnlyInt(); + RenderImpl(args, rects); + } + + public void RenderUnchecked(RenderArgs args, Rectangle[] roi, int startIndex, int length) + { + RenderImpl(args, roi, startIndex, length); + } + + /// + /// Override this method to provide your layer's rendering capabilities. + /// + /// Contains information about which objects to use for rendering + /// The rectangular region to be rendered. + protected abstract void RenderImpl(RenderArgs args, Rectangle roi); + + protected void RenderImpl(RenderArgs args, Rectangle[] roi) + { + RenderImpl(args, roi, 0, roi.Length); + } + + protected virtual void RenderImpl(RenderArgs args, Rectangle[] roi, int startIndex, int length) + { + for (int i = startIndex; i < startIndex + length; ++i) + { + RenderImpl(args, roi[i]); + } + } + + [field: NonSerialized] + public event InvalidateEventHandler Invalidated; + + protected virtual void OnInvalidated(InvalidateEventArgs e) + { + if (Invalidated != null) + { + Invalidated(this, e); + } + } + + /// + /// Causes the entire layer surface to be invalidated. + /// + public void Invalidate() + { + Rectangle rect = new Rectangle(0, 0, Width, Height); + OnInvalidated(new InvalidateEventArgs(rect)); + } + + /// + /// Causes a portion of the layer surface to be invalidated. + /// + /// The region of interest to be invalidated. + public void Invalidate(PdnRegion roi) + { + foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt()) + { + Invalidate(rect); + } + } + + /// + /// Causes a portion of the layer surface to be invalidated. + /// + /// The region of interest to be invalidated. + public void Invalidate(RectangleF[] roi) + { + foreach (RectangleF rectF in roi) + { + Invalidate(Rectangle.Truncate(rectF)); + } + } + + /// + /// Causes a portion of the layer surface to be invalidated. + /// + /// The rectangle of interest to be invalidated. + public void Invalidate(Rectangle roi) + { + Rectangle rect = Rectangle.Intersect(roi, this.Bounds); + // TODO: this is horrible for performance w.r.t. complex invalidation regions. Lots of heap pollution. + // fix that! + OnInvalidated(new InvalidateEventArgs(rect)); + } + + public Layer(int width, int height) + { + this.width = width; + this.height = height; + this.properties = new LayerProperties(null, new NameValueCollection(), true, false, 255); + } + + protected Layer(Layer copyMe) + { + this.width = copyMe.width; + this.height = copyMe.height; + this.properties = (LayerProperties)copyMe.properties.Clone(); + } + + // TODO: add "name" parameter, keep this for legacy and fill it in with "Background" + // goal is to put complete burden of loc on the client + public static BitmapLayer CreateBackgroundLayer(int width, int height) + { + // set colors to 0xffffffff + // note: we use alpha of 255 here so that "invert colors" works as expected + // that is, for just 1 layer we invert the initial white->black + // but on subsequent layers we invert transparent white -> transparent black, which shows up as white for the most part + BitmapLayer layer = new BitmapLayer(width, height, ColorBgra.White); + + layer.Name = PdnResources.GetString("Layer.Background.Name"); + + // tag it as a background layer + layer.properties.isBackground = true; + + return layer; + } + + /// + /// This allows a layer to provide a dialog for configuring + /// the layer's properties. + /// + public abstract PdnBaseForm CreateConfigDialog(); + + public abstract object Clone(); + + ~Layer() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool disposed = false; + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + + if (disposing) + { + } + } + } + } +} diff --git a/src/Data/LayerList.cs b/src/Data/LayerList.cs new file mode 100644 index 0000000..ad32ddb --- /dev/null +++ b/src/Data/LayerList.cs @@ -0,0 +1,284 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; + +namespace PaintDotNet +{ + // TODO: reimplement to not use ArrayList, although we'll need to keep this class around + // for compatibility with .PDN's saved with older versions + /// + /// Basically an ArrayList, but lets the containing Document instance be + /// notified when the list is modified so it can know that it needs to + /// re-render itself. + /// This implementation also enforces that any contained layer must be + /// of the same dimensions as the document it is contained within. + /// If you try to add a layer that is the wrong size, an exception will + /// be thrown. + /// + [Serializable] + public sealed class LayerList + : ArrayList + { + private Document parent; + + /// + /// Defines a generic "the collection is changing" event + /// This is always followed with a more specific event (RemovedAt, for instance). + /// + [field: NonSerialized] + public event EventHandler Changing; + + /// + /// Defines a generic "the collection's contents have changed" event + /// This is always preceded by a more specific event. + /// + [field: NonSerialized] + public event EventHandler Changed; + + /// + /// This event is raised after the collection has been cleared out; + /// thus, when you handle this event the collection is empty. + /// + [field: NonSerialized] + public EventHandler Cleared; + + /// + /// This event is raised when a new element is inserted into the collection. + /// The new element is at the array index specified by the Index property + /// of the IndexEventArgs. + /// + [field: NonSerialized] + public event IndexEventHandler Inserted; + + /// + /// This event is raised before an element is removed from the collection. + /// The index specified by the Index property of the IndexEventArgs is where + /// the element currently is. + /// + [field: NonSerialized] + public event IndexEventHandler RemovingAt; + + /// + /// This event is raised when an element is removed from the collection. + /// The index specified by the Index property of the IndexEventArgs is where + /// the element used to be. + /// + [field: NonSerialized] + public event IndexEventHandler RemovedAt; + + private void OnRemovingAt(int index) + { + if (RemovingAt != null) + { + RemovingAt(this, new IndexEventArgs(index)); + } + } + + private void OnRemovedAt(int index) + { + if (RemovedAt != null) + { + RemovedAt(this, new IndexEventArgs(index)); + } + } + + private void OnInserted(int index) + { + if (Inserted != null) + { + Inserted(this, new IndexEventArgs(index)); + } + } + + private void OnCleared() + { + if (Cleared != null) + { + Cleared(this, EventArgs.Empty); + } + } + + private void OnChanging() + { + if (Changing != null) + { + Changing(this, EventArgs.Empty); + } + } + + private void OnChanged() + { + if (Changed != null) + { + Changed(this, EventArgs.Empty); + } + } + + public LayerList(Document parent) + { + this.parent = parent; + } + + private void CheckLayerSize(object value) + { + Layer layer = (Layer)value; + + if (layer.Width != parent.Width || layer.Height != parent.Height) + { + throw new ArgumentException("Size of layer does not match size of containing document"); + } + } + + public override int Add(object value) + { + if (!(value is BitmapLayer)) + { + throw new ArgumentException("can only add bitmap layers"); + } + + OnChanging(); + CheckLayerSize(value); + parent.Invalidate(); // TODO: is this necessary? shouldn't Document just hook in to the Inserted event? + int index = base.Add(value); + OnInserted(index); + OnChanged(); + return index; + } + + public override void AddRange(ICollection c) + { + // Implemented using Add(), and thus we don't raise our own events + foreach (object o in c) + { + Add(o); + } + } + + public override void Clear() + { + OnChanging(); + base.Clear(); + OnCleared(); + OnChanged(); + } + + public override void Insert(int index, object value) + { + OnChanging(); + CheckLayerSize(value); + parent.Invalidate(); // TODO: is this necessary? shouldn't Document just hook in to the Inserted event? + base.Insert(index, value); + OnInserted(index); + OnChanged(); + } + + public override void InsertRange(int index, ICollection c) + { + // implemented using Insert, thus we don't raise our own events + foreach (object o in c) + { + Insert(index, o); + } + } + + /* + // Undocumented behavior of ArrayList: ArrayList.Remove actually uses ArrayList.RemoveAt! + public override void Remove(object obj) + { + //OnChanging(); + int index = IndexOf(obj); + RemoveAt(index); + //base.Remove (obj); + //OnRemovedAt(index); + //OnChanged(); + } + */ + + public override void RemoveAt(int index) + { + OnChanging(); + OnRemovingAt(index); + base.RemoveAt(index); + OnRemovedAt(index); + OnChanged(); + } + + public override void RemoveRange(int index, int count) + { + // Implemented by calling RemoveAt, thus we don't raise our own events + while (count > 0) + { + RemoveAt(index); + } + } + + public override void Reverse() + { + throw new NotSupportedException(); + } + + public override void Reverse(int index, int count) + { + throw new NotSupportedException(); + } + + public override void SetRange(int index, ICollection c) + { + throw new NotSupportedException(); + } + + public override void Sort() + { + throw new NotSupportedException(); + } + + public override void Sort(int index, int count, IComparer comparer) + { + throw new NotSupportedException(); + } + + public override void Sort(IComparer comparer) + { + throw new NotSupportedException(); + } + + public override void TrimToSize() + { + throw new NotSupportedException(); + } + + public Layer GetAt(int index) + { + return (Layer)this[index]; + } + + public void SetAt(int index, Layer newValue) + { + this[index] = newValue; + } + + public override object this[int index] + { + get + { + return base[index]; + } + + set + { + OnChanging(); + RemoveAt(index); + Insert(index, value); + OnChanged(); + } + } + } +} diff --git a/src/Data/LayerPropertiesDialog.cs b/src/Data/LayerPropertiesDialog.cs new file mode 100644 index 0000000..bee4421 --- /dev/null +++ b/src/Data/LayerPropertiesDialog.cs @@ -0,0 +1,256 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public class LayerPropertiesDialog + : PdnBaseForm + { + protected System.Windows.Forms.CheckBox visibleCheckBox; + protected System.Windows.Forms.Label nameLabel; + protected System.Windows.Forms.TextBox nameBox; + protected System.Windows.Forms.Button cancelButton; + protected System.Windows.Forms.Button okButton; + private System.ComponentModel.Container components = null; + private object originalProperties = null; + private PaintDotNet.HeaderLabel generalHeader; + + private Layer layer; + + [Browsable(false)] + public Layer Layer + { + get + { + return layer; + } + + set + { + this.layer = value; + this.originalProperties = this.layer.SaveProperties(); + InitDialogFromLayer(); + } + } + + protected virtual void InitLayerFromDialog() + { + this.layer.Name = this.nameBox.Text; + this.layer.Visible = this.visibleCheckBox.Checked; + + if (this.Owner != null) + { + this.Owner.Update(); + } + } + + protected virtual void InitDialogFromLayer() + { + this.nameBox.Text = this.layer.Name; + this.visibleCheckBox.Checked = this.layer.Visible; + } + + public LayerPropertiesDialog() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.Icon = Utility.ImageToIcon(PdnResources.GetImage("Icons.MenuLayersLayerPropertiesIcon.png"), Color.FromArgb(192, 192, 192)); + + this.Text = PdnResources.GetString("LayerPropertiesDialog.Text"); + this.visibleCheckBox.Text = PdnResources.GetString("LayerPropertiesDialog.VisibleCheckBox.Text"); + this.nameLabel.Text = PdnResources.GetString("LayerPropertiesDialog.NameLabel.Text"); + this.generalHeader.Text = PdnResources.GetString("LayerPropertiesDialog.GeneralHeader.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + protected override void OnLoad(EventArgs e) + { + this.nameBox.Select(); + this.nameBox.Select(0, this.nameBox.Text.Length); + base.OnLoad(e); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.visibleCheckBox = new System.Windows.Forms.CheckBox(); + this.nameBox = new System.Windows.Forms.TextBox(); + this.nameLabel = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.generalHeader = new PaintDotNet.HeaderLabel(); + this.SuspendLayout(); + // + // visibleCheckBox + // + this.visibleCheckBox.Location = new System.Drawing.Point(14, 43); + this.visibleCheckBox.Name = "visibleCheckBox"; + this.visibleCheckBox.Size = new System.Drawing.Size(90, 16); + this.visibleCheckBox.TabIndex = 3; + this.visibleCheckBox.TextAlign = System.Drawing.ContentAlignment.TopLeft; + this.visibleCheckBox.FlatStyle = FlatStyle.System; + this.visibleCheckBox.CheckedChanged += new System.EventHandler(this.VisibleCheckBox_CheckedChanged); + // + // nameBox + // + this.nameBox.Location = new System.Drawing.Point(64, 24); + this.nameBox.Name = "nameBox"; + this.nameBox.Size = new System.Drawing.Size(200, 20); + this.nameBox.TabIndex = 2; + this.nameBox.Text = ""; + this.nameBox.Enter += new System.EventHandler(this.NameBox_Enter); + // + // nameLabel + // + this.nameLabel.Location = new System.Drawing.Point(6, 24); + this.nameLabel.Name = "nameLabel"; + this.nameLabel.Size = new System.Drawing.Size(50, 16); + this.nameLabel.TabIndex = 2; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(194, 69); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 1; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.Location = new System.Drawing.Point(114, 69); + this.okButton.Name = "okButton"; + this.okButton.TabIndex = 0; + this.okButton.FlatStyle = FlatStyle.System; + this.okButton.Click += new System.EventHandler(this.OkButton_Click); + // + // generalHeader + // + this.generalHeader.Location = new System.Drawing.Point(6, 8); + this.generalHeader.Name = "generalHeader"; + this.generalHeader.Size = new System.Drawing.Size(269, 14); + this.generalHeader.TabIndex = 4; + this.generalHeader.TabStop = false; + // + // LayerPropertiesDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(274, 96); + this.ControlBox = true; + this.Controls.Add(this.generalHeader); + this.Controls.Add(this.okButton); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.nameBox); + this.Controls.Add(this.visibleCheckBox); + this.Controls.Add(this.nameLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "LayerPropertiesDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Controls.SetChildIndex(this.nameLabel, 0); + this.Controls.SetChildIndex(this.visibleCheckBox, 0); + this.Controls.SetChildIndex(this.nameBox, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.generalHeader, 0); + this.ResumeLayout(false); + + } + #endregion + + private void NameBox_Enter(object sender, System.EventArgs e) + { + this.nameBox.Select(0, nameBox.Text.Length); + } + + private void OkButton_Click(object sender, System.EventArgs e) + { + DialogResult = DialogResult.OK; + + using (new WaitCursorChanger(this)) + { + this.layer.PushSuppressPropertyChanged(); + InitLayerFromDialog(); + object currentProperties = this.layer.SaveProperties(); + this.layer.LoadProperties(this.originalProperties); + this.layer.PopSuppressPropertyChanged(); + + this.layer.LoadProperties(currentProperties); + this.originalProperties = layer.SaveProperties(); + //layer.Invalidate(); // no need to call Invalidate() -- it will be called by OnClosed() + } + + Close(); + } + + private void CancelButton_Click(object sender, System.EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + protected override void OnClosed(EventArgs e) + { + using (new WaitCursorChanger(this)) + { + this.layer.PushSuppressPropertyChanged(); + this.layer.LoadProperties(this.originalProperties); + this.layer.PopSuppressPropertyChanged(); + this.layer.Invalidate(); + } + + base.OnClosed(e); + } + + private void VisibleCheckBox_CheckedChanged(object sender, System.EventArgs e) + { + Layer.PushSuppressPropertyChanged(); + Layer.Visible = visibleCheckBox.Checked; + Layer.PopSuppressPropertyChanged(); + } + } +} diff --git a/src/Data/LayerPropertiesDialog.resx b/src/Data/LayerPropertiesDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Data/MetaData.cs b/src/Data/MetaData.cs new file mode 100644 index 0000000..55e82a8 --- /dev/null +++ b/src/Data/MetaData.cs @@ -0,0 +1,400 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Specialized; +using System.Drawing.Imaging; +using System.Text; + +namespace PaintDotNet +{ + /// + /// This class exposes two types of metadata: system, and user. + /// It is provided mostly for batching operations: loading all the data, modifying the copy, + /// and then saving back all the data. + /// + public class Metadata + { + /// + /// This is the name of the section where EXIF tags are stored. + /// + /// + /// All entries in here are expected to be PropertyItem objects which were serialized + /// using PdnGraphics.SerializePropertyItem. The name of each entry in this section is + /// irrelevant, as some EXIF tags are allowed to occur more than once. Thus, if you + /// want to search for EXIF tags of a certain ID you will have to deserialize each + /// one and compare the Id property. + /// It is the responsibility of the FileType implementation to load and save these. + /// + public const string ExifSectionName = "$exif"; + + /// + /// This is the name of the section where user-defined metadata may go. + /// + public const string UserSectionName = "$user"; + + /// + /// This is the name of the section where the main document metadata goes that + /// can be user-provided but is not necessarily user-defined. + /// + public const string MainSectionName = "$main"; + + private NameValueCollection userMetaData; + private const string sectionSeparator = "."; + + private int suppressChangeEvents = 0; + + public event EventHandler Changing; + protected virtual void OnChanging() + { + if (suppressChangeEvents <= 0 && Changing != null) + { + Changing(this, EventArgs.Empty); + } + } + + public event EventHandler Changed; + protected virtual void OnChanged() + { + if (suppressChangeEvents <= 0 && Changed != null) + { + Changed(this, EventArgs.Empty); + } + } + + private class ExifInfo + { + public string[] names; + public PropertyItem[] items; + + public ExifInfo(string[] names, PropertyItem[] items) + { + this.names = names; + this.items = items; + } + } + + private Hashtable exifIdToExifInfo = new Hashtable(); // maps short -> ExifInfo + + public string[] GetKeys(string section) + { + string sectionName = section + sectionSeparator; + ArrayList keys = new ArrayList(); + + foreach (string key in userMetaData.Keys) + { + if (key.StartsWith(sectionName)) + { + keys.Add(key.Substring(sectionName.Length)); + } + } + + return (string[])keys.ToArray(typeof(string)); + } + + public string[] GetSections() + { + Set sections = new Set(); + + foreach (string key in userMetaData.Keys) + { + int dotIndex = key.IndexOf(sectionSeparator); + + if (dotIndex != -1) + { + string sectionName = key.Substring(0, dotIndex); + + if (!sections.Contains(sectionName)) + { + sections.Add(sectionName); + } + } + } + + return (string[])sections.ToArray(typeof(string)); + } + + /// + /// Gets a value from the metadata collection. + /// + /// The logical section to retrieve from. + /// The name of the value to retrieve. + /// A string containing the value, or null if the value wasn't present. + public string GetValue(string section, string name) + { + return userMetaData.Get(section + sectionSeparator + name); + } + + public PropertyItem[] GetExifValues(ExifTagID id) + { + return GetExifValues((short)id); + } + + public PropertyItem[] GetExifValues(short id) + { + ExifInfo info = (ExifInfo)this.exifIdToExifInfo[id]; + + if (info == null) + { + return new PropertyItem[0]; + } + else + { + return (PropertyItem[])info.items.Clone(); + } + } + + public string GetUserValue(string name) + { + return GetValue(UserSectionName, name); + } + + /// + /// Removes a value from the metadata collection. + /// + /// The logical section to remove from. + /// The name of the value to retrieve. + public void RemoveValue(string section, string name) + { + OnChanging(); + userMetaData.Remove(section + sectionSeparator + name); + OnChanged(); + } + + public void ReplaceExifValues(ExifTagID id, PropertyItem[] items) + { + ReplaceExifValues((short)id, items); + } + + public void ReplaceExifValues(short id, PropertyItem[] items) + { + OnChanging(); + ++suppressChangeEvents; + RemoveExifValues(id); + AddExifValues(items); + --suppressChangeEvents; + OnChanged(); + } + + public void RemoveExifValues(ExifTagID id) + { + RemoveExifValues((short)id); + } + + public void RemoveExifValues(short id) + { + object idObj = (object)id; + ExifInfo info = (ExifInfo)this.exifIdToExifInfo[idObj]; + + OnChanging(); + ++suppressChangeEvents; + + if (info != null) + { + foreach (string name in info.names) + { + RemoveValue(ExifSectionName, name); + } + + this.exifIdToExifInfo.Remove(idObj); + } + + --suppressChangeEvents; + OnChanged(); + } + + public void RemoveUserValue(string name) + { + RemoveValue(UserSectionName, name); + } + + private void SetValueConcrete(string section, string name, string value) + { + OnChanging(); + userMetaData.Set(section + sectionSeparator + name, value); + OnChanged(); + } + + /// + /// Sets a value in the metadata collection. + /// + /// The logical section to add or update date in. + /// The name of the value to set. + /// The value to set. + public void SetValue(string section, string name, string value) + { + if (section == ExifSectionName) + { + throw new ArgumentException("you must use AddExifValues() to add items to the " + ExifSectionName + " section"); + } + + SetValueConcrete(section, name, value); + } + + public void SetUserValue(string name, string value) + { + SetValue(Metadata.UserSectionName, name, value); + } + + public void AddExifValues(PropertyItem[] items) + { + if (items.Length == 0) + { + return; + } + + short id = unchecked((short)items[0].Id); + + for (int i = 1; i < items.Length; ++i) + { + if (unchecked((short)items[i].Id) != id) + { + throw new ArgumentException("all PropertyItem instances in items must have the same id"); + } + } + + string[] names = new string[items.Length]; + + OnChanging(); + ++suppressChangeEvents; + + for (int i = 0; i < items.Length; ++i) + { + names[i] = GetUniqueExifName(); + string blob = PdnGraphics.SerializePropertyItem(items[i]); + SetValueConcrete(ExifSectionName, names[i], blob); + } + + object idObj = (object)id; // avoid boxing twice + ExifInfo info = (ExifInfo)this.exifIdToExifInfo[idObj]; + + if (info == null) + { + PropertyItem[] newItems = new PropertyItem[items.Length]; + + for (int i = 0; i < newItems.Length; ++i) + { + newItems[i] = PdnGraphics.ClonePropertyItem(items[i]); + } + + info = new ExifInfo(names, newItems); + } + else + { + string[] names2 = new string[info.names.Length + names.Length]; + PropertyItem[] items2 = new PropertyItem[info.items.Length + items.Length]; + + info.names.CopyTo(names2, 0); + names.CopyTo(names2, info.names.Length); + + info.items.CopyTo(items2, 0); + + for (int i = 0; i < items.Length; ++i) + { + items2[i + info.items.Length] = PdnGraphics.ClonePropertyItem(items[i]); + } + + info = new ExifInfo(names2, items2); + } + + this.exifIdToExifInfo[idObj] = info; + + --suppressChangeEvents; + OnChanged(); + } + + private int nextUniqueId = 0; + private string GetUniqueExifName() + { + int num = nextUniqueId; + const string namePrefix = "tag"; + + while (true) + { + string name = namePrefix + num.ToString(); + + if (GetValue(ExifSectionName, name) == null) + { + nextUniqueId = num + 1; + return name; + } + else + { + ++num; + } + } + } + + public void ReplaceWithDataFrom(Metadata source) + { + OnChanging(); + ++suppressChangeEvents; + + if (source != this && source.userMetaData != this.userMetaData) + { + Clear(); + + foreach (string key in source.userMetaData.Keys) + { + string value = source.userMetaData.Get(key); + this.userMetaData.Set(key, value); + } + + ReconstructExifInfoCache(); + } + + --suppressChangeEvents; + OnChanged(); + } + + public void Clear() + { + OnChanging(); + ++suppressChangeEvents; + this.userMetaData.Clear(); + this.exifIdToExifInfo.Clear(); + --suppressChangeEvents; + OnChanged(); + } + + private void ReconstructExifInfoCache() + { + OnChanging(); + ++suppressChangeEvents; + + exifIdToExifInfo.Clear(); + + string[] exifKeys = GetKeys(ExifSectionName); + string[] piBlobs = new string[exifKeys.Length]; + + for (int i = 0; i < exifKeys.Length; ++i) + { + piBlobs[i] = GetValue(ExifSectionName, exifKeys[i]); + this.RemoveValue(ExifSectionName, exifKeys[i]); + } + + foreach (string piBlob in piBlobs) + { + PropertyItem pi = PdnGraphics.DeserializePropertyItem(piBlob); + AddExifValues(new PropertyItem[] { pi }); + } + + --suppressChangeEvents; + OnChanged(); + } + + internal Metadata(NameValueCollection userMetaData) + { + this.userMetaData = userMetaData; + ReconstructExifInfoCache(); + } + } +} diff --git a/src/Data/NoSaveConfigWidget.cs b/src/Data/NoSaveConfigWidget.cs new file mode 100644 index 0000000..030d2ad --- /dev/null +++ b/src/Data/NoSaveConfigWidget.cs @@ -0,0 +1,78 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class NoSaveConfigWidget + : SaveConfigWidget + { + private System.Windows.Forms.Label label1; + private System.ComponentModel.IContainer components = null; + + public NoSaveConfigWidget() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Location = new System.Drawing.Point(24, 32); + this.label1.Name = "label1"; + this.label1.TabIndex = 0; + this.label1.Text = "(No Settings)"; + // + // NoSaveConfigWidget + // + this.Controls.Add(this.label1); + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.Name = "NoSaveConfigWidget"; + this.Size = new System.Drawing.Size(152, 88); + this.ResumeLayout(false); + + } + #endregion + } +} + diff --git a/src/Data/NoSaveConfigWidget.resx b/src/Data/NoSaveConfigWidget.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Data/PdnFileType.cs b/src/Data/PdnFileType.cs new file mode 100644 index 0000000..dea26d9 --- /dev/null +++ b/src/Data/PdnFileType.cs @@ -0,0 +1,84 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.IO; + +namespace PaintDotNet +{ + public sealed class PdnFileType + : FileType + { + public PdnFileType() + : base(PdnInfo.GetBareProductName(), + FileTypeFlags.SavesWithProgress | + FileTypeFlags.SupportsCustomHeaders | + FileTypeFlags.SupportsLayers | + FileTypeFlags.SupportsLoading | + FileTypeFlags.SupportsSaving, + new string[] { ".pdn" }) + { + } + + protected override Document OnLoad(Stream input) + { + return Document.FromStream(input); + } + + protected override void OnSave(Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback) + { + if (callback == null) + { + input.SaveToStream(output); + } + else + { + UpdateProgressTranslator upt = new UpdateProgressTranslator(ApproximateMaxOutputOffset(input), callback); + input.SaveToStream(output, new IOEventHandler(upt.IOEventHandler)); + } + } + + public override bool IsReflexive(SaveConfigToken token) + { + return true; + } + + private sealed class UpdateProgressTranslator + { + private long maxBytes; + private long totalBytes; + private ProgressEventHandler callback; + + public void IOEventHandler(object sender, IOEventArgs e) + { + double percent; + + lock (this) + { + totalBytes += (long)e.Count; + percent = Math.Max(0.0, Math.Min(100.0, ((double)totalBytes * 100.0) / (double)maxBytes)); + } + + callback(sender, new ProgressEventArgs(percent)); + } + + public UpdateProgressTranslator(long maxBytes, ProgressEventHandler callback) + { + this.maxBytes = maxBytes; + this.callback = callback; + this.totalBytes = 0; + } + } + + private long ApproximateMaxOutputOffset(Document measureMe) + { + return (long)measureMe.Layers.Count * (long)measureMe.Width * (long)measureMe.Height * (long)ColorBgra.SizeOf; + } + } +} diff --git a/src/Data/PdnFileTypes.cs b/src/Data/PdnFileTypes.cs new file mode 100644 index 0000000..c0a9f7d --- /dev/null +++ b/src/Data/PdnFileTypes.cs @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Data; +using System; +using System.Drawing; +using System.Drawing.Imaging; + +namespace PaintDotNet +{ + /// + /// This is the default Paint.NET FileTypeFactory. It provides all the built-in FileTypes. + /// + public class PdnFileTypes + : IFileTypeFactory + { + public static readonly FileType Bmp = new BmpFileType(); + public static readonly FileType Jpeg = new JpegFileType(); + public static readonly FileType Gif = new GifFileType(); + public static readonly FileType Tiff = new GdiPlusFileType("TIFF", ImageFormat.Tiff, false, new string[] { ".tif", ".tiff" }); + public static readonly PngFileType Png = new PngFileType(); + public static readonly FileType Pdn = new PdnFileType(); + public static readonly FileType Tga = new TgaFileType(); + + private static FileType[] fileTypes = new FileType[] { + Pdn, + Bmp, + Gif, + Jpeg, + Png, + Tiff, + Tga + }; + + internal FileTypeCollection GetFileTypeCollection() + { + return new FileTypeCollection(fileTypes); + } + + public FileType[] GetFileTypeInstances() + { + return (FileType[])fileTypes.Clone(); + } + } +} diff --git a/src/Data/PngFileType.cs b/src/Data/PngFileType.cs new file mode 100644 index 0000000..e5bb2e6 --- /dev/null +++ b/src/Data/PngFileType.cs @@ -0,0 +1,240 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.Serialization; +using System.Text; + +namespace PaintDotNet +{ + public sealed class PngFileType + : InternalFileType + { + protected override bool IsReflexive(PropertyBasedSaveConfigToken token) + { + PngBitDepthUIChoices bitDepth = (PngBitDepthUIChoices)token.GetProperty(PropertyNames.BitDepth).Value; + + // Only 32-bit is reflexive + return (bitDepth == PngBitDepthUIChoices.Bpp32); + } + + public PngFileType() + : base("PNG", FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving, new string[] { ".png" }) + { + } + + public enum PropertyNames + { + BitDepth = 0, + DitherLevel = 1, + Threshold = 2 + } + + public enum PngBitDepthUIChoices + { + AutoDetect = 0, + Bpp32 = 1, + Bpp24 = 2, + Bpp8 = 3 + } + + public override ControlInfo OnCreateSaveConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultSaveConfigUI(props); + + configUI.SetPropertyControlValue( + PropertyNames.BitDepth, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("PngFileType.ConfigUI.BitDepth.DisplayName")); + + PropertyControlInfo bitDepthPCI = configUI.FindControlForPropertyName(PropertyNames.BitDepth); + bitDepthPCI.SetValueDisplayName(PngBitDepthUIChoices.AutoDetect, PdnResources.GetString("PngFileType.ConfigUI.BitDepth.AutoDetect.DisplayName")); + bitDepthPCI.SetValueDisplayName(PngBitDepthUIChoices.Bpp32, PdnResources.GetString("PngFileType.ConfigUI.BitDepth.Bpp32.DisplayName")); + bitDepthPCI.SetValueDisplayName(PngBitDepthUIChoices.Bpp24, PdnResources.GetString("PngFileType.ConfigUI.BitDepth.Bpp24.DisplayName")); + bitDepthPCI.SetValueDisplayName(PngBitDepthUIChoices.Bpp8, PdnResources.GetString("PngFileType.ConfigUI.BitDepth.Bpp8.DisplayName")); + + configUI.SetPropertyControlType(PropertyNames.BitDepth, PropertyControlType.RadioButton); + + configUI.SetPropertyControlValue( + PropertyNames.DitherLevel, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("PngFileType.ConfigUI.DitherLevel.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.Threshold, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("PngFileType.ConfigUI.Threshold.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.Threshold, + ControlInfoPropertyNames.Description, + PdnResources.GetString("PngFileType.ConfigUI.Threshold.Description")); + + return configUI; + } + + public override PropertyCollection OnCreateSavePropertyCollection() + { + List props = new List(); + + props.Add(StaticListChoiceProperty.CreateForEnum(PropertyNames.BitDepth, PngBitDepthUIChoices.AutoDetect, false)); + props.Add(new Int32Property(PropertyNames.DitherLevel, 7, 0, 8)); + props.Add(new Int32Property(PropertyNames.Threshold, 128, 0, 255)); + + List rules = new List(); + + rules.Add(new ReadOnlyBoundToValueRule( + PropertyNames.Threshold, + PropertyNames.BitDepth, + PngBitDepthUIChoices.Bpp8, + true)); + + rules.Add(new ReadOnlyBoundToValueRule( + PropertyNames.DitherLevel, + PropertyNames.BitDepth, + PngBitDepthUIChoices.Bpp8, + true)); + + PropertyCollection pc = new PropertyCollection(props, rules); + + return pc; + } + + protected override Document OnLoad(Stream input) + { + using (Image image = PdnResources.LoadImage(input)) + { + Document document = Document.FromImage(image); + return document; + } + } + + internal override Set CreateAllowedBitDepthListFromToken(PropertyBasedSaveConfigToken token) + { + PngBitDepthUIChoices bitDepthFromToken = (PngBitDepthUIChoices)token.GetProperty(PropertyNames.BitDepth).Value; + + Set bitDepths = new Set(); + + switch (bitDepthFromToken) + { + case PngBitDepthUIChoices.AutoDetect: + bitDepths.AddRange(SavableBitDepths.Rgb24, SavableBitDepths.Rgb8, SavableBitDepths.Rgba32, SavableBitDepths.Rgba8); + break; + + case PngBitDepthUIChoices.Bpp24: + bitDepths.AddRange(SavableBitDepths.Rgb24); + break; + + case PngBitDepthUIChoices.Bpp32: + bitDepths.AddRange(SavableBitDepths.Rgba32); + break; + + case PngBitDepthUIChoices.Bpp8: + bitDepths.AddRange(SavableBitDepths.Rgb8, SavableBitDepths.Rgba8); + break; + + default: + throw new InvalidEnumArgumentException("bitDepthFromToken", (int)bitDepthFromToken, typeof(PngBitDepthUIChoices)); + } + + return bitDepths; + } + + internal override int GetThresholdFromToken(PropertyBasedSaveConfigToken token) + { + int threshold = token.GetProperty(PropertyNames.Threshold).Value; + return threshold; + } + + internal override int GetDitherLevelFromToken(PropertyBasedSaveConfigToken token) + { + int ditherLevel = token.GetProperty(PropertyNames.DitherLevel).Value; + return ditherLevel; + } + + internal override unsafe void FinalSave( + Document input, + Stream output, + Surface scratchSurface, + int ditherLevel, + SavableBitDepths bitDepth, + PropertyBasedSaveConfigToken token, + ProgressEventHandler progressCallback) + { + if (bitDepth == SavableBitDepths.Rgba32) + { + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Png); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 32); + parms.Param[0] = parm; + + using (Bitmap bitmap = scratchSurface.CreateAliasedBitmap()) + { + GdiPlusFileType.LoadProperties(bitmap, input); + bitmap.Save(output, icf, parms); + } + } + else if (bitDepth == SavableBitDepths.Rgb24) + { + // In order to save memory, we 'squish' the 32-bit bitmap down to 24-bit in-place + // instead of allocating a new bitmap and copying it over. + SquishSurfaceTo24Bpp(scratchSurface); + + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Png); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 24); + parms.Param[0] = parm; + + using (Bitmap bitmap = CreateAliased24BppBitmap(scratchSurface)) + { + GdiPlusFileType.LoadProperties(bitmap, input); + bitmap.Save(output, icf, parms); + } + } + else if (bitDepth == SavableBitDepths.Rgb8) + { + using (Bitmap quantized = Quantize(scratchSurface, ditherLevel, 256, false, progressCallback)) + { + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Png); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 8); + parms.Param[0] = parm; + + GdiPlusFileType.LoadProperties(quantized, input); + quantized.Save(output, icf, parms); + } + } + else if (bitDepth == SavableBitDepths.Rgba8) + { + using (Bitmap quantized = Quantize(scratchSurface, ditherLevel, 256, true, progressCallback)) + { + ImageCodecInfo icf = GdiPlusFileType.GetImageCodecInfo(ImageFormat.Png); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 8); + parms.Param[0] = parm; + + GdiPlusFileType.LoadProperties(quantized, input); + quantized.Save(output, icf, parms); + } + } + else + { + throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(SavableBitDepths)); + } + } + } +} diff --git a/src/Data/PropertyBasedFileType.cs b/src/Data/PropertyBasedFileType.cs new file mode 100644 index 0000000..8318a65 --- /dev/null +++ b/src/Data/PropertyBasedFileType.cs @@ -0,0 +1,151 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.IO; + +namespace PaintDotNet +{ + public abstract class PropertyBasedFileType + : FileType + { + public abstract PropertyCollection OnCreateSavePropertyCollection(); + + public PropertyCollection CreateSavePropertyCollection() + { + PropertyCollection props = OnCreateSavePropertyCollection(); + + // Perform any necessary validation here. Right now there's nothing special to do. + + return props.Clone(); + } + + public static ControlInfo CreateDefaultSaveConfigUI(PropertyCollection props) + { + PanelControlInfo configUI = new PanelControlInfo(); + + foreach (Property property in props) + { + PropertyControlInfo propertyControlInfo = PropertyControlInfo.CreateFor(property); + + foreach (Property controlProperty in propertyControlInfo.ControlProperties) + { + if (0 == string.Compare(controlProperty.Name, ControlInfoPropertyNames.DisplayName.ToString(), StringComparison.InvariantCulture)) + { + controlProperty.Value = property.Name; + } + else if (0 == string.Compare(controlProperty.Name, ControlInfoPropertyNames.ShowResetButton.ToString(), StringComparison.InvariantCulture)) + { + controlProperty.Value = false; + } + } + + configUI.AddChildControl(propertyControlInfo); + } + + return configUI; + } + + public virtual ControlInfo OnCreateSaveConfigUI(PropertyCollection props) + { + return CreateDefaultSaveConfigUI(props); + } + + public ControlInfo CreateSaveConfigUI(PropertyCollection props) + { + ControlInfo configUI = OnCreateSaveConfigUI(props); + + // Perform any necessary validation here. Right now there's nothing special to do. + + return configUI; + } + + protected override sealed PropertyBasedSaveConfigToken OnCreateDefaultSaveConfigTokenT() + { + PropertyCollection props = CreateSavePropertyCollection(); + PropertyCollection props1 = props.Clone(); + + PropertyBasedSaveConfigToken token = new PropertyBasedSaveConfigToken(props1); + + return token; + } + + protected override sealed PropertyBasedSaveConfigWidget OnCreateSaveConfigWidgetT() + { + PropertyCollection props1 = CreateSavePropertyCollection(); + PropertyCollection props2 = props1.Clone(); + PropertyCollection props3 = props1.Clone(); + + ControlInfo configUI1 = CreateSaveConfigUI(props2); + ControlInfo configUI2 = configUI1.Clone(); + + PropertyBasedSaveConfigWidget widget = new PropertyBasedSaveConfigWidget(this, props3, configUI2); + + return widget; + } + + protected override PropertyBasedSaveConfigToken GetSaveConfigTokenFromSerializablePortionT(object portion) + { + PropertyCollection props1 = CreateSavePropertyCollection(); + PropertyCollection props2 = props1.Clone(); + + Pair[] nameValues = (Pair[])portion; + + foreach (Pair nameValue in nameValues) + { + Property property = props2[nameValue.First]; + + if (property.ReadOnly) + { + property.ReadOnly = false; + property.Value = nameValue.Second; + property.ReadOnly = true; + } + else + { + property.Value = nameValue.Second; + } + } + + PropertyBasedSaveConfigToken newToken = CreateDefaultSaveConfigToken(); + + newToken.Properties.CopyCompatibleValuesFrom(props2, true); + + return newToken; + } + + protected override object GetSerializablePortionOfSaveConfigToken(PropertyBasedSaveConfigToken token) + { + // We do not want to save the schema, just the [name, value] pairs + int propCount = token.Properties.Count; + + Pair[] nameValues = new Pair[propCount]; + + int index = 0; + foreach (string propertyName in token.PropertyNames) + { + Property property = token.GetProperty(propertyName); + object value = property.Value; + Pair nameValue = Pair.Create(propertyName, value); + nameValues[index] = nameValue; + ++index; + } + + return nameValues; + } + + public PropertyBasedFileType(string name, FileTypeFlags flags, string[] extensions) + : base(name, flags, extensions) + { + } + } +} diff --git a/src/Data/PropertyBasedSaveConfigToken.cs b/src/Data/PropertyBasedSaveConfigToken.cs new file mode 100644 index 0000000..7679d79 --- /dev/null +++ b/src/Data/PropertyBasedSaveConfigToken.cs @@ -0,0 +1,91 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + [Serializable] + public sealed class PropertyBasedSaveConfigToken + : SaveConfigToken + { + private PropertyCollection properties; + + public PropertyCollection Properties + { + get + { + return this.properties; + } + } + + public IEnumerable PropertyNames + { + get + { + return this.properties.PropertyNames; + } + } + + public Property GetProperty(object propertyName) + { + return this.properties[propertyName]; + } + + public T GetProperty(object propertyName) + where T : Property + { + return (T)this.properties[propertyName]; + } + + public bool SetPropertyValue(object propertyName, object newValue) + { + try + { + Property property = this.properties[propertyName]; + property.Value = newValue; + } + + catch (Exception ex) + { + if (ex is KeyNotFoundException || + ex is ReadOnlyException) + { + return false; + } + else + { + throw; + } + } + + return true; + } + + public PropertyBasedSaveConfigToken(PropertyCollection props) + { + this.properties = props.Clone(); + } + + private PropertyBasedSaveConfigToken(PropertyBasedSaveConfigToken copyMe) + : base(copyMe) + { + this.properties = copyMe.properties.Clone(); + } + + public override object Clone() + { + return new PropertyBasedSaveConfigToken(this); + } + } + +} diff --git a/src/Data/PropertyBasedSaveConfigWidget.cs b/src/Data/PropertyBasedSaveConfigWidget.cs new file mode 100644 index 0000000..2077f4c --- /dev/null +++ b/src/Data/PropertyBasedSaveConfigWidget.cs @@ -0,0 +1,147 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class PropertyBasedSaveConfigWidget + : SaveConfigWidget + { + private PropertyCollection originalProps; + private ControlInfo configUI; + private Control configUIControl; + + protected override void InitWidgetFromToken(PropertyBasedSaveConfigToken sourceToken) + { + foreach (string propertyName in sourceToken.PropertyNames) + { + Property srcProperty = sourceToken.GetProperty(propertyName); + PropertyControlInfo dstPropertyControlInfo = this.configUI.FindControlForPropertyName(propertyName); + + if (dstPropertyControlInfo != null) + { + Property dstProperty = dstPropertyControlInfo.Property; + + if (dstProperty.ReadOnly) + { + dstProperty.ReadOnly = false; + dstProperty.Value = srcProperty.Value; + dstProperty.ReadOnly = true; + } + else + { + dstProperty.Value = srcProperty.Value; + } + } + } + } + + protected override PropertyBasedSaveConfigToken CreateTokenFromWidget() + { + PropertyCollection props = this.originalProps.Clone(); + + foreach (string propertyName in props.PropertyNames) + { + PropertyControlInfo srcPropertyControlInfo = this.configUI.FindControlForPropertyName(propertyName); + + if (srcPropertyControlInfo != null) + { + Property srcProperty = srcPropertyControlInfo.Property; + Property dstProperty = props[propertyName]; + + if (dstProperty.ReadOnly) + { + dstProperty.ReadOnly = false; + dstProperty.Value = srcProperty.Value; + dstProperty.ReadOnly = true; + } + else + { + dstProperty.Value = srcProperty.Value; + } + } + } + + PropertyBasedSaveConfigToken pbsct = new PropertyBasedSaveConfigToken(props); + + return pbsct; + } + + public PropertyBasedSaveConfigWidget(PropertyBasedFileType fileType, PropertyCollection props, ControlInfo configUI) + : base(fileType) + { + this.originalProps = props.Clone(); + this.configUI = configUI.Clone(); + + // Make sure that the properties in props and configUI are not the same objects + foreach (Property property in props) + { + PropertyControlInfo pci = this.configUI.FindControlForPropertyName(property.Name); + + if (pci != null) + { + if (object.ReferenceEquals(property, pci.Property)) + { + throw new ArgumentException("Property references in propertyCollection must not be the same as those in configUI"); + } + } + } + + SuspendLayout(); + + this.configUIControl = (Control)this.configUI.CreateConcreteControl(this); + this.configUIControl.SuspendLayout(); + + this.configUIControl.TabIndex = 0; + + // Set up data binding + foreach (Property property in this.originalProps) + { + PropertyControlInfo pci = this.configUI.FindControlForPropertyName(property.Name); + + if (pci == null) + { + throw new InvalidOperationException("Every property must have a control associated with it"); + } + else + { + Property controlsProperty = pci.Property; + + // ASSUMPTION: We assume that the concrete WinForms Control holds a reference to + // the same Property instance as the ControlInfo it was created from. + + controlsProperty.ValueChanged += ControlsProperty_ValueChanged; + } + } + + this.Controls.Add(this.configUIControl); + + this.configUIControl.ResumeLayout(false); + ResumeLayout(false); + PerformLayout(); + } + + protected override void OnLayout(LayoutEventArgs e) + { + this.configUIControl.Width = this.ClientSize.Width; + this.configUIControl.PerformLayout(); + this.ClientSize = this.configUIControl.Size; + base.OnLayout(e); + } + + private void ControlsProperty_ValueChanged(object sender, EventArgs e) + { + UpdateToken(); + } + } +} diff --git a/src/Data/Quantize/OctreeQuantizer.cs b/src/Data/Quantize/OctreeQuantizer.cs new file mode 100644 index 0000000..f72ff30 --- /dev/null +++ b/src/Data/Quantize/OctreeQuantizer.cs @@ -0,0 +1,569 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp + +using PaintDotNet; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; + +namespace PaintDotNet.Data.Quantize +{ + internal unsafe class OctreeQuantizer + : Quantizer + { + private bool enableTransparency; + private Octree octree; + private int maxColors; + + /// + /// Construct the octree quantizer + /// + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree, + /// the second pass quantizes a color based on the nodes in the tree + /// + /// The maximum number of colors to return + /// The number of significant bits + /// If true, then one color slot in the palette will be reserved for transparency. + /// Any color passed through QuantizePixel which does not have an alpha of 255 will use this color slot. + /// If false, then all colors should have an alpha of 255. Otherwise the results may be unpredictable. + public OctreeQuantizer(int maxColors, bool enableTransparency) + : base(false) + { + if (maxColors > 256) + { + throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be 256 or less"); + } + + if (maxColors < 2) + { + throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors must be 2 or more"); + } + + this.octree = new Octree(8); // 8-bits per color + this.enableTransparency = enableTransparency; + this.maxColors = maxColors - (this.enableTransparency ? 1 : 0); // subtract 1 if enableTransparency is true + } + + /// + /// Process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected override void InitialQuantizePixel(ColorBgra *pixel) + { + this.octree.AddColor(pixel); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// The quantized value + protected override byte QuantizePixel(ColorBgra *pixel) + { + byte paletteIndex = 0; + + if (!this.enableTransparency || pixel->A == 255) + { + paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); + } + else + { + paletteIndex = (byte)this.maxColors; // maxColors will have a maximum value of 255 is enableTransparency is true + } + + return paletteIndex; + } + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overwritten + /// The new color palette + protected override ColorPalette GetPalette(ColorPalette original) + { + // First off convert the octree to _maxColors colors + List palette = this.octree.Palletize(maxColors); + + // Then convert the palette based on those colors + for (int index = 0; index < palette.Count; index++) + { + original.Entries[index] = palette[index]; + } + + // Fill the rest of the palette with transparent + for (int i = palette.Count; i < original.Entries.Length; ++i) + { + original.Entries[i] = Color.FromArgb(255, 0, 0, 0); + } + + // Add the transparent color + if (this.enableTransparency) + { + original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0); + } + + return original; + } + + /// + /// Class which does the actual quantization + /// + private class Octree + { + /// + /// Construct the octree + /// + /// The maximum number of significant bits in the image + public Octree(int maxColorBits) + { + _maxColorBits = maxColorBits; + _leafCount = 0; + _reducibleNodes = new OctreeNode[9]; + _root = new OctreeNode(0, _maxColorBits, this); + _previousColor = 0; + _previousNode = null; + } + + /// + /// Add a given color value to the octree + /// + /// + public void AddColor(ColorBgra *pixel) + { + // Check if this request is for the same color as the last + if (_previousColor == pixel->Bgra) + { + // If so, check if I have a previous node setup. This will only ocurr if the first color in the image + // happens to be black, with an alpha component of zero. + if (null == _previousNode) + { + _previousColor = pixel->Bgra; + _root.AddColor(pixel, _maxColorBits, 0, this); + } + else + { + // Just update the previous node + _previousNode.Increment(pixel); + } + } + else + { + _previousColor = pixel->Bgra; + _root.AddColor(pixel, _maxColorBits, 0, this); + } + } + + /// + /// Reduce the depth of the tree + /// + public void Reduce() + { + int index; + + // Find the deepest level containing at least one reducible node + for (index = _maxColorBits - 1; + (index > 0) && (null == _reducibleNodes[index]); + index--) + { + // intentionally blank + } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = _reducibleNodes[index]; + _reducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + _leafCount -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + _previousNode = null; + } + + /// + /// Get/Set the number of leaves in the tree + /// + public int Leaves + { + get + { + return _leafCount; + } + + set + { + _leafCount = value; + } + } + + /// + /// Return the array of reducible nodes + /// + protected OctreeNode[] ReducibleNodes + { + get + { + return _reducibleNodes; + } + } + + /// + /// Keep track of the previous node that was quantized + /// + /// The node last quantized + protected void TrackPrevious(OctreeNode node) + { + _previousNode = node; + } + + private Color[] _palette; + private PaletteTable paletteTable; + + /// + /// Convert the nodes in the octree to a palette with a maximum of colorCount colors + /// + /// The maximum number of colors + /// A list with the palettized colors + public List Palletize(int colorCount) + { + while (Leaves > colorCount) + { + Reduce(); + } + + // Now palettize the nodes + List palette = new List(Leaves); + int paletteIndex = 0; + + _root.ConstructPalette(palette, ref paletteIndex); + + // And return the palette + this._palette = palette.ToArray(); + this.paletteTable = null; + + return palette; + } + + /// + /// Get the palette index for the passed color + /// + /// + /// + public int GetPaletteIndex(ColorBgra *pixel) + { + int ret = -1; + + ret = _root.GetPaletteIndex(pixel, 0); + + if (ret < 0) + { + if (this.paletteTable == null) + { + this.paletteTable = new PaletteTable(this._palette); + } + + ret = this.paletteTable.FindClosestPaletteIndex(pixel->ToColor()); + } + + return ret; + } + + /// + /// Mask used when getting the appropriate pixels for a given node + /// + private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// + /// The root of the octree + /// + private OctreeNode _root; + + /// + /// Number of leaves in the tree + /// + private int _leafCount; + + /// + /// Array of reducible nodes + /// + private OctreeNode[] _reducibleNodes; + + /// + /// Maximum number of significant bits in the image + /// + private int _maxColorBits; + + /// + /// Store the last node quantized + /// + private OctreeNode _previousNode; + + /// + /// Cache the previous color quantized + /// + private uint _previousColor; + + /// + /// Class which encapsulates each node in the tree + /// + protected class OctreeNode + { + /// + /// Construct the node + /// + /// The level in the tree = 0 - 7 + /// The number of significant color bits in the image + /// The tree to which this node belongs + public OctreeNode(int level, int colorBits, Octree octree) + { + // Construct the new node + _leaf = (level == colorBits); + + _red = 0; + _green = 0; + _blue = 0; + _pixelCount = 0; + + // If a leaf, increment the leaf count + if (_leaf) + { + octree.Leaves++; + _nextReducible = null; + _children = null; + } + else + { + // Otherwise add this to the reducible nodes + _nextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + _children = new OctreeNode[8]; + } + } + + /// + /// Add a color into the tree + /// + /// The color + /// The number of significant color bits + /// The level in the tree + /// The tree to which this node belongs + public void AddColor(ColorBgra *pixel, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (_leaf) + { + Increment(pixel); + + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int shift = 7 - level; + int index = ((pixel->R & mask[level]) >> (shift - 2)) | + ((pixel->G & mask[level]) >> (shift - 1)) | + ((pixel->B & mask[level]) >> (shift)); + + OctreeNode child = _children[index]; + + if (null == child) + { + // Create a new child node & store in the array + child = new OctreeNode(level + 1, colorBits, octree); + _children[index] = child; + } + + // Add the color to the child node + child.AddColor(pixel, colorBits, level + 1, octree); + } + + } + + /// + /// Get/Set the next reducible node + /// + public OctreeNode NextReducible + { + get + { + return _nextReducible; + } + + set + { + _nextReducible = value; + } + } + + /// + /// Return the child nodes + /// + public OctreeNode[] Children + { + get + { + return _children; + } + } + + /// + /// Reduce this node by removing all of its children + /// + /// The number of leaves removed + public int Reduce() + { + int children = 0; + _red = 0; + _green = 0; + _blue = 0; + + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + if (null != _children[index]) + { + _red += _children[index]._red; + _green += _children[index]._green; + _blue += _children[index]._blue; + _pixelCount += _children[index]._pixelCount; + ++children; + _children[index] = null; + } + } + + // Now change this to a leaf node + _leaf = true; + + // Return the number of nodes to decrement the leaf count by + return(children - 1); + } + + /// + /// Traverse the tree, building up the color palette + /// + /// The palette + /// The current palette index + public void ConstructPalette(List palette, ref int paletteIndex) + { + if (_leaf) + { + // Consume the next palette index + _paletteIndex = paletteIndex++; + + // And set the color of the palette entry + int r = _red / _pixelCount; + int g = _green / _pixelCount; + int b = _blue / _pixelCount; + + palette.Add(Color.FromArgb(r, g, b)); + } + else + { + // Loop through children looking for leaves + for (int index = 0; index < 8; index++) + { + if (null != _children[index]) + { + _children[index].ConstructPalette(palette, ref paletteIndex); + } + } + } + } + + /// + /// Return the palette index for the passed color + /// + public int GetPaletteIndex(ColorBgra *pixel, int level) + { + int paletteIndex = _paletteIndex; + + if (!_leaf) + { + int shift = 7 - level; + int index = ((pixel->R & mask[level]) >> (shift - 2)) | + ((pixel->G & mask[level]) >> (shift - 1)) | + ((pixel->B & mask[level]) >> (shift)); + + if (null != _children[index]) + { + paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1); + } + else + { + paletteIndex = -1; + } + } + + return paletteIndex; + } + + /// + /// Increment the pixel count and add to the color information + /// + public void Increment(ColorBgra *pixel) + { + ++_pixelCount; + _red += pixel->R; + _green += pixel->G; + _blue += pixel->B; + } + + /// + /// Flag indicating that this is a leaf node + /// + private bool _leaf; + + /// + /// Number of pixels in this node + /// + private int _pixelCount; + + /// + /// Red component + /// + private int _red; + + /// + /// Green Component + /// + private int _green; + + /// + /// Blue component + /// + private int _blue; + + /// + /// Pointers to any child nodes + /// + private OctreeNode[] _children; + + /// + /// Pointer to next reducible node + /// + private OctreeNode _nextReducible; + + /// + /// The index of this node in the palette + /// + private int _paletteIndex; + } + } + } +} diff --git a/src/Data/Quantize/PaletteQuantizer.cs b/src/Data/Quantize/PaletteQuantizer.cs new file mode 100644 index 0000000..a157266 --- /dev/null +++ b/src/Data/Quantize/PaletteQuantizer.cs @@ -0,0 +1,136 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp + +using PaintDotNet; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; + +namespace PaintDotNet.Data.Quantize +{ + internal unsafe class PaletteQuantizer + : Quantizer + { + /// + /// Lookup table for colors + /// + private Dictionary _colorMap; + + /// + /// List of all colors in the palette + /// + private Color[] _colors; + + /// + /// Construct the palette quantizer + /// + /// The color palette to quantize to + /// + /// Palette quantization only requires a single quantization step + /// + public PaletteQuantizer(List palette) + : base(true) + { + _colorMap = new Dictionary(); + _colors = new Color[palette.Count]; + palette.CopyTo(_colors); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// The quantized value + protected override byte QuantizePixel(ColorBgra* pixel) + { + byte colorIndex = 0; + uint colorHash = pixel->Bgra; + + // Check if the color is in the lookup table + if (_colorMap.ContainsKey(colorHash)) + { + colorIndex = _colorMap[colorHash]; + } + else + { + // Not found - loop through the palette and find the nearest match. + // Firstly check the alpha value - if 0, lookup the transparent color + if (0 == pixel->A) + { + // Transparent. Lookup the first color with an alpha value of 0 + for (int index = 0; index < _colors.Length; index++) + { + if (0 == _colors[index].A) + { + colorIndex = (byte)index; + break; + } + } + } + else + { + // Not transparent... + int leastDistance = int.MaxValue; + int red = pixel->R; + int green = pixel->G; + int blue = pixel->B; + + // Loop through the entire palette, looking for the closest color match + for (int index = 0; index < _colors.Length; index++) + { + Color paletteColor = _colors[index]; + + int redDistance = paletteColor.R - red; + int greenDistance = paletteColor.G - green; + int blueDistance = paletteColor.B - blue; + + int distance = (redDistance * redDistance) + (greenDistance * greenDistance) + + (blueDistance * blueDistance); + + if (distance < leastDistance) + { + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (0 == distance) + { + break; + } + } + } + } + + // Now I have the color, pop it into the hashtable for next time + _colorMap.Add(colorHash, colorIndex); + } + + return colorIndex; + } + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overrwritten + /// The new color palette + protected override ColorPalette GetPalette(ColorPalette palette) + { + for (int index = 0; index < _colors.Length; index++) + { + palette.Entries[index] = _colors[index]; + } + + return palette; + } + } +} diff --git a/src/Data/Quantize/PaletteTable.cs b/src/Data/Quantize/PaletteTable.cs new file mode 100644 index 0000000..de3cb42 --- /dev/null +++ b/src/Data/Quantize/PaletteTable.cs @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Drawing; + +namespace PaintDotNet.Data.Quantize +{ + public sealed class PaletteTable + { + private Color[] palette; + + public Color this[int index] + { + get + { + return this.palette[index]; + } + + set + { + this.palette[index] = value; + } + } + + private int GetDistanceSquared(Color a, Color b) + { + int dsq = 0; // delta squared + int v; + + v = a.B - b.B; + dsq += v * v; + v = a.G - b.G; + dsq += v * v; + v = a.R - b.R; + dsq += v * v; + + return dsq; + } + + public int FindClosestPaletteIndex(Color pixel) + { + int dsqBest = int.MaxValue; + int ret = 0; + + for (int i = 0; i < this.palette.Length; ++i) + { + int dsq = GetDistanceSquared(this.palette[i], pixel); + + if (dsq < dsqBest) + { + dsqBest = dsq; + ret = i; + } + } + + return ret; + } + + public PaletteTable(Color[] palette) + { + this.palette = (Color[])palette.Clone(); + } + } +} diff --git a/src/Data/Quantize/Quantizer.cs b/src/Data/Quantize/Quantizer.cs new file mode 100644 index 0000000..bf46705 --- /dev/null +++ b/src/Data/Quantize/Quantizer.cs @@ -0,0 +1,345 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp + +using PaintDotNet; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; + +namespace PaintDotNet.Data.Quantize +{ + internal unsafe abstract class Quantizer + { + /// + /// Flag used to indicate whether a single pass or two passes are needed for quantization. + /// + private bool singlePass; + + protected int ditherLevel; + public int DitherLevel + { + get + { + return this.ditherLevel; + } + + set + { + this.ditherLevel = value; + } + } + + /// + /// Construct the quantizer + /// + /// If true, the quantization only needs to loop through the source pixels once + /// + /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, + /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' + /// and then 'QuantizeImage'. + /// + public Quantizer(bool singlePass) + { + this.singlePass = singlePass; + } + + /// + /// Quantize an image and return the resulting output bitmap + /// + /// The image to quantize + /// A quantized version of the image + public Bitmap Quantize(Image source, ProgressEventHandler progressCallback) + { + // Get the size of the source image + int height = source.Height; + int width = source.Width; + + // And construct a rectangle from these dimensions + Rectangle bounds = new Rectangle(0, 0, width, height); + + // First off take a 32bpp version of the image + Bitmap img32bpp; + + if (source is Bitmap && source.PixelFormat == PixelFormat.Format32bppArgb) + { + img32bpp = (Bitmap)source; + } + else + { + img32bpp = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + // Now lock the bitmap into memory + using (Graphics g = Graphics.FromImage(img32bpp)) + { + g.PageUnit = GraphicsUnit.Pixel; + + // Draw the source image onto the copy bitmap, + // which will effect a widening as appropriate. + g.DrawImage(source, 0, 0, bounds.Width, bounds.Height); + } + } + + // And construct an 8bpp version + Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + + // Define a pointer to the bitmap data + BitmapData sourceData = null; + + try + { + // Get the source image bits and lock into memory + sourceData = img32bpp.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + + // Call the FirstPass function if not a single pass algorithm. + // For something like an octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!singlePass) + { + FirstPass(sourceData, width, height, progressCallback); + } + + // Then set the color palette on the output bitmap. I'm passing in the current palette + // as there's no way to construct a new, empty palette. + output.Palette = this.GetPalette(output.Palette); + + // Then call the second pass which actually does the conversion + SecondPass(sourceData, output, width, height, bounds, progressCallback); + } + + finally + { + // Ensure that the bits are unlocked + img32bpp.UnlockBits(sourceData); + } + + if (img32bpp != source) + { + img32bpp.Dispose(); + img32bpp = null; + } + + // Last but not least, return the output bitmap + return output; + } + + /// + /// Execute the first pass through the pixels in the image + /// + /// The source data + /// The width in pixels of the image + /// The height in pixels of the image + protected virtual void FirstPass(BitmapData sourceData, int width, int height, ProgressEventHandler progressCallback) + { + // Define the source data pointers. The source row is a byte to + // keep addition of the stride value easier (as this is in bytes) + byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer(); + Int32* pSourcePixel; + + // Loop through each row + for (int row = 0; row < height; row++) + { + // Set the source pixel to the first pixel in this row + pSourcePixel = (Int32*)pSourceRow; + + // And loop through each column + for (int col = 0; col < width; col++, pSourcePixel++) + { + InitialQuantizePixel((ColorBgra *)pSourcePixel); + } + + // Add the stride to the source row + pSourceRow += sourceData.Stride; + + if (progressCallback != null) + { + progressCallback(this, new ProgressEventArgs(100.0 * (((double)(row + 1) / (double)height) / 2.0))); + } + } + } + + /// + /// Execute a second pass through the bitmap + /// + /// The source bitmap, locked into memory + /// The output bitmap + /// The width in pixels of the image + /// The height in pixels of the image + /// The bounding rectangle + protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds, ProgressEventHandler progressCallback) + { + BitmapData outputData = null; + Color[] pallete = output.Palette.Entries; + int weight = ditherLevel; + + try + { + // Lock the output bitmap into memory + outputData = output.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); + + // Define the source data pointers. The source row is a byte to + // keep addition of the stride value easier (as this is in bytes) + byte* pSourceRow = (byte *)sourceData.Scan0.ToPointer(); + Int32* pSourcePixel = (Int32 *)pSourceRow; + + // Now define the destination data pointers + byte* pDestinationRow = (byte *)outputData.Scan0.ToPointer(); + byte* pDestinationPixel = pDestinationRow; + + int[] errorThisRowR = new int[width + 1]; + int[] errorThisRowG = new int[width + 1]; + int[] errorThisRowB = new int[width + 1]; + + for (int row = 0; row < height; row++) + { + int[] errorNextRowR = new int[width + 1]; + int[] errorNextRowG = new int[width + 1]; + int[] errorNextRowB = new int[width + 1]; + + int ptrInc; + + if ((row & 1) == 0) + { + pSourcePixel = (Int32*)pSourceRow; + pDestinationPixel = pDestinationRow; + ptrInc = +1; + } + else + { + pSourcePixel = (Int32*)pSourceRow + width - 1; + pDestinationPixel = pDestinationRow + width - 1; + ptrInc = -1; + } + + // Loop through each pixel on this scan line + for (int col = 0; col < width; ++col) + { + // Quantize the pixel + ColorBgra srcPixel = *(ColorBgra *)pSourcePixel; + ColorBgra target = new ColorBgra(); + + target.B = Utility.ClampToByte(srcPixel.B - ((errorThisRowB[col] * weight) / 8)); + target.G = Utility.ClampToByte(srcPixel.G - ((errorThisRowG[col] * weight) / 8)); + target.R = Utility.ClampToByte(srcPixel.R - ((errorThisRowR[col] * weight) / 8)); + target.A = srcPixel.A; + + byte pixelValue = QuantizePixel(&target); + *pDestinationPixel = pixelValue; + + ColorBgra actual = ColorBgra.FromColor(pallete[pixelValue]); + + int errorR = actual.R - target.R; + int errorG = actual.G - target.G; + int errorB = actual.B - target.B; + + // Floyd-Steinberg Error Diffusion: + // a) 7/16 error goes to x+1 + // b) 5/16 error goes to y+1 + // c) 3/16 error goes to x-1,y+1 + // d) 1/16 error goes to x+1,y+1 + + const int a = 7; + const int b = 5; + const int c = 3; + + int errorRa = (errorR * a) / 16; + int errorRb = (errorR * b) / 16; + int errorRc = (errorR * c) / 16; + int errorRd = errorR - errorRa - errorRb - errorRc; + + int errorGa = (errorG * a) / 16; + int errorGb = (errorG * b) / 16; + int errorGc = (errorG * c) / 16; + int errorGd = errorG - errorGa - errorGb - errorGc; + + int errorBa = (errorB * a) / 16; + int errorBb = (errorB * b) / 16; + int errorBc = (errorB * c) / 16; + int errorBd = errorB - errorBa - errorBb - errorBc; + + errorThisRowR[col + 1] += errorRa; + errorThisRowG[col + 1] += errorGa; + errorThisRowB[col + 1] += errorBa; + + errorNextRowR[width - col] += errorRb; + errorNextRowG[width - col] += errorGb; + errorNextRowB[width - col] += errorBb; + + if (col != 0) + { + errorNextRowR[width - (col - 1)] += errorRc; + errorNextRowG[width - (col - 1)] += errorGc; + errorNextRowB[width - (col - 1)] += errorBc; + } + + errorNextRowR[width - (col + 1)] += errorRd; + errorNextRowG[width - (col + 1)] += errorGd; + errorNextRowB[width - (col + 1)] += errorBd; + + // unchecked is necessary because otherwise it throws a fit if ptrInc is negative. + unchecked + { + pSourcePixel += ptrInc; + pDestinationPixel += ptrInc; + } + } + + // Add the stride to the source row + pSourceRow += sourceData.Stride; + + // And to the destination row + pDestinationRow += outputData.Stride; + + if (progressCallback != null) + { + progressCallback(this, new ProgressEventArgs(100.0 * (0.5 + ((double)(row + 1) / (double)height) / 2.0))); + } + + errorThisRowB = errorNextRowB; + errorThisRowG = errorNextRowG; + errorThisRowR = errorNextRowR; + } + } + + finally + { + // Ensure that I unlock the output bits + output.UnlockBits(outputData); + } + } + + /// + /// Override this to process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected virtual void InitialQuantizePixel(ColorBgra *pixel) + { + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// The quantized value + protected abstract byte QuantizePixel(ColorBgra *pixel); + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overrwritten + /// The new color palette + protected abstract ColorPalette GetPalette(ColorPalette original); + } +} diff --git a/src/Data/SaveConfigToken.cs b/src/Data/SaveConfigToken.cs new file mode 100644 index 0000000..27251bc --- /dev/null +++ b/src/Data/SaveConfigToken.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + [Serializable] + public class SaveConfigToken + : ICloneable, + IDeserializationCallback + + { + #region ICloneable Members + /// + /// This should simply call "new myType(this)" ... do not call base class' + /// implementation of Clone, as this is handled by the constructors. + /// + public virtual object Clone() + { + return new SaveConfigToken(this); + } + #endregion + + public SaveConfigToken() + { + } + + protected SaveConfigToken(SaveConfigToken copyMe) + { + } + + public virtual void Validate() + { + } + + public void OnDeserialization(object sender) + { + Validate(); + } + } +} + diff --git a/src/Data/SaveConfigWidget.cs b/src/Data/SaveConfigWidget.cs new file mode 100644 index 0000000..1d1c15d --- /dev/null +++ b/src/Data/SaveConfigWidget.cs @@ -0,0 +1,169 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public class SaveConfigWidget + : UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + protected SaveConfigToken token; + + [Browsable(false)] + public SaveConfigToken Token + { + get + { + return this.token; + } + + set + { + this.token = value; + OnTokenChanged(); + + if (this.token != null) + { + InitWidgetFromToken((SaveConfigToken)this.token.Clone()); + } + } + } + + public event EventHandler TokenChanged; + protected virtual void OnTokenChanged() + { + if (TokenChanged != null) + { + TokenChanged(this, EventArgs.Empty); + } + } + + [Browsable(false)] + protected FileType fileType; + public FileType FileType + { + get + { + return fileType; + } + } + + internal SaveConfigWidget(FileType fileType) + { + InitializeComponent(); + this.fileType = fileType; + } + + public SaveConfigWidget() + { + InitializeComponent(); + InitFileType(); + } + + public void UpdateToken() + { + InitTokenFromWidget(); + OnTokenChanged(); + } + + /// + /// This method msut be overriden in derived classes. + /// In this method you must initialize the protected fileType field. + /// + protected virtual void InitFileType() + { + //throw new InvalidOperationException("InitFileType was not implemented, or the derived method called the base method"); + } + + /// + /// This method must be overridden in derived classes. + /// In this method you must take the values from the given EffectToken + /// and use them to properly initialize the dialog's user interface elements. + /// + protected virtual void InitWidgetFromToken(SaveConfigToken sourceToken) + { + //throw new InvalidOperationException("InitWidgetFromToken was not implemented, or the derived method called the base method"); + } + + protected void InitWidgetFromToken() + { + // If we don't check for null, we get awful errors in the designer. + // Good idea to check for that anyway, yeah? + if (token != null) + { + InitWidgetFromToken((SaveConfigToken)token.Clone()); + } + } + + /// + /// This method must be overridden in derived classes. + /// In this method you must take the values from the dialog box + /// and use them to properly initialize theEffectToken. + /// + protected virtual void InitTokenFromWidget() + { + //throw new InvalidOperationException("InitTokenFromWidget was not implemented, or the derived method called the base method"); + } + + /// + /// Overrides Form.OnLoad. + /// + /// + /// + /// Derived classes MUST call this base method if they override it! + /// + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + InitWidgetFromToken(); + UpdateToken(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + SuspendLayout(); + components = new System.ComponentModel.Container(); + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + ResumeLayout(false); + } + #endregion + } +} diff --git a/src/Data/SaveConfigWidget.resx b/src/Data/SaveConfigWidget.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Data/SaveConfigWidget`2.cs b/src/Data/SaveConfigWidget`2.cs new file mode 100644 index 0000000..b945adb --- /dev/null +++ b/src/Data/SaveConfigWidget`2.cs @@ -0,0 +1,70 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public abstract class SaveConfigWidget + : SaveConfigWidget + where TFileType : FileType + where TToken : SaveConfigToken + { + private FileType fileTypeFromCtor; + + public new TToken Token + { + get + { + return (TToken)base.Token; + } + + set + { + base.Token = value; + } + } + + public new TFileType FileType + { + get + { + return (TFileType)base.FileType; + } + } + + protected override void InitFileType() + { + // This method won't actually be called, but is implemented anyway. + this.fileType = this.fileTypeFromCtor; + } + + protected abstract void InitWidgetFromToken(TToken sourceToken); + + protected override sealed void InitWidgetFromToken(SaveConfigToken sourceToken) + { + InitWidgetFromToken((TToken)sourceToken); + } + + protected abstract TToken CreateTokenFromWidget(); + + protected override sealed void InitTokenFromWidget() + { + TToken token = CreateTokenFromWidget(); + this.Token = token; + base.InitTokenFromWidget(); + } + + public SaveConfigWidget(FileType fileType) + : base(fileType) + { + this.fileTypeFromCtor = fileType; + } + } +} diff --git a/src/Data/TgaFileType.cs b/src/Data/TgaFileType.cs new file mode 100644 index 0000000..9de04f1 --- /dev/null +++ b/src/Data/TgaFileType.cs @@ -0,0 +1,886 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////////////////// +// Some of this code is adapted from code in the CxImage library by Davide Pizzolato. +// The following text is the original license.txt from that library: +// +// COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: +// +// CxImage version 5.99c 17/Oct/2004 +// +// CxImage : Copyright (C) 2001 - 2004, Davide Pizzolato +// +// Original CImage and CImageIterator implementation are: +// Copyright (C) 1995, Alejandro Aguilar Sierra (asierra(at)servidor(dot)unam(dot)mx) +// +// Covered code is provided under this license on an "as is" basis, without warranty +// of any kind, either expressed or implied, including, without limitation, warranties +// that the covered code is free of defects, merchantable, fit for a particular purpose +// or non-infringing. The entire risk as to the quality and performance of the covered +// code is with you. Should any covered code prove defective in any respect, you (not +// the initial developer or any other contributor) assume the cost of any necessary +// servicing, repair or correction. This disclaimer of warranty constitutes an essential +// part of this license. No use of any covered code is authorized hereunder except under +// this disclaimer. +// +// Permission is hereby granted to use, copy, modify, and distribute this +// source code, or portions hereof, for any purpose, including commercial applications, +// freely and without fee, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; + +namespace PaintDotNet.Data +{ + public sealed class TgaFileType + : InternalFileType + { + public enum PropertyNames + { + BitDepth = 0, + RleCompress = 1 + } + + public enum TgaBitDepthUIChoices + { + AutoDetect = 0, + Bpp32 = 1, + Bpp24 = 2 + } + + protected override bool IsReflexive(PropertyBasedSaveConfigToken token) + { + if ((TgaBitDepthUIChoices)token.GetProperty(PropertyNames.BitDepth).Value == TgaBitDepthUIChoices.Bpp32) + { + return true; + } + else + { + return base.IsReflexive(token); + } + } + + private enum TgaType + : byte + { + Null = 0, + Map = 1, + Rgb = 2, + Mono = 3, + RleMap = 9, + RleRgb = 10, + RleMono = 11, + CompMap = 32, + CompMap4 = 33 + } + + [StructLayout(LayoutKind.Sequential)] + private struct TgaHeader + { + public byte idLength; // Image ID Field Length + public byte cmapType; // Color Map Type + public TgaType imageType; // Image Type + + public ushort cmapIndex; // First Entry Index + public ushort cmapLength; // Color Map Length + public byte cmapEntrySize; // Color Map Entry Size + + public ushort xOrigin; // X-origin of Image + public ushort yOrigin; // Y-origin of Image + public ushort imageWidth; // Image Width + public ushort imageHeight; // Image Height + public byte pixelDepth; // Pixel Depth + public byte imageDesc; // Image Descriptor + + public void Write(Stream output) + { + output.WriteByte(this.idLength); + output.WriteByte(this.cmapType); + output.WriteByte((byte)this.imageType); + + Utility.WriteUInt16(output, this.cmapIndex); + Utility.WriteUInt16(output, this.cmapLength); + output.WriteByte(this.cmapEntrySize); + + Utility.WriteUInt16(output, this.xOrigin); + Utility.WriteUInt16(output, this.yOrigin); + Utility.WriteUInt16(output, this.imageWidth); + Utility.WriteUInt16(output, this.imageHeight); + output.WriteByte(this.pixelDepth); + output.WriteByte(this.imageDesc); + } + + public TgaHeader(Stream input) + { + int byteRead = input.ReadByte(); + if (byteRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.idLength = (byte)byteRead; + } + + byteRead = input.ReadByte(); + if (byteRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.cmapType = (byte)byteRead; + } + + byteRead = input.ReadByte(); + if (byteRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.imageType = (TgaType)byteRead; + } + + int shortRead = Utility.ReadUInt16(input); + if (shortRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.cmapIndex = (ushort)shortRead; + } + + shortRead = Utility.ReadUInt16(input); + if (shortRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.cmapLength = (ushort)shortRead; + } + + byteRead = input.ReadByte(); + if (byteRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.cmapEntrySize = (byte)byteRead; + } + + shortRead = Utility.ReadUInt16(input); + if (shortRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.xOrigin = (ushort)shortRead; + } + + shortRead = Utility.ReadUInt16(input); + if (shortRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.yOrigin = (ushort)shortRead; + } + + shortRead = Utility.ReadUInt16(input); + if (shortRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.imageWidth = (ushort)shortRead; + } + + shortRead = Utility.ReadUInt16(input); + if (shortRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.imageHeight = (ushort)shortRead; + } + + byteRead = input.ReadByte(); + if (byteRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.pixelDepth = (byte)byteRead; + } + + byteRead = input.ReadByte(); + if (byteRead == -1) + { + throw new EndOfStreamException(); + } + else + { + this.imageDesc = (byte)byteRead; + } + } + } + + internal override Set CreateAllowedBitDepthListFromToken(PropertyBasedSaveConfigToken token) + { + TgaBitDepthUIChoices bitDepth = (TgaBitDepthUIChoices)token.GetProperty(PropertyNames.BitDepth).Value; + + Set bitDepths = new Set(); + + switch (bitDepth) + { + case TgaBitDepthUIChoices.AutoDetect: + bitDepths.Add(SavableBitDepths.Rgb24); + bitDepths.Add(SavableBitDepths.Rgba32); + break; + + case TgaBitDepthUIChoices.Bpp24: + bitDepths.Add(SavableBitDepths.Rgb24); + break; + + case TgaBitDepthUIChoices.Bpp32: + bitDepths.Add(SavableBitDepths.Rgba32); + break; + + default: + throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(TgaBitDepthUIChoices)); + } + + return bitDepths; + } + + internal override int GetDitherLevelFromToken(PropertyBasedSaveConfigToken token) + { + return 0; + } + + internal override int GetThresholdFromToken(PropertyBasedSaveConfigToken token) + { + return 0; + } + + public override PropertyCollection OnCreateSavePropertyCollection() + { + List props = new List(); + + props.Add(StaticListChoiceProperty.CreateForEnum(PropertyNames.BitDepth, TgaBitDepthUIChoices.AutoDetect, false)); + props.Add(new BooleanProperty(PropertyNames.RleCompress, true)); + + return new PropertyCollection(props); + } + + public override ControlInfo OnCreateSaveConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultSaveConfigUI(props); + + configUI.SetPropertyControlValue( + PropertyNames.BitDepth, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("TgaFileType.ConfigUI.BitDepth.DisplayName")); + + configUI.SetPropertyControlType(PropertyNames.BitDepth, PropertyControlType.RadioButton); + + PropertyControlInfo bitDepthPCI = configUI.FindControlForPropertyName(PropertyNames.BitDepth); + bitDepthPCI.SetValueDisplayName(TgaBitDepthUIChoices.AutoDetect, PdnResources.GetString("TgaFileType.ConfigUI.BitDepth.AutoDetect.DisplayName")); + bitDepthPCI.SetValueDisplayName(TgaBitDepthUIChoices.Bpp24, PdnResources.GetString("TgaFileType.ConfigUI.BitDepth.Bpp24.DisplayName")); + bitDepthPCI.SetValueDisplayName(TgaBitDepthUIChoices.Bpp32, PdnResources.GetString("TgaFileType.ConfigUI.BitDepth.Bpp32.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.RleCompress, + ControlInfoPropertyNames.DisplayName, + string.Empty); + + configUI.SetPropertyControlValue( + PropertyNames.RleCompress, + ControlInfoPropertyNames.Description, + PdnResources.GetString("TgaFileType.ConfigUI.RleCompress.Description")); + + return configUI; + } + + protected override Document OnLoad(System.IO.Stream input) + { + TgaHeader header = new TgaHeader(input); + bool compressed; + + switch (header.imageType) + { + case TgaType.Map: + case TgaType.Rgb: + case TgaType.Mono: + compressed = false; + break; + + case TgaType.RleMap: + case TgaType.RleRgb: + case TgaType.RleMono: + compressed = true; + break; + + default: + throw new FormatException("unknown TGA image type"); + } + + if (header.imageWidth == 0 || + header.imageHeight == 0 || + header.pixelDepth == 0 || + header.cmapLength > 256) + { + throw new FormatException("bad TGA header"); + } + + if (header.pixelDepth != 8 && + header.pixelDepth != 15 && + header.pixelDepth != 16 && + header.pixelDepth != 24 && + header.pixelDepth != 32) + { + throw new FormatException("bad TGA header: pixelDepth not one of {8, 15, 16, 24, 32}"); + } + + if (header.idLength > 0) + { + input.Position += header.idLength; // skip descriptor + } + + BitmapLayer layer = Layer.CreateBackgroundLayer(header.imageWidth, header.imageHeight); + + try + { + Surface surface = layer.Surface; + surface.Clear((ColorBgra)0xffffffff); + + ColorBgra[] palette = null; + if (header.cmapType != 0) + { + palette = LoadPalette(input, header.cmapLength); + } + + if (header.imageType == TgaType.Mono || + header.imageType == TgaType.RleMono) + { + palette = CreateGrayPalette(); + } + + // Bits 0 - 3 of the image descriptor byte describe number of bits used for alpha channel + // For loading, we won't worry about this. Not all TGA implementations are correct (such + // as older Paint.NET TGA implementations!) and we don't want to lose all their alpha bits. + //int alphaBits = header.imageDesc & 0xf; + + // Bits 4 & 5 of the image descriptor byte control the ordering of the pixels + bool xReversed = ((header.imageDesc & 16) == 16); + bool yReversed = ((header.imageDesc & 32) == 32); + + byte rleLeftOver = 255; // for images with illegal packet boundary + + for (int y = 0; y < header.imageHeight; ++y) + { + MemoryBlock dstRow; + + if (yReversed) + { + dstRow = surface.GetRow(y); + } + else + { + dstRow = surface.GetRow(header.imageHeight - y - 1); + } + + if (compressed) + { + rleLeftOver = ExpandCompressedLine(dstRow, 0, ref header, input, header.imageWidth, y, rleLeftOver, palette); + } + else + { + ExpandUncompressedLine(dstRow, 0, ref header, input, header.imageWidth, y, 0, palette); + } + } + + if (xReversed) + { + MirrorX(surface); + } + + Document document = new Document(surface.Width, surface.Height); + document.Layers.Add(layer); + return document; + } + + catch + { + if (layer != null) + { + layer.Dispose(); + layer = null; + } + + throw; + } + } + + private void MirrorX(Surface surface) + { + for (int y = 0; y < surface.Height; ++y) + { + for (int x = 0; x < surface.Width / 2; ++x) + { + ColorBgra rightSide = surface[surface.Width - x - 1, y]; + surface[surface.Width - x - 1, y] = surface[x, y]; + surface[x, y] = rightSide; + } + } + } + + private ColorBgra[] CreateGrayPalette() + { + ColorBgra[] palette = new ColorBgra[256]; + + for (int i = 0; i < palette.Length; ++i) + { + palette[i] = ColorBgra.FromBgra((byte)i, (byte)i, (byte)i, 255); + } + + return palette; + } + + private ColorBgra[] LoadPalette(Stream input, int count) + { + ColorBgra[] palette = new ColorBgra[count]; + + for (int i = 0; i < palette.Length; ++i) + { + int blue = input.ReadByte(); + if (blue == -1) + { + throw new EndOfStreamException(); + } + + int green = input.ReadByte(); + if (green == -1) + { + throw new EndOfStreamException(); + } + + int red = input.ReadByte(); + if (red == -1) + { + throw new EndOfStreamException(); + } + + palette[i] = ColorBgra.FromBgra((byte)blue, (byte)green, (byte)red, 255); + } + + return palette; + } + + private byte ExpandCompressedLine(MemoryBlock dst, int dstIndex, ref TgaHeader header, Stream input, int width, int y, byte rleLeftOver, ColorBgra[] palette) + { + byte rle; + long filePos = 0; + + int x = 0; + while (x < width) + { + if (rleLeftOver != 255) + { + rle = rleLeftOver; + rleLeftOver = 255; + } + else + { + int byte1 = input.ReadByte(); + + if (byte1 == -1) + { + throw new EndOfStreamException(); + } + else + { + rle = (byte)byte1; + } + } + + if ((rle & 128) != 0) + { + // RLE Encoded packet + rle -= 127; // calculate real repeat count + + if ((x + rle) > width) + { + rleLeftOver = (byte)(128 + (rle - (width - x) - 1)); + filePos = input.Position; + rle = (byte)(width - x); + } + + ColorBgra color = ReadColor(input, header.pixelDepth, palette); + + for (int ix = 0; ix < rle; ++ix) + { + int index = dstIndex + (ix * ColorBgra.SizeOf); + + dst[index] = color[0]; + dst[1 + index] = color[1]; + dst[2 + index] = color[2]; + dst[3 + index] = color[3]; + } + + if (rleLeftOver != 255) + { + input.Position = filePos; + } + } + else + { + // Raw packet + rle += 1; // calculate real repeat count + + if ((x + rle) > width) + { + rleLeftOver = (byte)(rle - (width - x) - 1); + rle = (byte)(width - x); + } + + ExpandUncompressedLine(dst, dstIndex, ref header, input, rle, y, x, palette); + } + + dstIndex += rle * ColorBgra.SizeOf; + x += rle; + } + + return rleLeftOver; + } + + private void ExpandUncompressedLine(MemoryBlock dst, int dstIndex, ref TgaHeader header, Stream input, int width, int y, int xoffset, ColorBgra[] palette) + { + for (int i = 0; i < width; ++i) + { + ColorBgra color = ReadColor(input, header.pixelDepth, palette); + dst[dstIndex] = color[0]; + dst[1 + dstIndex] = color[1]; + dst[2 + dstIndex] = color[2]; + dst[3 + dstIndex] = color[3]; + dstIndex += 4; + } + } + + private ColorBgra ReadColor(Stream input, int pixelDepth, ColorBgra[] palette) + { + ColorBgra color; + + switch (pixelDepth) + { + case 32: + { + long colorInt = Utility.ReadUInt32(input); + + if (colorInt == -1) + { + throw new EndOfStreamException(); + } + + color = ColorBgra.FromUInt32((uint)colorInt); + break; + } + + case 24: + { + int colorInt = Utility.ReadUInt24(input); + + if (colorInt == -1) + { + throw new EndOfStreamException(); + } + + color = ColorBgra.FromUInt32((uint)colorInt); + color.A = 255; + break; + } + + case 15: + case 16: + { + int colorWord = Utility.ReadUInt16(input); + + if (colorWord == -1) + { + throw new EndOfStreamException(); + } + + color = ColorBgra.FromBgra( + (byte)((colorWord >> 7) & 0xf8), + (byte)((colorWord >> 2) & 0xf8), + (byte)((colorWord & 0x1f) * 8), + 255); + + break; + } + + case 8: + { + int colorByte = input.ReadByte(); + + if (colorByte == -1) + { + throw new EndOfStreamException(); + } + + if (colorByte >= palette.Length) + { + throw new FormatException("color index was outside the bounds of the palette"); + } + + color = palette[colorByte]; + break; + } + + default: + throw new FormatException("colorDepth was not one of {8, 15, 16, 24, 32}"); + } + + return color; + } + + internal override void FinalSave( + Document input, + Stream output, + Surface scratchSurface, + int ditherLevel, + SavableBitDepths bitDepth, + PropertyBasedSaveConfigToken token, + ProgressEventHandler progressCallback) + { + bool rleCompress = token.GetProperty(PropertyNames.RleCompress).Value; + SaveTga(scratchSurface, output, bitDepth, rleCompress, progressCallback); + } + + private void SaveTga(Surface input, Stream output, SavableBitDepths bitDepth, bool rleCompress, ProgressEventHandler progressCallback) + { + TgaHeader header = new TgaHeader(); + + header.idLength = 0; + header.cmapType = 0; + header.imageType = rleCompress ? TgaType.RleRgb : TgaType.Rgb; + header.cmapIndex = 0; + header.cmapLength = 0; + header.cmapEntrySize = 0; // if bpp=8, set this to 24 + header.xOrigin = 0; + header.yOrigin = 0; + header.imageWidth = (ushort)input.Width; + header.imageHeight = (ushort)input.Height; + + header.imageDesc = 0; + + switch (bitDepth) + { + case SavableBitDepths.Rgba32: + header.pixelDepth = 32; + header.imageDesc |= 8; + break; + + case SavableBitDepths.Rgb24: + header.pixelDepth = 24; + break; + + default: + throw new InvalidEnumArgumentException("bitDepth", (int)bitDepth, typeof(SavableBitDepths)); + } + + header.Write(output); + + // write palette if doing 8-bit + // ... todo? + + for (int y = input.Height - 1; y >= 0; --y) + { + // non-rle output + if (rleCompress) + { + SaveTgaRowRle(output, input, ref header, y); + } + else + { + SaveTgaRowRaw(output, input, ref header, y); + } + + if (progressCallback != null) + { + progressCallback(this, new ProgressEventArgs(100.0 * ((double)(input.Height - y) / (double)input.Height))); + } + } + } + + private class TgaPacketStateMachine + { + private bool rlePacket = false; + private ColorBgra[] packetColors = new ColorBgra[128]; + private int packetLength; + private Stream output; + private int bitDepth; + + public void Flush() + { + byte header = (byte)((rlePacket ? 128 : 0) + (byte)(packetLength - 1)); + output.WriteByte(header); + + int length = (rlePacket ? 1 : packetLength); + for (int i = 0; i < length; ++i) + { + WriteColor(this.output, packetColors[i], this.bitDepth); + } + + packetLength = 0; + } + + public void Push(ColorBgra color) + { + if (packetLength == 0) + { + // Starting a fresh packet. + rlePacket = false; + packetColors[0] = color; + packetLength = 1; + } + else if (packetLength == 1) + { + // 2nd byte of this packet... decide RLE or non-RLE. + rlePacket = (color == packetColors[0]); + packetColors[1] = color; + packetLength = 2; + } + else if (packetLength == packetColors.Length) + { + // Packet is full. Start a new one. + Flush(); + Push(color); + } + else if (packetLength >= 2 && rlePacket && color != packetColors[packetLength - 1]) + { + // We were filling in an RLE packet, and we got a non-repeated color. + // Emit the current packet and start a new one. + Flush(); + Push(color); + } + else if (packetLength >= 2 && rlePacket && color == packetColors[packetLength - 1]) + { + // We are filling in an RLE packet, and we got another repeated color. + // Add the new color to the current packet. + ++packetLength; + packetColors[packetLength - 1] = color; + } + else if (packetLength >= 2 && !rlePacket && color != packetColors[packetLength - 1]) + { + // We are filling in a raw packet, and we got another random color. + // Add the new color to the current packet. + ++packetLength; + packetColors[packetLength - 1] = color; + } + else if (packetLength >= 2 && !rlePacket && color == packetColors[packetLength - 1]) + { + // We were filling in a raw packet, but we got a repeated color. + // Emit the current packet without its last color, and start a + // new RLE packet that starts with a length of 2. + --packetLength; + Flush(); + Push(color); + Push(color); + } + } + + public TgaPacketStateMachine(Stream output, int bitDepth) + { + this.output = output; + this.bitDepth = bitDepth; + } + } + + private static void SaveTgaRowRle(Stream output, Surface input, ref TgaHeader header, int y) + { + TgaPacketStateMachine machine = new TgaPacketStateMachine(output, header.pixelDepth); + + for (int x = 0; x < input.Width; ++x) + { + machine.Push(input[x, y]); + } + + machine.Flush(); + } + + private static void SaveTgaRowRaw(Stream output, Surface input, ref TgaHeader header, int y) + { + for (int x = 0; x < input.Width; ++x) + { + ColorBgra color = input[x, y]; + WriteColor(output, color, header.pixelDepth); + } + } + + private static void WriteColor(Stream output, ColorBgra color, int bitDepth) + { + switch (bitDepth) + { + case 24: + { + int red = ((color.R * color.A) + (255 * (255 - color.A))) / 255; + int green = ((color.G * color.A) + (255 * (255 - color.A))) / 255; + int blue = ((color.B * color.A) + (255 * (255 - color.A))) / 255; + int colorInt = blue + (green << 8) + (red << 16); + + Utility.WriteUInt24(output, colorInt); + break; + } + + case 32: + Utility.WriteUInt32(output, color.Bgra); + break; + } + } + + public TgaFileType() + : base("TGA", + FileTypeFlags.SavesWithProgress | + FileTypeFlags.SupportsLoading | + FileTypeFlags.SupportsSaving, + new string[] { ".tga" }) + { + } + } +} diff --git a/src/Data/UserBlendOp.cs b/src/Data/UserBlendOp.cs new file mode 100644 index 0000000..4035fa2 --- /dev/null +++ b/src/Data/UserBlendOp.cs @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Abstract base class that all "user" blend ops derive from. + /// These ops are available in the UI for a user to choose from + /// in order to configure the blending properties of a Layer. + /// + /// See UserBlendOps.cs for guidelines on implementation. + /// + [Serializable] + public abstract class UserBlendOp + : BinaryPixelOp + { + public virtual UserBlendOp CreateWithOpacity(int opacity) + { + return this; + } + + public override string ToString() + { + return Utility.GetStaticName(this.GetType()); + } + } +} diff --git a/src/Data/UserBlendOps.Generated.H.cs b/src/Data/UserBlendOps.Generated.H.cs new file mode 100644 index 0000000..27ae0fa --- /dev/null +++ b/src/Data/UserBlendOps.Generated.H.cs @@ -0,0 +1,591 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#include "GeneratedCodeWarning.h" + +using System; + +// The generalized alpha compositing formula, "B OVER A" is: +// C(A,a,B,b) = bB + aA - baA +// where: +// A = background color value +// a = background alpha value +// B = foreground color value +// b = foreground alpha value +// +// However, we need a general formula for composition based on any type of +// blend operation and not just for 'normal' blending. We want multiplicative, +// additive, etc. blend operations. +// +// The generalized alpha compositing formula w.r.t. a replaceable blending +// function is: +// +// G(A,a,B,b,F) = (a - ab)A + (b - ab)B + abF(A, B) +// +// Where F is a function of A and B, or F(A,B), that results in another color +// value. For A OVER B blending, we simply use F(A,B) = B. It can be easily +// shown that the two formulas simplify to the same expression when this F is +// used. +// +// G can be generalized even further to take a function for the other input +// values. This can be useful if one wishes to implement something like +// (1 - B) OVER A blending. +// +// In this reality, F(A,B) is really F(A,B,r). The syntax "r = F(A,B)" is +// the same as "F(A,B,r)" where r is essentially an 'out' parameter. + + +// Multiplies a and b, which are [0,255] as if they were scaled to [0,1], and returns the result in r +// a and b are evaluated once. r is evaluated multiple times. +#define INT_SCALE_MULT(a, b) ((a) * (b) + 0x80) +#define INT_SCALE_DIV(r) ((((r) >> 8) + (r)) >> 8) +#define INT_SCALE(a, b, r) { r = INT_SCALE_MULT(a, b); r = INT_SCALE_DIV(r); } + +#define COMPUTE_ALPHA(a, b, r) { INT_SCALE(a, 255 - (b), r); r += (b); } + +// F(A,B) = blending function for the pixel values +// h(a) = function for loading lhs.A, usually just ID +// j(a) = function for loading rhs.A, usually just ID + +#define BLEND(lhs, rhs, F, h, j) \ + int lhsA; \ + h((lhs).A, lhsA); \ + int rhsA; \ + j((rhs).A, rhsA); \ + int y; \ + INT_SCALE(lhsA, 255 - rhsA, y); \ + int totalA = y + rhsA; \ + uint ret; \ + \ + if (totalA == 0) \ + { \ + ret = 0; \ + } \ + else \ + { \ + int fB; \ + int fG; \ + int fR; \ + \ + F((lhs).B, (rhs).B, fB); \ + F((lhs).G, (rhs).G, fG); \ + F((lhs).R, (rhs).R, fR); \ + \ + int x; \ + INT_SCALE(lhsA, rhsA, x); \ + int z = rhsA - x; \ + \ + int masIndex = totalA * 3; \ + uint taM = masTable[masIndex]; \ + uint taA = masTable[masIndex + 1]; \ + uint taS = masTable[masIndex + 2]; \ + \ + uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); \ + uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); \ + uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); \ + int a; \ + COMPUTE_ALPHA(lhsA, rhsA, a); \ + \ + ret = b + (g << 8) + (r << 16) + ((uint)a << 24); \ + } \ + +#define IMPLEMENT_INSTANCE_FUNCTIONS(F, h, j) \ + public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) \ + { \ + BLEND(lhs, rhs, F, h, j); \ + return ColorBgra.FromUInt32(ret); \ + } \ + \ + public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) \ + { \ + while (length > 0) \ + { \ + BLEND(*dst, *src, F, h, j); \ + dst->Bgra = ret; \ + ++dst; \ + ++src; \ + --length; \ + } \ + } \ + \ + public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) \ + { \ + while (length > 0) \ + { \ + BLEND(*lhs, *rhs, F, h, j); \ + dst->Bgra = ret; \ + ++dst; \ + ++lhs; \ + ++rhs; \ + --length; \ + } \ + } + +#define IMPLEMENT_STATIC_FUNCTIONS(F, h, j) \ + public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) \ + { \ + BLEND(lhs, rhs, F, h, j); \ + return ColorBgra.FromUInt32(ret); \ + } + +#define IMPLEMENT_OP_STATICNAME(NAME) \ + public static string StaticName \ + { \ + get \ + { \ + return PdnResources.GetString("UserBlendOps." + #NAME + "BlendOp.Name"); \ + } \ + } \ + +#define ID(x, r) { r = (x); } +#define ALPHA_WITH_OPACITY(a, r) INT_SCALE(a, this.opacity, r) +#define APPLY_OPACITY_ADAPTER(a, r) { r = ApplyOpacity(a); } + +#define DEFINE_OP(NAME, PROTECTION, F, h, j) \ + [Serializable] \ + PROTECTION sealed class NAME##BlendOp \ + : UserBlendOp \ + { \ + IMPLEMENT_OP_STATICNAME(NAME) \ + IMPLEMENT_INSTANCE_FUNCTIONS(F, h, j) \ + IMPLEMENT_STATIC_FUNCTIONS(F, h, j) \ + \ + public override UserBlendOp CreateWithOpacity(int opacity) \ + { \ + return new NAME##BlendOpWithOpacity(opacity); \ + } \ + \ + private sealed class NAME##BlendOpWithOpacity \ + : UserBlendOp \ + { \ + private int opacity; \ + \ + private byte ApplyOpacity(byte a) \ + { \ + int r; \ + j(a, r); \ + ALPHA_WITH_OPACITY(r, r); \ + return (byte)r; \ + } \ + \ + IMPLEMENT_OP_STATICNAME(NAME) \ + \ + IMPLEMENT_INSTANCE_FUNCTIONS(F, h, APPLY_OPACITY_ADAPTER) \ + \ + public NAME##BlendOpWithOpacity(int opacity) \ + { \ + if (this.opacity < 0 || this.opacity > 255) \ + { \ + throw new ArgumentOutOfRangeException(); \ + } \ + \ + this.opacity = opacity; \ + } \ + } \ + } + +#define DEFINE_STANDARD_OP(NAME, F) DEFINE_OP(NAME, public, F, ID, ID) + +#define OVER(A, B, r) \ +{ \ + r = (B); \ +} + +#define MULTIPLY(A, B, r) \ +{ \ + INT_SCALE((A), (B), r); \ +} + +#define MIN(A, B, r) \ +{ \ + r = Math.Min(A, B); \ +} + +#define MAX(A, B, r) \ +{ \ + r = Math.Max(A, B); \ +} + +#define SATURATE(A, B, r) \ +{ \ + r = Math.Min(255, (A) + (B)); \ +} + +// n DIV d +#define INT_DIV(n, d, r) \ +{ \ + int i = (d) * 3; \ + uint M = masTable[i]; \ + uint A = masTable[i + 1]; \ + uint S = masTable[i + 2]; \ + r = (int)(((n * M) + A) >> (int)S); \ +} + +//{ r = (((B) == 0) ? 0 : Math.Max(0, (255 - (((255 - (A)) * 255) / (B))))); } +#define COLORBURN(A, B, r) \ +{ \ + if ((B) == 0) \ + { \ + r = 0; \ + } \ + else \ + { \ + INT_DIV(((255 - (A)) * 255), (B), r); \ + r = 255 - r; \ + r = Math.Max(0, r); \ + } \ +} + +// { r = ((B) == 255 ? 255 : Math.Min(255, ((A) * 255) / (255 - (B)))); } +#define COLORDODGE(A, B, r) \ +{ \ + if ((B) == 255) \ + { \ + r = 255; \ + } \ + else \ + { \ + INT_DIV(((A) * 255), (255 - (B)), r); \ + r = Math.Min(255, r); \ + } \ +} + +// r = { (((B) == 255) ? 255 : Math.Min(255, ((A) * (A)) / (255 - (B)))); } +#define REFLECT(A, B, r) \ +{ \ + if ((B) == 255) \ + { \ + r = 255; \ + } \ + else \ + { \ + INT_DIV((A) * (A), 255 - (B), r); \ + r = Math.Min(255, r); \ + } \ +} + +#define GLOW(A, B, r) REFLECT(B, A, r) + +#define OVERLAY(A, B, r) \ +{ \ + if ((A) < 128) \ + { \ + INT_SCALE(2 * (A), (B), r); \ + } \ + else \ + { \ + INT_SCALE(2 * (255 - (A)), 255 - (B), r); \ + r = 255 - r; \ + } \ +} + +#define DIFFERENCE(A, B, r) \ +{ \ + r = Math.Abs((B) - (A)); \ +} + +#define NEGATION(A, B, r) \ +{ \ + r = (255 - Math.Abs(255 - (A) - (B))); \ +} + +//{ r = ((B) + (A) - (((B) * (A)) / 255)); } +#define SCREEN(A, B, r) \ +{ \ + INT_SCALE((B), (A), r); \ + r = (B) + (A) - r; \ +} + +#define XOR(A, B, r) \ +{ \ + r = ((A) ^ (B)); \ +} + +namespace PaintDotNet +{ + partial class UserBlendOps + { + // i = z * 3; + // (x / z) = ((x * masTable[i]) + masTable[i + 1]) >> masTable[i + 2) + private static readonly uint[] masTable = + { + 0x00000000, 0x00000000, 0, // 0 + 0x00000001, 0x00000000, 0, // 1 + 0x00000001, 0x00000000, 1, // 2 + 0xAAAAAAAB, 0x00000000, 33, // 3 + 0x00000001, 0x00000000, 2, // 4 + 0xCCCCCCCD, 0x00000000, 34, // 5 + 0xAAAAAAAB, 0x00000000, 34, // 6 + 0x49249249, 0x49249249, 33, // 7 + 0x00000001, 0x00000000, 3, // 8 + 0x38E38E39, 0x00000000, 33, // 9 + 0xCCCCCCCD, 0x00000000, 35, // 10 + 0xBA2E8BA3, 0x00000000, 35, // 11 + 0xAAAAAAAB, 0x00000000, 35, // 12 + 0x4EC4EC4F, 0x00000000, 34, // 13 + 0x49249249, 0x49249249, 34, // 14 + 0x88888889, 0x00000000, 35, // 15 + 0x00000001, 0x00000000, 4, // 16 + 0xF0F0F0F1, 0x00000000, 36, // 17 + 0x38E38E39, 0x00000000, 34, // 18 + 0xD79435E5, 0xD79435E5, 36, // 19 + 0xCCCCCCCD, 0x00000000, 36, // 20 + 0xC30C30C3, 0xC30C30C3, 36, // 21 + 0xBA2E8BA3, 0x00000000, 36, // 22 + 0xB21642C9, 0x00000000, 36, // 23 + 0xAAAAAAAB, 0x00000000, 36, // 24 + 0x51EB851F, 0x00000000, 35, // 25 + 0x4EC4EC4F, 0x00000000, 35, // 26 + 0x97B425ED, 0x97B425ED, 36, // 27 + 0x49249249, 0x49249249, 35, // 28 + 0x8D3DCB09, 0x00000000, 36, // 29 + 0x88888889, 0x00000000, 36, // 30 + 0x42108421, 0x42108421, 35, // 31 + 0x00000001, 0x00000000, 5, // 32 + 0x3E0F83E1, 0x00000000, 35, // 33 + 0xF0F0F0F1, 0x00000000, 37, // 34 + 0x75075075, 0x75075075, 36, // 35 + 0x38E38E39, 0x00000000, 35, // 36 + 0x6EB3E453, 0x6EB3E453, 36, // 37 + 0xD79435E5, 0xD79435E5, 37, // 38 + 0x69069069, 0x69069069, 36, // 39 + 0xCCCCCCCD, 0x00000000, 37, // 40 + 0xC7CE0C7D, 0x00000000, 37, // 41 + 0xC30C30C3, 0xC30C30C3, 37, // 42 + 0x2FA0BE83, 0x00000000, 35, // 43 + 0xBA2E8BA3, 0x00000000, 37, // 44 + 0x5B05B05B, 0x5B05B05B, 36, // 45 + 0xB21642C9, 0x00000000, 37, // 46 + 0xAE4C415D, 0x00000000, 37, // 47 + 0xAAAAAAAB, 0x00000000, 37, // 48 + 0x5397829D, 0x00000000, 36, // 49 + 0x51EB851F, 0x00000000, 36, // 50 + 0xA0A0A0A1, 0x00000000, 37, // 51 + 0x4EC4EC4F, 0x00000000, 36, // 52 + 0x9A90E7D9, 0x9A90E7D9, 37, // 53 + 0x97B425ED, 0x97B425ED, 37, // 54 + 0x94F2094F, 0x94F2094F, 37, // 55 + 0x49249249, 0x49249249, 36, // 56 + 0x47DC11F7, 0x47DC11F7, 36, // 57 + 0x8D3DCB09, 0x00000000, 37, // 58 + 0x22B63CBF, 0x00000000, 35, // 59 + 0x88888889, 0x00000000, 37, // 60 + 0x4325C53F, 0x00000000, 36, // 61 + 0x42108421, 0x42108421, 36, // 62 + 0x41041041, 0x41041041, 36, // 63 + 0x00000001, 0x00000000, 6, // 64 + 0xFC0FC0FD, 0x00000000, 38, // 65 + 0x3E0F83E1, 0x00000000, 36, // 66 + 0x07A44C6B, 0x00000000, 33, // 67 + 0xF0F0F0F1, 0x00000000, 38, // 68 + 0x76B981DB, 0x00000000, 37, // 69 + 0x75075075, 0x75075075, 37, // 70 + 0xE6C2B449, 0x00000000, 38, // 71 + 0x38E38E39, 0x00000000, 36, // 72 + 0x381C0E07, 0x381C0E07, 36, // 73 + 0x6EB3E453, 0x6EB3E453, 37, // 74 + 0x1B4E81B5, 0x00000000, 35, // 75 + 0xD79435E5, 0xD79435E5, 38, // 76 + 0x3531DEC1, 0x00000000, 36, // 77 + 0x69069069, 0x69069069, 37, // 78 + 0xCF6474A9, 0x00000000, 38, // 79 + 0xCCCCCCCD, 0x00000000, 38, // 80 + 0xCA4587E7, 0x00000000, 38, // 81 + 0xC7CE0C7D, 0x00000000, 38, // 82 + 0x3159721F, 0x00000000, 36, // 83 + 0xC30C30C3, 0xC30C30C3, 38, // 84 + 0xC0C0C0C1, 0x00000000, 38, // 85 + 0x2FA0BE83, 0x00000000, 36, // 86 + 0x2F149903, 0x00000000, 36, // 87 + 0xBA2E8BA3, 0x00000000, 38, // 88 + 0xB81702E1, 0x00000000, 38, // 89 + 0x5B05B05B, 0x5B05B05B, 37, // 90 + 0x2D02D02D, 0x2D02D02D, 36, // 91 + 0xB21642C9, 0x00000000, 38, // 92 + 0xB02C0B03, 0x00000000, 38, // 93 + 0xAE4C415D, 0x00000000, 38, // 94 + 0x2B1DA461, 0x2B1DA461, 36, // 95 + 0xAAAAAAAB, 0x00000000, 38, // 96 + 0xA8E83F57, 0xA8E83F57, 38, // 97 + 0x5397829D, 0x00000000, 37, // 98 + 0xA57EB503, 0x00000000, 38, // 99 + 0x51EB851F, 0x00000000, 37, // 100 + 0xA237C32B, 0xA237C32B, 38, // 101 + 0xA0A0A0A1, 0x00000000, 38, // 102 + 0x9F1165E7, 0x9F1165E7, 38, // 103 + 0x4EC4EC4F, 0x00000000, 37, // 104 + 0x27027027, 0x27027027, 36, // 105 + 0x9A90E7D9, 0x9A90E7D9, 38, // 106 + 0x991F1A51, 0x991F1A51, 38, // 107 + 0x97B425ED, 0x97B425ED, 38, // 108 + 0x2593F69B, 0x2593F69B, 36, // 109 + 0x94F2094F, 0x94F2094F, 38, // 110 + 0x24E6A171, 0x24E6A171, 36, // 111 + 0x49249249, 0x49249249, 37, // 112 + 0x90FDBC09, 0x90FDBC09, 38, // 113 + 0x47DC11F7, 0x47DC11F7, 37, // 114 + 0x8E78356D, 0x8E78356D, 38, // 115 + 0x8D3DCB09, 0x00000000, 38, // 116 + 0x23023023, 0x23023023, 36, // 117 + 0x22B63CBF, 0x00000000, 36, // 118 + 0x44D72045, 0x00000000, 37, // 119 + 0x88888889, 0x00000000, 38, // 120 + 0x8767AB5F, 0x8767AB5F, 38, // 121 + 0x4325C53F, 0x00000000, 37, // 122 + 0x85340853, 0x85340853, 38, // 123 + 0x42108421, 0x42108421, 37, // 124 + 0x10624DD3, 0x00000000, 35, // 125 + 0x41041041, 0x41041041, 37, // 126 + 0x10204081, 0x10204081, 35, // 127 + 0x00000001, 0x00000000, 7, // 128 + 0x0FE03F81, 0x00000000, 35, // 129 + 0xFC0FC0FD, 0x00000000, 39, // 130 + 0xFA232CF3, 0x00000000, 39, // 131 + 0x3E0F83E1, 0x00000000, 37, // 132 + 0xF6603D99, 0x00000000, 39, // 133 + 0x07A44C6B, 0x00000000, 34, // 134 + 0xF2B9D649, 0x00000000, 39, // 135 + 0xF0F0F0F1, 0x00000000, 39, // 136 + 0x077975B9, 0x00000000, 34, // 137 + 0x76B981DB, 0x00000000, 38, // 138 + 0x75DED953, 0x00000000, 38, // 139 + 0x75075075, 0x75075075, 38, // 140 + 0x3A196B1F, 0x00000000, 37, // 141 + 0xE6C2B449, 0x00000000, 39, // 142 + 0xE525982B, 0x00000000, 39, // 143 + 0x38E38E39, 0x00000000, 37, // 144 + 0xE1FC780F, 0x00000000, 39, // 145 + 0x381C0E07, 0x381C0E07, 37, // 146 + 0xDEE95C4D, 0x00000000, 39, // 147 + 0x6EB3E453, 0x6EB3E453, 38, // 148 + 0xDBEB61EF, 0x00000000, 39, // 149 + 0x1B4E81B5, 0x00000000, 36, // 150 + 0x36406C81, 0x00000000, 37, // 151 + 0xD79435E5, 0xD79435E5, 39, // 152 + 0xD62B80D7, 0x00000000, 39, // 153 + 0x3531DEC1, 0x00000000, 37, // 154 + 0xD3680D37, 0x00000000, 39, // 155 + 0x69069069, 0x69069069, 38, // 156 + 0x342DA7F3, 0x00000000, 37, // 157 + 0xCF6474A9, 0x00000000, 39, // 158 + 0xCE168A77, 0xCE168A77, 39, // 159 + 0xCCCCCCCD, 0x00000000, 39, // 160 + 0xCB8727C1, 0x00000000, 39, // 161 + 0xCA4587E7, 0x00000000, 39, // 162 + 0xC907DA4F, 0x00000000, 39, // 163 + 0xC7CE0C7D, 0x00000000, 39, // 164 + 0x634C0635, 0x00000000, 38, // 165 + 0x3159721F, 0x00000000, 37, // 166 + 0x621B97C3, 0x00000000, 38, // 167 + 0xC30C30C3, 0xC30C30C3, 39, // 168 + 0x60F25DEB, 0x00000000, 38, // 169 + 0xC0C0C0C1, 0x00000000, 39, // 170 + 0x17F405FD, 0x17F405FD, 36, // 171 + 0x2FA0BE83, 0x00000000, 37, // 172 + 0xBD691047, 0xBD691047, 39, // 173 + 0x2F149903, 0x00000000, 37, // 174 + 0x5D9F7391, 0x00000000, 38, // 175 + 0xBA2E8BA3, 0x00000000, 39, // 176 + 0x5C90A1FD, 0x5C90A1FD, 38, // 177 + 0xB81702E1, 0x00000000, 39, // 178 + 0x5B87DDAD, 0x5B87DDAD, 38, // 179 + 0x5B05B05B, 0x5B05B05B, 38, // 180 + 0xB509E68B, 0x00000000, 39, // 181 + 0x2D02D02D, 0x2D02D02D, 37, // 182 + 0xB30F6353, 0x00000000, 39, // 183 + 0xB21642C9, 0x00000000, 39, // 184 + 0x1623FA77, 0x1623FA77, 36, // 185 + 0xB02C0B03, 0x00000000, 39, // 186 + 0xAF3ADDC7, 0x00000000, 39, // 187 + 0xAE4C415D, 0x00000000, 39, // 188 + 0x15AC056B, 0x15AC056B, 36, // 189 + 0x2B1DA461, 0x2B1DA461, 37, // 190 + 0xAB8F69E3, 0x00000000, 39, // 191 + 0xAAAAAAAB, 0x00000000, 39, // 192 + 0x15390949, 0x00000000, 36, // 193 + 0xA8E83F57, 0xA8E83F57, 39, // 194 + 0x15015015, 0x15015015, 36, // 195 + 0x5397829D, 0x00000000, 38, // 196 + 0xA655C439, 0xA655C439, 39, // 197 + 0xA57EB503, 0x00000000, 39, // 198 + 0x5254E78F, 0x00000000, 38, // 199 + 0x51EB851F, 0x00000000, 38, // 200 + 0x028C1979, 0x00000000, 33, // 201 + 0xA237C32B, 0xA237C32B, 39, // 202 + 0xA16B312F, 0x00000000, 39, // 203 + 0xA0A0A0A1, 0x00000000, 39, // 204 + 0x4FEC04FF, 0x00000000, 38, // 205 + 0x9F1165E7, 0x9F1165E7, 39, // 206 + 0x27932B49, 0x00000000, 37, // 207 + 0x4EC4EC4F, 0x00000000, 38, // 208 + 0x9CC8E161, 0x00000000, 39, // 209 + 0x27027027, 0x27027027, 37, // 210 + 0x9B4C6F9F, 0x00000000, 39, // 211 + 0x9A90E7D9, 0x9A90E7D9, 39, // 212 + 0x99D722DB, 0x00000000, 39, // 213 + 0x991F1A51, 0x991F1A51, 39, // 214 + 0x4C346405, 0x00000000, 38, // 215 + 0x97B425ED, 0x97B425ED, 39, // 216 + 0x4B809701, 0x4B809701, 38, // 217 + 0x2593F69B, 0x2593F69B, 37, // 218 + 0x12B404AD, 0x12B404AD, 36, // 219 + 0x94F2094F, 0x94F2094F, 39, // 220 + 0x25116025, 0x25116025, 37, // 221 + 0x24E6A171, 0x24E6A171, 37, // 222 + 0x24BC44E1, 0x24BC44E1, 37, // 223 + 0x49249249, 0x49249249, 38, // 224 + 0x91A2B3C5, 0x00000000, 39, // 225 + 0x90FDBC09, 0x90FDBC09, 39, // 226 + 0x905A3863, 0x905A3863, 39, // 227 + 0x47DC11F7, 0x47DC11F7, 38, // 228 + 0x478BBCED, 0x00000000, 38, // 229 + 0x8E78356D, 0x8E78356D, 39, // 230 + 0x46ED2901, 0x46ED2901, 38, // 231 + 0x8D3DCB09, 0x00000000, 39, // 232 + 0x2328A701, 0x2328A701, 37, // 233 + 0x23023023, 0x23023023, 37, // 234 + 0x45B81A25, 0x45B81A25, 38, // 235 + 0x22B63CBF, 0x00000000, 37, // 236 + 0x08A42F87, 0x08A42F87, 35, // 237 + 0x44D72045, 0x00000000, 38, // 238 + 0x891AC73B, 0x00000000, 39, // 239 + 0x88888889, 0x00000000, 39, // 240 + 0x10FEF011, 0x00000000, 36, // 241 + 0x8767AB5F, 0x8767AB5F, 39, // 242 + 0x86D90545, 0x00000000, 39, // 243 + 0x4325C53F, 0x00000000, 38, // 244 + 0x85BF3761, 0x85BF3761, 39, // 245 + 0x85340853, 0x85340853, 39, // 246 + 0x10953F39, 0x10953F39, 36, // 247 + 0x42108421, 0x42108421, 38, // 248 + 0x41CC9829, 0x41CC9829, 38, // 249 + 0x10624DD3, 0x00000000, 36, // 250 + 0x828CBFBF, 0x00000000, 39, // 251 + 0x41041041, 0x41041041, 38, // 252 + 0x81848DA9, 0x00000000, 39, // 253 + 0x10204081, 0x10204081, 36, // 254 + 0x80808081, 0x00000000, 39 // 255 + }; + + DEFINE_STANDARD_OP(Normal, OVER) + DEFINE_STANDARD_OP(Multiply, MULTIPLY) + DEFINE_STANDARD_OP(Additive, SATURATE) + DEFINE_STANDARD_OP(ColorBurn, COLORBURN) + DEFINE_STANDARD_OP(ColorDodge, COLORDODGE) + DEFINE_STANDARD_OP(Reflect, REFLECT) + DEFINE_STANDARD_OP(Glow, GLOW) + DEFINE_STANDARD_OP(Overlay, OVERLAY) + DEFINE_STANDARD_OP(Difference, DIFFERENCE) + DEFINE_STANDARD_OP(Negation, NEGATION) + DEFINE_STANDARD_OP(Lighten, MAX) + DEFINE_STANDARD_OP(Darken, MIN) + DEFINE_STANDARD_OP(Screen, SCREEN) + DEFINE_STANDARD_OP(Xor, XOR) + } +} diff --git a/src/Data/UserBlendOps.Generated.cs b/src/Data/UserBlendOps.Generated.cs new file mode 100644 index 0000000..a7d592d --- /dev/null +++ b/src/Data/UserBlendOps.Generated.cs @@ -0,0 +1,594 @@ + +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + + +// This file is generated at compile time. DO NOT MODIFY! + + +using System; + +// The generalized alpha compositing formula, "B OVER A" is: +// C(A,a,B,b) = bB + aA - baA +// where: +// A = background color value +// a = background alpha value +// B = foreground color value +// b = foreground alpha value +// +// However, we need a general formula for composition based on any type of +// blend operation and not just for 'normal' blending. We want multiplicative, +// additive, etc. blend operations. +// +// The generalized alpha compositing formula w.r.t. a replaceable blending +// function is: +// +// G(A,a,B,b,F) = (a - ab)A + (b - ab)B + abF(A, B) +// +// Where F is a function of A and B, or F(A,B), that results in another color +// value. For A OVER B blending, we simply use F(A,B) = B. It can be easily +// shown that the two formulas simplify to the same expression when this F is +// used. +// +// G can be generalized even further to take a function for the other input +// values. This can be useful if one wishes to implement something like +// (1 - B) OVER A blending. +// +// In this reality, F(A,B) is really F(A,B,r). The syntax "r = F(A,B)" is +// the same as "F(A,B,r)" where r is essentially an 'out' parameter. + + +// Multiplies a and b, which are [0,255] as if they were scaled to [0,1], and returns the result in r +// a and b are evaluated once. r is evaluated multiple times. + + + + + + +// F(A,B) = blending function for the pixel values +// h(a) = function for loading lhs.A, usually just ID +// j(a) = function for loading rhs.A, usually just ID + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +// n DIV d + + + + + + + + + +//{ r = (((B) == 0) ? 0 : Math.Max(0, (255 - (((255 - (A)) * 255) / (B))))); } + + + + + + + + + + + + + + +// { r = ((B) == 255 ? 255 : Math.Min(255, ((A) * 255) / (255 - (B)))); } + + + + + + + + + + + + + +// r = { (((B) == 255) ? 255 : Math.Min(255, ((A) * (A)) / (255 - (B)))); } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//{ r = ((B) + (A) - (((B) * (A)) / 255)); } + + + + + + + + + + + +namespace PaintDotNet +{ + partial class UserBlendOps + { + // i = z * 3; + // (x / z) = ((x * masTable[i]) + masTable[i + 1]) >> masTable[i + 2) + private static readonly uint[] masTable = + { + 0x00000000, 0x00000000, 0, // 0 + 0x00000001, 0x00000000, 0, // 1 + 0x00000001, 0x00000000, 1, // 2 + 0xAAAAAAAB, 0x00000000, 33, // 3 + 0x00000001, 0x00000000, 2, // 4 + 0xCCCCCCCD, 0x00000000, 34, // 5 + 0xAAAAAAAB, 0x00000000, 34, // 6 + 0x49249249, 0x49249249, 33, // 7 + 0x00000001, 0x00000000, 3, // 8 + 0x38E38E39, 0x00000000, 33, // 9 + 0xCCCCCCCD, 0x00000000, 35, // 10 + 0xBA2E8BA3, 0x00000000, 35, // 11 + 0xAAAAAAAB, 0x00000000, 35, // 12 + 0x4EC4EC4F, 0x00000000, 34, // 13 + 0x49249249, 0x49249249, 34, // 14 + 0x88888889, 0x00000000, 35, // 15 + 0x00000001, 0x00000000, 4, // 16 + 0xF0F0F0F1, 0x00000000, 36, // 17 + 0x38E38E39, 0x00000000, 34, // 18 + 0xD79435E5, 0xD79435E5, 36, // 19 + 0xCCCCCCCD, 0x00000000, 36, // 20 + 0xC30C30C3, 0xC30C30C3, 36, // 21 + 0xBA2E8BA3, 0x00000000, 36, // 22 + 0xB21642C9, 0x00000000, 36, // 23 + 0xAAAAAAAB, 0x00000000, 36, // 24 + 0x51EB851F, 0x00000000, 35, // 25 + 0x4EC4EC4F, 0x00000000, 35, // 26 + 0x97B425ED, 0x97B425ED, 36, // 27 + 0x49249249, 0x49249249, 35, // 28 + 0x8D3DCB09, 0x00000000, 36, // 29 + 0x88888889, 0x00000000, 36, // 30 + 0x42108421, 0x42108421, 35, // 31 + 0x00000001, 0x00000000, 5, // 32 + 0x3E0F83E1, 0x00000000, 35, // 33 + 0xF0F0F0F1, 0x00000000, 37, // 34 + 0x75075075, 0x75075075, 36, // 35 + 0x38E38E39, 0x00000000, 35, // 36 + 0x6EB3E453, 0x6EB3E453, 36, // 37 + 0xD79435E5, 0xD79435E5, 37, // 38 + 0x69069069, 0x69069069, 36, // 39 + 0xCCCCCCCD, 0x00000000, 37, // 40 + 0xC7CE0C7D, 0x00000000, 37, // 41 + 0xC30C30C3, 0xC30C30C3, 37, // 42 + 0x2FA0BE83, 0x00000000, 35, // 43 + 0xBA2E8BA3, 0x00000000, 37, // 44 + 0x5B05B05B, 0x5B05B05B, 36, // 45 + 0xB21642C9, 0x00000000, 37, // 46 + 0xAE4C415D, 0x00000000, 37, // 47 + 0xAAAAAAAB, 0x00000000, 37, // 48 + 0x5397829D, 0x00000000, 36, // 49 + 0x51EB851F, 0x00000000, 36, // 50 + 0xA0A0A0A1, 0x00000000, 37, // 51 + 0x4EC4EC4F, 0x00000000, 36, // 52 + 0x9A90E7D9, 0x9A90E7D9, 37, // 53 + 0x97B425ED, 0x97B425ED, 37, // 54 + 0x94F2094F, 0x94F2094F, 37, // 55 + 0x49249249, 0x49249249, 36, // 56 + 0x47DC11F7, 0x47DC11F7, 36, // 57 + 0x8D3DCB09, 0x00000000, 37, // 58 + 0x22B63CBF, 0x00000000, 35, // 59 + 0x88888889, 0x00000000, 37, // 60 + 0x4325C53F, 0x00000000, 36, // 61 + 0x42108421, 0x42108421, 36, // 62 + 0x41041041, 0x41041041, 36, // 63 + 0x00000001, 0x00000000, 6, // 64 + 0xFC0FC0FD, 0x00000000, 38, // 65 + 0x3E0F83E1, 0x00000000, 36, // 66 + 0x07A44C6B, 0x00000000, 33, // 67 + 0xF0F0F0F1, 0x00000000, 38, // 68 + 0x76B981DB, 0x00000000, 37, // 69 + 0x75075075, 0x75075075, 37, // 70 + 0xE6C2B449, 0x00000000, 38, // 71 + 0x38E38E39, 0x00000000, 36, // 72 + 0x381C0E07, 0x381C0E07, 36, // 73 + 0x6EB3E453, 0x6EB3E453, 37, // 74 + 0x1B4E81B5, 0x00000000, 35, // 75 + 0xD79435E5, 0xD79435E5, 38, // 76 + 0x3531DEC1, 0x00000000, 36, // 77 + 0x69069069, 0x69069069, 37, // 78 + 0xCF6474A9, 0x00000000, 38, // 79 + 0xCCCCCCCD, 0x00000000, 38, // 80 + 0xCA4587E7, 0x00000000, 38, // 81 + 0xC7CE0C7D, 0x00000000, 38, // 82 + 0x3159721F, 0x00000000, 36, // 83 + 0xC30C30C3, 0xC30C30C3, 38, // 84 + 0xC0C0C0C1, 0x00000000, 38, // 85 + 0x2FA0BE83, 0x00000000, 36, // 86 + 0x2F149903, 0x00000000, 36, // 87 + 0xBA2E8BA3, 0x00000000, 38, // 88 + 0xB81702E1, 0x00000000, 38, // 89 + 0x5B05B05B, 0x5B05B05B, 37, // 90 + 0x2D02D02D, 0x2D02D02D, 36, // 91 + 0xB21642C9, 0x00000000, 38, // 92 + 0xB02C0B03, 0x00000000, 38, // 93 + 0xAE4C415D, 0x00000000, 38, // 94 + 0x2B1DA461, 0x2B1DA461, 36, // 95 + 0xAAAAAAAB, 0x00000000, 38, // 96 + 0xA8E83F57, 0xA8E83F57, 38, // 97 + 0x5397829D, 0x00000000, 37, // 98 + 0xA57EB503, 0x00000000, 38, // 99 + 0x51EB851F, 0x00000000, 37, // 100 + 0xA237C32B, 0xA237C32B, 38, // 101 + 0xA0A0A0A1, 0x00000000, 38, // 102 + 0x9F1165E7, 0x9F1165E7, 38, // 103 + 0x4EC4EC4F, 0x00000000, 37, // 104 + 0x27027027, 0x27027027, 36, // 105 + 0x9A90E7D9, 0x9A90E7D9, 38, // 106 + 0x991F1A51, 0x991F1A51, 38, // 107 + 0x97B425ED, 0x97B425ED, 38, // 108 + 0x2593F69B, 0x2593F69B, 36, // 109 + 0x94F2094F, 0x94F2094F, 38, // 110 + 0x24E6A171, 0x24E6A171, 36, // 111 + 0x49249249, 0x49249249, 37, // 112 + 0x90FDBC09, 0x90FDBC09, 38, // 113 + 0x47DC11F7, 0x47DC11F7, 37, // 114 + 0x8E78356D, 0x8E78356D, 38, // 115 + 0x8D3DCB09, 0x00000000, 38, // 116 + 0x23023023, 0x23023023, 36, // 117 + 0x22B63CBF, 0x00000000, 36, // 118 + 0x44D72045, 0x00000000, 37, // 119 + 0x88888889, 0x00000000, 38, // 120 + 0x8767AB5F, 0x8767AB5F, 38, // 121 + 0x4325C53F, 0x00000000, 37, // 122 + 0x85340853, 0x85340853, 38, // 123 + 0x42108421, 0x42108421, 37, // 124 + 0x10624DD3, 0x00000000, 35, // 125 + 0x41041041, 0x41041041, 37, // 126 + 0x10204081, 0x10204081, 35, // 127 + 0x00000001, 0x00000000, 7, // 128 + 0x0FE03F81, 0x00000000, 35, // 129 + 0xFC0FC0FD, 0x00000000, 39, // 130 + 0xFA232CF3, 0x00000000, 39, // 131 + 0x3E0F83E1, 0x00000000, 37, // 132 + 0xF6603D99, 0x00000000, 39, // 133 + 0x07A44C6B, 0x00000000, 34, // 134 + 0xF2B9D649, 0x00000000, 39, // 135 + 0xF0F0F0F1, 0x00000000, 39, // 136 + 0x077975B9, 0x00000000, 34, // 137 + 0x76B981DB, 0x00000000, 38, // 138 + 0x75DED953, 0x00000000, 38, // 139 + 0x75075075, 0x75075075, 38, // 140 + 0x3A196B1F, 0x00000000, 37, // 141 + 0xE6C2B449, 0x00000000, 39, // 142 + 0xE525982B, 0x00000000, 39, // 143 + 0x38E38E39, 0x00000000, 37, // 144 + 0xE1FC780F, 0x00000000, 39, // 145 + 0x381C0E07, 0x381C0E07, 37, // 146 + 0xDEE95C4D, 0x00000000, 39, // 147 + 0x6EB3E453, 0x6EB3E453, 38, // 148 + 0xDBEB61EF, 0x00000000, 39, // 149 + 0x1B4E81B5, 0x00000000, 36, // 150 + 0x36406C81, 0x00000000, 37, // 151 + 0xD79435E5, 0xD79435E5, 39, // 152 + 0xD62B80D7, 0x00000000, 39, // 153 + 0x3531DEC1, 0x00000000, 37, // 154 + 0xD3680D37, 0x00000000, 39, // 155 + 0x69069069, 0x69069069, 38, // 156 + 0x342DA7F3, 0x00000000, 37, // 157 + 0xCF6474A9, 0x00000000, 39, // 158 + 0xCE168A77, 0xCE168A77, 39, // 159 + 0xCCCCCCCD, 0x00000000, 39, // 160 + 0xCB8727C1, 0x00000000, 39, // 161 + 0xCA4587E7, 0x00000000, 39, // 162 + 0xC907DA4F, 0x00000000, 39, // 163 + 0xC7CE0C7D, 0x00000000, 39, // 164 + 0x634C0635, 0x00000000, 38, // 165 + 0x3159721F, 0x00000000, 37, // 166 + 0x621B97C3, 0x00000000, 38, // 167 + 0xC30C30C3, 0xC30C30C3, 39, // 168 + 0x60F25DEB, 0x00000000, 38, // 169 + 0xC0C0C0C1, 0x00000000, 39, // 170 + 0x17F405FD, 0x17F405FD, 36, // 171 + 0x2FA0BE83, 0x00000000, 37, // 172 + 0xBD691047, 0xBD691047, 39, // 173 + 0x2F149903, 0x00000000, 37, // 174 + 0x5D9F7391, 0x00000000, 38, // 175 + 0xBA2E8BA3, 0x00000000, 39, // 176 + 0x5C90A1FD, 0x5C90A1FD, 38, // 177 + 0xB81702E1, 0x00000000, 39, // 178 + 0x5B87DDAD, 0x5B87DDAD, 38, // 179 + 0x5B05B05B, 0x5B05B05B, 38, // 180 + 0xB509E68B, 0x00000000, 39, // 181 + 0x2D02D02D, 0x2D02D02D, 37, // 182 + 0xB30F6353, 0x00000000, 39, // 183 + 0xB21642C9, 0x00000000, 39, // 184 + 0x1623FA77, 0x1623FA77, 36, // 185 + 0xB02C0B03, 0x00000000, 39, // 186 + 0xAF3ADDC7, 0x00000000, 39, // 187 + 0xAE4C415D, 0x00000000, 39, // 188 + 0x15AC056B, 0x15AC056B, 36, // 189 + 0x2B1DA461, 0x2B1DA461, 37, // 190 + 0xAB8F69E3, 0x00000000, 39, // 191 + 0xAAAAAAAB, 0x00000000, 39, // 192 + 0x15390949, 0x00000000, 36, // 193 + 0xA8E83F57, 0xA8E83F57, 39, // 194 + 0x15015015, 0x15015015, 36, // 195 + 0x5397829D, 0x00000000, 38, // 196 + 0xA655C439, 0xA655C439, 39, // 197 + 0xA57EB503, 0x00000000, 39, // 198 + 0x5254E78F, 0x00000000, 38, // 199 + 0x51EB851F, 0x00000000, 38, // 200 + 0x028C1979, 0x00000000, 33, // 201 + 0xA237C32B, 0xA237C32B, 39, // 202 + 0xA16B312F, 0x00000000, 39, // 203 + 0xA0A0A0A1, 0x00000000, 39, // 204 + 0x4FEC04FF, 0x00000000, 38, // 205 + 0x9F1165E7, 0x9F1165E7, 39, // 206 + 0x27932B49, 0x00000000, 37, // 207 + 0x4EC4EC4F, 0x00000000, 38, // 208 + 0x9CC8E161, 0x00000000, 39, // 209 + 0x27027027, 0x27027027, 37, // 210 + 0x9B4C6F9F, 0x00000000, 39, // 211 + 0x9A90E7D9, 0x9A90E7D9, 39, // 212 + 0x99D722DB, 0x00000000, 39, // 213 + 0x991F1A51, 0x991F1A51, 39, // 214 + 0x4C346405, 0x00000000, 38, // 215 + 0x97B425ED, 0x97B425ED, 39, // 216 + 0x4B809701, 0x4B809701, 38, // 217 + 0x2593F69B, 0x2593F69B, 37, // 218 + 0x12B404AD, 0x12B404AD, 36, // 219 + 0x94F2094F, 0x94F2094F, 39, // 220 + 0x25116025, 0x25116025, 37, // 221 + 0x24E6A171, 0x24E6A171, 37, // 222 + 0x24BC44E1, 0x24BC44E1, 37, // 223 + 0x49249249, 0x49249249, 38, // 224 + 0x91A2B3C5, 0x00000000, 39, // 225 + 0x90FDBC09, 0x90FDBC09, 39, // 226 + 0x905A3863, 0x905A3863, 39, // 227 + 0x47DC11F7, 0x47DC11F7, 38, // 228 + 0x478BBCED, 0x00000000, 38, // 229 + 0x8E78356D, 0x8E78356D, 39, // 230 + 0x46ED2901, 0x46ED2901, 38, // 231 + 0x8D3DCB09, 0x00000000, 39, // 232 + 0x2328A701, 0x2328A701, 37, // 233 + 0x23023023, 0x23023023, 37, // 234 + 0x45B81A25, 0x45B81A25, 38, // 235 + 0x22B63CBF, 0x00000000, 37, // 236 + 0x08A42F87, 0x08A42F87, 35, // 237 + 0x44D72045, 0x00000000, 38, // 238 + 0x891AC73B, 0x00000000, 39, // 239 + 0x88888889, 0x00000000, 39, // 240 + 0x10FEF011, 0x00000000, 36, // 241 + 0x8767AB5F, 0x8767AB5F, 39, // 242 + 0x86D90545, 0x00000000, 39, // 243 + 0x4325C53F, 0x00000000, 38, // 244 + 0x85BF3761, 0x85BF3761, 39, // 245 + 0x85340853, 0x85340853, 39, // 246 + 0x10953F39, 0x10953F39, 36, // 247 + 0x42108421, 0x42108421, 38, // 248 + 0x41CC9829, 0x41CC9829, 38, // 249 + 0x10624DD3, 0x00000000, 36, // 250 + 0x828CBFBF, 0x00000000, 39, // 251 + 0x41041041, 0x41041041, 38, // 252 + 0x81848DA9, 0x00000000, 39, // 253 + 0x10204081, 0x10204081, 36, // 254 + 0x80808081, 0x00000000, 39 // 255 + }; + + [Serializable] public sealed class NormalBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Normal" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((rhs).B); }; { fG = ((rhs).G); }; { fR = ((rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((*src).B); }; { fG = ((*src).G); }; { fR = ((*src).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((*rhs).B); }; { fG = ((*rhs).G); }; { fR = ((*rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((rhs).B); }; { fG = ((rhs).G); }; { fR = ((rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new NormalBlendOpWithOpacity(opacity); } private sealed class NormalBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Normal" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((rhs).B); }; { fG = ((rhs).G); }; { fR = ((rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((*src).B); }; { fG = ((*src).G); }; { fR = ((*src).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = ((*rhs).B); }; { fG = ((*rhs).G); }; { fR = ((*rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public NormalBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class MultiplyBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Multiply" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((lhs).B)) * (((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((lhs).G)) * (((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((lhs).R)) * (((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*dst).B)) * (((*src).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((*dst).G)) * (((*src).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((*dst).R)) * (((*src).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*lhs).B)) * (((*rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((*lhs).G)) * (((*rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((*lhs).R)) * (((*rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((lhs).B)) * (((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((lhs).G)) * (((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((lhs).R)) * (((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new MultiplyBlendOpWithOpacity(opacity); } private sealed class MultiplyBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Multiply" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((lhs).B)) * (((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((lhs).G)) * (((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((lhs).R)) * (((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*dst).B)) * (((*src).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((*dst).G)) * (((*src).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((*dst).R)) * (((*src).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*lhs).B)) * (((*rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; }; { { fG = ((((*lhs).G)) * (((*rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; }; { { fR = ((((*lhs).R)) * (((*rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public MultiplyBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class AdditiveBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Additive" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((lhs).B) + ((rhs).B)); }; { fG = Math.Min(255, ((lhs).G) + ((rhs).G)); }; { fR = Math.Min(255, ((lhs).R) + ((rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((*dst).B) + ((*src).B)); }; { fG = Math.Min(255, ((*dst).G) + ((*src).G)); }; { fR = Math.Min(255, ((*dst).R) + ((*src).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((*lhs).B) + ((*rhs).B)); }; { fG = Math.Min(255, ((*lhs).G) + ((*rhs).G)); }; { fR = Math.Min(255, ((*lhs).R) + ((*rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((lhs).B) + ((rhs).B)); }; { fG = Math.Min(255, ((lhs).G) + ((rhs).G)); }; { fR = Math.Min(255, ((lhs).R) + ((rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new AdditiveBlendOpWithOpacity(opacity); } private sealed class AdditiveBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Additive" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((lhs).B) + ((rhs).B)); }; { fG = Math.Min(255, ((lhs).G) + ((rhs).G)); }; { fR = Math.Min(255, ((lhs).R) + ((rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((*dst).B) + ((*src).B)); }; { fG = Math.Min(255, ((*dst).G) + ((*src).G)); }; { fR = Math.Min(255, ((*dst).R) + ((*src).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min(255, ((*lhs).B) + ((*rhs).B)); }; { fG = Math.Min(255, ((*lhs).G) + ((*rhs).G)); }; { fR = Math.Min(255, ((*lhs).R) + ((*rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public AdditiveBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class ColorBurnBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "ColorBurn" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 0) { fB = 0; } else { { int i = (((rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((lhs).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((rhs).G) == 0) { fG = 0; } else { { int i = (((rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((lhs).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((rhs).R) == 0) { fR = 0; } else { { int i = (((rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((lhs).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*src).B) == 0) { fB = 0; } else { { int i = (((*src).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((*dst).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((*src).G) == 0) { fG = 0; } else { { int i = (((*src).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((*dst).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((*src).R) == 0) { fR = 0; } else { { int i = (((*src).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((*dst).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*rhs).B) == 0) { fB = 0; } else { { int i = (((*rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((*lhs).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((*rhs).G) == 0) { fG = 0; } else { { int i = (((*rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((*lhs).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((*rhs).R) == 0) { fR = 0; } else { { int i = (((*rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((*lhs).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 0) { fB = 0; } else { { int i = (((rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((lhs).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((rhs).G) == 0) { fG = 0; } else { { int i = (((rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((lhs).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((rhs).R) == 0) { fR = 0; } else { { int i = (((rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((lhs).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new ColorBurnBlendOpWithOpacity(opacity); } private sealed class ColorBurnBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "ColorBurn" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 0) { fB = 0; } else { { int i = (((rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((lhs).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((rhs).G) == 0) { fG = 0; } else { { int i = (((rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((lhs).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((rhs).R) == 0) { fR = 0; } else { { int i = (((rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((lhs).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*src).B) == 0) { fB = 0; } else { { int i = (((*src).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((*dst).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((*src).G) == 0) { fG = 0; } else { { int i = (((*src).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((*dst).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((*src).R) == 0) { fR = 0; } else { { int i = (((*src).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((*dst).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*rhs).B) == 0) { fB = 0; } else { { int i = (((*rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((255 - ((*lhs).B)) * 255) * M) + A) >> (int)S); }; fB = 255 - fB; fB = Math.Max(0, fB); } }; { if (((*rhs).G) == 0) { fG = 0; } else { { int i = (((*rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((255 - ((*lhs).G)) * 255) * M) + A) >> (int)S); }; fG = 255 - fG; fG = Math.Max(0, fG); } }; { if (((*rhs).R) == 0) { fR = 0; } else { { int i = (((*rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((255 - ((*lhs).R)) * 255) * M) + A) >> (int)S); }; fR = 255 - fR; fR = Math.Max(0, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public ColorBurnBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class ColorDodgeBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "ColorDodge" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 255) { fB = 255; } else { { int i = ((255 - ((rhs).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((lhs).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((rhs).G) == 255) { fG = 255; } else { { int i = ((255 - ((rhs).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((lhs).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((rhs).R) == 255) { fR = 255; } else { { int i = ((255 - ((rhs).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((lhs).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*src).B) == 255) { fB = 255; } else { { int i = ((255 - ((*src).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((*dst).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*src).G) == 255) { fG = 255; } else { { int i = ((255 - ((*src).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((*dst).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*src).R) == 255) { fR = 255; } else { { int i = ((255 - ((*src).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((*dst).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*rhs).B) == 255) { fB = 255; } else { { int i = ((255 - ((*rhs).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((*lhs).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*rhs).G) == 255) { fG = 255; } else { { int i = ((255 - ((*rhs).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((*lhs).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*rhs).R) == 255) { fR = 255; } else { { int i = ((255 - ((*rhs).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((*lhs).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 255) { fB = 255; } else { { int i = ((255 - ((rhs).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((lhs).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((rhs).G) == 255) { fG = 255; } else { { int i = ((255 - ((rhs).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((lhs).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((rhs).R) == 255) { fR = 255; } else { { int i = ((255 - ((rhs).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((lhs).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new ColorDodgeBlendOpWithOpacity(opacity); } private sealed class ColorDodgeBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "ColorDodge" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 255) { fB = 255; } else { { int i = ((255 - ((rhs).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((lhs).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((rhs).G) == 255) { fG = 255; } else { { int i = ((255 - ((rhs).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((lhs).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((rhs).R) == 255) { fR = 255; } else { { int i = ((255 - ((rhs).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((lhs).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*src).B) == 255) { fB = 255; } else { { int i = ((255 - ((*src).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((*dst).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*src).G) == 255) { fG = 255; } else { { int i = ((255 - ((*src).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((*dst).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*src).R) == 255) { fR = 255; } else { { int i = ((255 - ((*src).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((*dst).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*rhs).B) == 255) { fB = 255; } else { { int i = ((255 - ((*rhs).B))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)((((((*lhs).B) * 255) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*rhs).G) == 255) { fG = 255; } else { { int i = ((255 - ((*rhs).G))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)((((((*lhs).G) * 255) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*rhs).R) == 255) { fR = 255; } else { { int i = ((255 - ((*rhs).R))) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)((((((*lhs).R) * 255) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public ColorDodgeBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class ReflectBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Reflect" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 255) { fB = 255; } else { { int i = (255 - ((rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((lhs).B) * ((lhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((rhs).G) == 255) { fG = 255; } else { { int i = (255 - ((rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((lhs).G) * ((lhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((rhs).R) == 255) { fR = 255; } else { { int i = (255 - ((rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((lhs).R) * ((lhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*src).B) == 255) { fB = 255; } else { { int i = (255 - ((*src).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*dst).B) * ((*dst).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*src).G) == 255) { fG = 255; } else { { int i = (255 - ((*src).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*dst).G) * ((*dst).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*src).R) == 255) { fR = 255; } else { { int i = (255 - ((*src).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*dst).R) * ((*dst).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*rhs).B) == 255) { fB = 255; } else { { int i = (255 - ((*rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*lhs).B) * ((*lhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*rhs).G) == 255) { fG = 255; } else { { int i = (255 - ((*rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*lhs).G) * ((*lhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*rhs).R) == 255) { fR = 255; } else { { int i = (255 - ((*rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*lhs).R) * ((*lhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 255) { fB = 255; } else { { int i = (255 - ((rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((lhs).B) * ((lhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((rhs).G) == 255) { fG = 255; } else { { int i = (255 - ((rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((lhs).G) * ((lhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((rhs).R) == 255) { fR = 255; } else { { int i = (255 - ((rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((lhs).R) * ((lhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new ReflectBlendOpWithOpacity(opacity); } private sealed class ReflectBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Reflect" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((rhs).B) == 255) { fB = 255; } else { { int i = (255 - ((rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((lhs).B) * ((lhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((rhs).G) == 255) { fG = 255; } else { { int i = (255 - ((rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((lhs).G) * ((lhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((rhs).R) == 255) { fR = 255; } else { { int i = (255 - ((rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((lhs).R) * ((lhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*src).B) == 255) { fB = 255; } else { { int i = (255 - ((*src).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*dst).B) * ((*dst).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*src).G) == 255) { fG = 255; } else { { int i = (255 - ((*src).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*dst).G) * ((*dst).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*src).R) == 255) { fR = 255; } else { { int i = (255 - ((*src).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*dst).R) * ((*dst).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*rhs).B) == 255) { fB = 255; } else { { int i = (255 - ((*rhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*lhs).B) * ((*lhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*rhs).G) == 255) { fG = 255; } else { { int i = (255 - ((*rhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*lhs).G) * ((*lhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*rhs).R) == 255) { fR = 255; } else { { int i = (255 - ((*rhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*lhs).R) * ((*lhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public ReflectBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class GlowBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Glow" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((lhs).B) == 255) { fB = 255; } else { { int i = (255 - ((lhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((rhs).B) * ((rhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((lhs).G) == 255) { fG = 255; } else { { int i = (255 - ((lhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((rhs).G) * ((rhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((lhs).R) == 255) { fR = 255; } else { { int i = (255 - ((lhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((rhs).R) * ((rhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*dst).B) == 255) { fB = 255; } else { { int i = (255 - ((*dst).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*src).B) * ((*src).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*dst).G) == 255) { fG = 255; } else { { int i = (255 - ((*dst).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*src).G) * ((*src).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*dst).R) == 255) { fR = 255; } else { { int i = (255 - ((*dst).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*src).R) * ((*src).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*lhs).B) == 255) { fB = 255; } else { { int i = (255 - ((*lhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*rhs).B) * ((*rhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*lhs).G) == 255) { fG = 255; } else { { int i = (255 - ((*lhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*rhs).G) * ((*rhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*lhs).R) == 255) { fR = 255; } else { { int i = (255 - ((*lhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*rhs).R) * ((*rhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((lhs).B) == 255) { fB = 255; } else { { int i = (255 - ((lhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((rhs).B) * ((rhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((lhs).G) == 255) { fG = 255; } else { { int i = (255 - ((lhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((rhs).G) * ((rhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((lhs).R) == 255) { fR = 255; } else { { int i = (255 - ((lhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((rhs).R) * ((rhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new GlowBlendOpWithOpacity(opacity); } private sealed class GlowBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Glow" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((lhs).B) == 255) { fB = 255; } else { { int i = (255 - ((lhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((rhs).B) * ((rhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((lhs).G) == 255) { fG = 255; } else { { int i = (255 - ((lhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((rhs).G) * ((rhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((lhs).R) == 255) { fR = 255; } else { { int i = (255 - ((lhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((rhs).R) * ((rhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*dst).B) == 255) { fB = 255; } else { { int i = (255 - ((*dst).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*src).B) * ((*src).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*dst).G) == 255) { fG = 255; } else { { int i = (255 - ((*dst).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*src).G) * ((*src).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*dst).R) == 255) { fR = 255; } else { { int i = (255 - ((*dst).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*src).R) * ((*src).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*lhs).B) == 255) { fB = 255; } else { { int i = (255 - ((*lhs).B)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fB = (int)(((((*rhs).B) * ((*rhs).B) * M) + A) >> (int)S); }; fB = Math.Min(255, fB); } }; { if (((*lhs).G) == 255) { fG = 255; } else { { int i = (255 - ((*lhs).G)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fG = (int)(((((*rhs).G) * ((*rhs).G) * M) + A) >> (int)S); }; fG = Math.Min(255, fG); } }; { if (((*lhs).R) == 255) { fR = 255; } else { { int i = (255 - ((*lhs).R)) * 3; uint M = masTable[i]; uint A = masTable[i + 1]; uint S = masTable[i + 2]; fR = (int)(((((*rhs).R) * ((*rhs).R) * M) + A) >> (int)S); }; fR = Math.Min(255, fR); } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public GlowBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class OverlayBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Overlay" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((lhs).B) < 128) { { fB = ((2 * ((lhs).B)) * (((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((lhs).B))) * (255 - ((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((lhs).G) < 128) { { fG = ((2 * ((lhs).G)) * (((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((lhs).G))) * (255 - ((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((lhs).R) < 128) { { fR = ((2 * ((lhs).R)) * (((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((lhs).R))) * (255 - ((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*dst).B) < 128) { { fB = ((2 * ((*dst).B)) * (((*src).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((*dst).B))) * (255 - ((*src).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((*dst).G) < 128) { { fG = ((2 * ((*dst).G)) * (((*src).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((*dst).G))) * (255 - ((*src).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((*dst).R) < 128) { { fR = ((2 * ((*dst).R)) * (((*src).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((*dst).R))) * (255 - ((*src).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*lhs).B) < 128) { { fB = ((2 * ((*lhs).B)) * (((*rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((*lhs).B))) * (255 - ((*rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((*lhs).G) < 128) { { fG = ((2 * ((*lhs).G)) * (((*rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((*lhs).G))) * (255 - ((*rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((*lhs).R) < 128) { { fR = ((2 * ((*lhs).R)) * (((*rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((*lhs).R))) * (255 - ((*rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((lhs).B) < 128) { { fB = ((2 * ((lhs).B)) * (((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((lhs).B))) * (255 - ((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((lhs).G) < 128) { { fG = ((2 * ((lhs).G)) * (((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((lhs).G))) * (255 - ((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((lhs).R) < 128) { { fR = ((2 * ((lhs).R)) * (((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((lhs).R))) * (255 - ((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new OverlayBlendOpWithOpacity(opacity); } private sealed class OverlayBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Overlay" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((lhs).B) < 128) { { fB = ((2 * ((lhs).B)) * (((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((lhs).B))) * (255 - ((rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((lhs).G) < 128) { { fG = ((2 * ((lhs).G)) * (((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((lhs).G))) * (255 - ((rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((lhs).R) < 128) { { fR = ((2 * ((lhs).R)) * (((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((lhs).R))) * (255 - ((rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*dst).B) < 128) { { fB = ((2 * ((*dst).B)) * (((*src).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((*dst).B))) * (255 - ((*src).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((*dst).G) < 128) { { fG = ((2 * ((*dst).G)) * (((*src).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((*dst).G))) * (255 - ((*src).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((*dst).R) < 128) { { fR = ((2 * ((*dst).R)) * (((*src).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((*dst).R))) * (255 - ((*src).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { if (((*lhs).B) < 128) { { fB = ((2 * ((*lhs).B)) * (((*rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; } else { { fB = ((2 * (255 - ((*lhs).B))) * (255 - ((*rhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = 255 - fB; } }; { if (((*lhs).G) < 128) { { fG = ((2 * ((*lhs).G)) * (((*rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; } else { { fG = ((2 * (255 - ((*lhs).G))) * (255 - ((*rhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = 255 - fG; } }; { if (((*lhs).R) < 128) { { fR = ((2 * ((*lhs).R)) * (((*rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; } else { { fR = ((2 * (255 - ((*lhs).R))) * (255 - ((*rhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = 255 - fR; } }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public OverlayBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class DifferenceBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Difference" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((rhs).B) - ((lhs).B)); }; { fG = Math.Abs(((rhs).G) - ((lhs).G)); }; { fR = Math.Abs(((rhs).R) - ((lhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((*src).B) - ((*dst).B)); }; { fG = Math.Abs(((*src).G) - ((*dst).G)); }; { fR = Math.Abs(((*src).R) - ((*dst).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((*rhs).B) - ((*lhs).B)); }; { fG = Math.Abs(((*rhs).G) - ((*lhs).G)); }; { fR = Math.Abs(((*rhs).R) - ((*lhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((rhs).B) - ((lhs).B)); }; { fG = Math.Abs(((rhs).G) - ((lhs).G)); }; { fR = Math.Abs(((rhs).R) - ((lhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new DifferenceBlendOpWithOpacity(opacity); } private sealed class DifferenceBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Difference" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((rhs).B) - ((lhs).B)); }; { fG = Math.Abs(((rhs).G) - ((lhs).G)); }; { fR = Math.Abs(((rhs).R) - ((lhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((*src).B) - ((*dst).B)); }; { fG = Math.Abs(((*src).G) - ((*dst).G)); }; { fR = Math.Abs(((*src).R) - ((*dst).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Abs(((*rhs).B) - ((*lhs).B)); }; { fG = Math.Abs(((*rhs).G) - ((*lhs).G)); }; { fR = Math.Abs(((*rhs).R) - ((*lhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public DifferenceBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class NegationBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Negation" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((lhs).B) - ((rhs).B))); }; { fG = (255 - Math.Abs(255 - ((lhs).G) - ((rhs).G))); }; { fR = (255 - Math.Abs(255 - ((lhs).R) - ((rhs).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((*dst).B) - ((*src).B))); }; { fG = (255 - Math.Abs(255 - ((*dst).G) - ((*src).G))); }; { fR = (255 - Math.Abs(255 - ((*dst).R) - ((*src).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((*lhs).B) - ((*rhs).B))); }; { fG = (255 - Math.Abs(255 - ((*lhs).G) - ((*rhs).G))); }; { fR = (255 - Math.Abs(255 - ((*lhs).R) - ((*rhs).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((lhs).B) - ((rhs).B))); }; { fG = (255 - Math.Abs(255 - ((lhs).G) - ((rhs).G))); }; { fR = (255 - Math.Abs(255 - ((lhs).R) - ((rhs).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new NegationBlendOpWithOpacity(opacity); } private sealed class NegationBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Negation" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((lhs).B) - ((rhs).B))); }; { fG = (255 - Math.Abs(255 - ((lhs).G) - ((rhs).G))); }; { fR = (255 - Math.Abs(255 - ((lhs).R) - ((rhs).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((*dst).B) - ((*src).B))); }; { fG = (255 - Math.Abs(255 - ((*dst).G) - ((*src).G))); }; { fR = (255 - Math.Abs(255 - ((*dst).R) - ((*src).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (255 - Math.Abs(255 - ((*lhs).B) - ((*rhs).B))); }; { fG = (255 - Math.Abs(255 - ((*lhs).G) - ((*rhs).G))); }; { fR = (255 - Math.Abs(255 - ((*lhs).R) - ((*rhs).R))); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public NegationBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class LightenBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Lighten" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((lhs).B, (rhs).B); }; { fG = Math.Max((lhs).G, (rhs).G); }; { fR = Math.Max((lhs).R, (rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((*dst).B, (*src).B); }; { fG = Math.Max((*dst).G, (*src).G); }; { fR = Math.Max((*dst).R, (*src).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((*lhs).B, (*rhs).B); }; { fG = Math.Max((*lhs).G, (*rhs).G); }; { fR = Math.Max((*lhs).R, (*rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((lhs).B, (rhs).B); }; { fG = Math.Max((lhs).G, (rhs).G); }; { fR = Math.Max((lhs).R, (rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new LightenBlendOpWithOpacity(opacity); } private sealed class LightenBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Lighten" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((lhs).B, (rhs).B); }; { fG = Math.Max((lhs).G, (rhs).G); }; { fR = Math.Max((lhs).R, (rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((*dst).B, (*src).B); }; { fG = Math.Max((*dst).G, (*src).G); }; { fR = Math.Max((*dst).R, (*src).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Max((*lhs).B, (*rhs).B); }; { fG = Math.Max((*lhs).G, (*rhs).G); }; { fR = Math.Max((*lhs).R, (*rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public LightenBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class DarkenBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Darken" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((lhs).B, (rhs).B); }; { fG = Math.Min((lhs).G, (rhs).G); }; { fR = Math.Min((lhs).R, (rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((*dst).B, (*src).B); }; { fG = Math.Min((*dst).G, (*src).G); }; { fR = Math.Min((*dst).R, (*src).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((*lhs).B, (*rhs).B); }; { fG = Math.Min((*lhs).G, (*rhs).G); }; { fR = Math.Min((*lhs).R, (*rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((lhs).B, (rhs).B); }; { fG = Math.Min((lhs).G, (rhs).G); }; { fR = Math.Min((lhs).R, (rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new DarkenBlendOpWithOpacity(opacity); } private sealed class DarkenBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Darken" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((lhs).B, (rhs).B); }; { fG = Math.Min((lhs).G, (rhs).G); }; { fR = Math.Min((lhs).R, (rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((*dst).B, (*src).B); }; { fG = Math.Min((*dst).G, (*src).G); }; { fR = Math.Min((*dst).R, (*src).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = Math.Min((*lhs).B, (*rhs).B); }; { fG = Math.Min((*lhs).G, (*rhs).G); }; { fR = Math.Min((*lhs).R, (*rhs).R); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public DarkenBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class ScreenBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Screen" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((rhs).B)) * (((lhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((rhs).B) + ((lhs).B) - fB; }; { { fG = ((((rhs).G)) * (((lhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((rhs).G) + ((lhs).G) - fG; }; { { fR = ((((rhs).R)) * (((lhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((rhs).R) + ((lhs).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*src).B)) * (((*dst).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((*src).B) + ((*dst).B) - fB; }; { { fG = ((((*src).G)) * (((*dst).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((*src).G) + ((*dst).G) - fG; }; { { fR = ((((*src).R)) * (((*dst).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((*src).R) + ((*dst).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*rhs).B)) * (((*lhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((*rhs).B) + ((*lhs).B) - fB; }; { { fG = ((((*rhs).G)) * (((*lhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((*rhs).G) + ((*lhs).G) - fG; }; { { fR = ((((*rhs).R)) * (((*lhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((*rhs).R) + ((*lhs).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((rhs).B)) * (((lhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((rhs).B) + ((lhs).B) - fB; }; { { fG = ((((rhs).G)) * (((lhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((rhs).G) + ((lhs).G) - fG; }; { { fR = ((((rhs).R)) * (((lhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((rhs).R) + ((lhs).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new ScreenBlendOpWithOpacity(opacity); } private sealed class ScreenBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Screen" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((rhs).B)) * (((lhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((rhs).B) + ((lhs).B) - fB; }; { { fG = ((((rhs).G)) * (((lhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((rhs).G) + ((lhs).G) - fG; }; { { fR = ((((rhs).R)) * (((lhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((rhs).R) + ((lhs).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*src).B)) * (((*dst).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((*src).B) + ((*dst).B) - fB; }; { { fG = ((((*src).G)) * (((*dst).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((*src).G) + ((*dst).G) - fG; }; { { fR = ((((*src).R)) * (((*dst).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((*src).R) + ((*dst).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { { fB = ((((*rhs).B)) * (((*lhs).B)) + 0x80); fB = ((((fB) >> 8) + (fB)) >> 8); }; fB = ((*rhs).B) + ((*lhs).B) - fB; }; { { fG = ((((*rhs).G)) * (((*lhs).G)) + 0x80); fG = ((((fG) >> 8) + (fG)) >> 8); }; fG = ((*rhs).G) + ((*lhs).G) - fG; }; { { fR = ((((*rhs).R)) * (((*lhs).R)) + 0x80); fR = ((((fR) >> 8) + (fR)) >> 8); }; fR = ((*rhs).R) + ((*lhs).R) - fR; }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public ScreenBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + [Serializable] public sealed class XorBlendOp : UserBlendOp { public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Xor" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((lhs).B) ^ ((rhs).B)); }; { fG = (((lhs).G) ^ ((rhs).G)); }; { fR = (((lhs).R) ^ ((rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((*dst).B) ^ ((*src).B)); }; { fG = (((*dst).G) ^ ((*src).G)); }; { fR = (((*dst).R) ^ ((*src).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((*lhs).B) ^ ((*rhs).B)); }; { fG = (((*lhs).G) ^ ((*rhs).G)); }; { fR = (((*lhs).R) ^ ((*rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public static ColorBgra ApplyStatic(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((lhs).B) ^ ((rhs).B)); }; { fG = (((lhs).G) ^ ((rhs).G)); }; { fR = (((lhs).R) ^ ((rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public override UserBlendOp CreateWithOpacity(int opacity) { return new XorBlendOpWithOpacity(opacity); } private sealed class XorBlendOpWithOpacity : UserBlendOp { private int opacity; private byte ApplyOpacity(byte a) { int r; { r = (a); }; { r = ((r) * (this.opacity) + 0x80); r = ((((r) >> 8) + (r)) >> 8); }; return (byte)r; } public static string StaticName { get { return PdnResources.GetString("UserBlendOps." + "Xor" + "BlendOp.Name"); } } public override ColorBgra Apply(ColorBgra lhs, ColorBgra rhs) { int lhsA; { lhsA = ((lhs).A); }; int rhsA; { rhsA = ApplyOpacity((rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((lhs).B) ^ ((rhs).B)); }; { fG = (((lhs).G) ^ ((rhs).G)); }; { fR = (((lhs).R) ^ ((rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((lhs).B * y) + ((rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((lhs).G * y) + ((rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((lhs).R * y) + ((rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; return ColorBgra.FromUInt32(ret); } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { while (length > 0) { int lhsA; { lhsA = ((*dst).A); }; int rhsA; { rhsA = ApplyOpacity((*src).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((*dst).B) ^ ((*src).B)); }; { fG = (((*dst).G) ^ ((*src).G)); }; { fR = (((*dst).R) ^ ((*src).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*dst).B * y) + ((*src).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*dst).G * y) + ((*src).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*dst).R * y) + ((*src).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++src; --length; } } public unsafe override void Apply(ColorBgra *dst, ColorBgra *lhs, ColorBgra *rhs, int length) { while (length > 0) { int lhsA; { lhsA = ((*lhs).A); }; int rhsA; { rhsA = ApplyOpacity((*rhs).A); }; int y; { y = ((lhsA) * (255 - rhsA) + 0x80); y = ((((y) >> 8) + (y)) >> 8); }; int totalA = y + rhsA; uint ret; if (totalA == 0) { ret = 0; } else { int fB; int fG; int fR; { fB = (((*lhs).B) ^ ((*rhs).B)); }; { fG = (((*lhs).G) ^ ((*rhs).G)); }; { fR = (((*lhs).R) ^ ((*rhs).R)); }; int x; { x = ((lhsA) * (rhsA) + 0x80); x = ((((x) >> 8) + (x)) >> 8); }; int z = rhsA - x; int masIndex = totalA * 3; uint taM = masTable[masIndex]; uint taA = masTable[masIndex + 1]; uint taS = masTable[masIndex + 2]; uint b = (uint)(((((long)((((*lhs).B * y) + ((*rhs).B * z) + (fB * x)))) * taM) + taA) >> (int)taS); uint g = (uint)(((((long)((((*lhs).G * y) + ((*rhs).G * z) + (fG * x)))) * taM) + taA) >> (int)taS); uint r = (uint)(((((long)((((*lhs).R * y) + ((*rhs).R * z) + (fR * x)))) * taM) + taA) >> (int)taS); int a; { { a = ((lhsA) * (255 - (rhsA)) + 0x80); a = ((((a) >> 8) + (a)) >> 8); }; a += (rhsA); }; ret = b + (g << 8) + (r << 16) + ((uint)a << 24); }; dst->Bgra = ret; ++dst; ++lhs; ++rhs; --length; } } public XorBlendOpWithOpacity(int opacity) { if (this.opacity < 0 || this.opacity > 255) { throw new ArgumentOutOfRangeException(); } this.opacity = opacity; } } } + } +} diff --git a/src/Data/UserBlendOps.cs b/src/Data/UserBlendOps.cs new file mode 100644 index 0000000..0cffc37 --- /dev/null +++ b/src/Data/UserBlendOps.cs @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace PaintDotNet +{ + /// + /// This class contains all the render ops that can be used by the user + /// to configure a layer's blending mode. It also contains helper + /// functions to aid in enumerating and using these blend ops. + /// + /// Credit for mathematical descriptions of many of the blend modes goes to + /// a page on Pegtop Software's website called, "Blend Modes" + /// http://www.pegtop.net/delphi/articles/blendmodes/ + /// + public sealed partial class UserBlendOps + { + private UserBlendOps() + { + } + + /// + /// Returns an array of Type objects that lists all of the pixel ops contained + /// within this class. You can then use Utility.GetStaticName to retrieve the + /// value of the StaticName property. + /// + /// + public static Type[] GetBlendOps() + { + Type[] allTypes = typeof(UserBlendOps).GetNestedTypes(); + List types = new List(allTypes.Length); + + foreach (Type type in allTypes) + { + if (type.IsSubclassOf(typeof(UserBlendOp)) && !type.IsAbstract) + { + types.Add(type); + } + } + + return types.ToArray(); + } + + public static string GetBlendOpFriendlyName(Type opType) + { + return Utility.GetStaticName(opType); + } + + public static UserBlendOp CreateBlendOp(Type opType) + { + ConstructorInfo ci = opType.GetConstructor(System.Type.EmptyTypes); + UserBlendOp op = (UserBlendOp)ci.Invoke(null); + return op; + } + + public static UserBlendOp CreateDefaultBlendOp() + { + return new NormalBlendOp(); + } + + public static Type GetDefaultBlendOp() + { + return typeof(NormalBlendOp); + } + } +} diff --git a/src/DdsFileType/DdsFileType/DdsFile.cs b/src/DdsFileType/DdsFileType/DdsFile.cs new file mode 100644 index 0000000..4442c63 --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsFile.cs @@ -0,0 +1,954 @@ +//------------------------------------------------------------------------------ +/* + @brief DDS File Type Plugin for Paint.NET + + @note Copyright (c) 2007 Dean Ashton http://www.dmashton.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**/ +//------------------------------------------------------------------------------ + +// If we want to do the alignment as per the (broken) DDS documentation, then we +// uncomment this define.. +//#define APPLY_PITCH_ALIGNMENT + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using PaintDotNet; +using System.Drawing; + +namespace DdsFileTypePlugin +{ + public enum DdsFileFormat + { + DDS_FORMAT_DXT1, + DDS_FORMAT_DXT3, + DDS_FORMAT_DXT5, + DDS_FORMAT_A8R8G8B8, + DDS_FORMAT_X8R8G8B8, + DDS_FORMAT_A8B8G8R8, + DDS_FORMAT_X8B8G8R8, + DDS_FORMAT_A1R5G5B5, + DDS_FORMAT_A4R4G4B4, + DDS_FORMAT_R8G8B8, + DDS_FORMAT_R5G6B5, + + DDS_FORMAT_INVALID, + }; + + public class DdsPixelFormat + { + public enum PixelFormatFlags + { + DDS_FOURCC = 0x00000004, + DDS_RGB = 0x00000040, + DDS_RGBA = 0x00000041, + } + + public uint m_size; + public uint m_flags; + public uint m_fourCC; + public uint m_rgbBitCount; + public uint m_rBitMask; + public uint m_gBitMask; + public uint m_bBitMask; + public uint m_aBitMask; + + public uint Size() + { + return 8 * 4; + } + + public void Initialise( DdsFileFormat fileFormat ) + { + m_size = Size(); + switch( fileFormat ) + { + case DdsFileFormat.DDS_FORMAT_DXT1: + case DdsFileFormat.DDS_FORMAT_DXT3: + case DdsFileFormat.DDS_FORMAT_DXT5: + { + // DXT1/DXT3/DXT5 + m_flags = ( int )PixelFormatFlags.DDS_FOURCC; + m_rgbBitCount = 0; + m_rBitMask = 0; + m_gBitMask = 0; + m_bBitMask = 0; + m_aBitMask = 0; + if ( fileFormat == DdsFileFormat.DDS_FORMAT_DXT1 ) m_fourCC = 0x31545844; //"DXT1" + if ( fileFormat == DdsFileFormat.DDS_FORMAT_DXT3 ) m_fourCC = 0x33545844; //"DXT1" + if ( fileFormat == DdsFileFormat.DDS_FORMAT_DXT5 ) m_fourCC = 0x35545844; //"DXT1" + break; + } + + case DdsFileFormat.DDS_FORMAT_A8R8G8B8: + { + m_flags = ( int )PixelFormatFlags.DDS_RGBA; + m_rgbBitCount = 32; + m_fourCC = 0; + m_rBitMask = 0x00ff0000; + m_gBitMask = 0x0000ff00; + m_bBitMask = 0x000000ff; + m_aBitMask = 0xff000000; + break; + } + + case DdsFileFormat.DDS_FORMAT_X8R8G8B8: + { + m_flags = ( int )PixelFormatFlags.DDS_RGB; + m_rgbBitCount = 32; + m_fourCC = 0; + m_rBitMask = 0x00ff0000; + m_gBitMask = 0x0000ff00; + m_bBitMask = 0x000000ff; + m_aBitMask = 0x00000000; + break; + } + + case DdsFileFormat.DDS_FORMAT_A8B8G8R8: + { + m_flags = ( int )PixelFormatFlags.DDS_RGBA; + m_rgbBitCount = 32; + m_fourCC = 0; + m_rBitMask = 0x000000ff; + m_gBitMask = 0x0000ff00; + m_bBitMask = 0x00ff0000; + m_aBitMask = 0xff000000; + break; + } + + case DdsFileFormat.DDS_FORMAT_X8B8G8R8: + { + m_flags = ( int )PixelFormatFlags.DDS_RGB; + m_rgbBitCount = 32; + m_fourCC = 0; + m_rBitMask = 0x000000ff; + m_gBitMask = 0x0000ff00; + m_bBitMask = 0x00ff0000; + m_aBitMask = 0x00000000; + break; + } + + case DdsFileFormat.DDS_FORMAT_A1R5G5B5: + { + m_flags = ( int )PixelFormatFlags.DDS_RGBA; + m_rgbBitCount = 16; + m_fourCC = 0; + m_rBitMask = 0x00007c00; + m_gBitMask = 0x000003e0; + m_bBitMask = 0x0000001f; + m_aBitMask = 0x00008000; + break; + } + + case DdsFileFormat.DDS_FORMAT_A4R4G4B4: + { + m_flags = ( int )PixelFormatFlags.DDS_RGBA; + m_rgbBitCount = 16; + m_fourCC = 0; + m_rBitMask = 0x00000f00; + m_gBitMask = 0x000000f0; + m_bBitMask = 0x0000000f; + m_aBitMask = 0x0000f000; + break; + } + + case DdsFileFormat.DDS_FORMAT_R8G8B8: + { + m_flags = ( int )PixelFormatFlags.DDS_RGB; + m_fourCC = 0; + m_rgbBitCount = 24; + m_rBitMask = 0x00ff0000; + m_gBitMask = 0x0000ff00; + m_bBitMask = 0x000000ff; + m_aBitMask = 0x00000000; + break; + } + + case DdsFileFormat.DDS_FORMAT_R5G6B5: + { + m_flags = ( int )PixelFormatFlags.DDS_RGB; + m_fourCC = 0; + m_rgbBitCount = 16; + m_rBitMask = 0x0000f800; + m_gBitMask = 0x000007e0; + m_bBitMask = 0x0000001f; + m_aBitMask = 0x00000000; + break; + } + + default: + break; + } + } + + public void Read( System.IO.Stream input ) + { + this.m_size = ( uint )Utility.ReadUInt32( input ); + this.m_flags = ( uint )Utility.ReadUInt32( input ); + this.m_fourCC = ( uint )Utility.ReadUInt32( input ); + this.m_rgbBitCount = ( uint )Utility.ReadUInt32( input ); + this.m_rBitMask = ( uint )Utility.ReadUInt32( input ); + this.m_gBitMask = ( uint )Utility.ReadUInt32( input ); + this.m_bBitMask = ( uint )Utility.ReadUInt32( input ); + this.m_aBitMask = ( uint )Utility.ReadUInt32( input ); + } + + public void Write( System.IO.Stream output ) + { + Utility.WriteUInt32( output, this.m_size ); + Utility.WriteUInt32( output, this.m_flags ); + Utility.WriteUInt32( output, this.m_fourCC ); + Utility.WriteUInt32( output, this.m_rgbBitCount ); + Utility.WriteUInt32( output, this.m_rBitMask ); + Utility.WriteUInt32( output, this.m_gBitMask ); + Utility.WriteUInt32( output, this.m_bBitMask ); + Utility.WriteUInt32( output, this.m_aBitMask ); + } + } + + public class DdsHeader + { + public enum HeaderFlags + { + DDS_HEADER_FLAGS_TEXTURE = 0x00001007, // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT + DDS_HEADER_FLAGS_MIPMAP = 0x00020000, // DDSD_MIPMAPCOUNT + DDS_HEADER_FLAGS_VOLUME = 0x00800000, // DDSD_DEPTH + DDS_HEADER_FLAGS_PITCH = 0x00000008, // DDSD_PITCH + DDS_HEADER_FLAGS_LINEARSIZE = 0x00080000, // DDSD_LINEARSIZE + } + + public enum SurfaceFlags + { + DDS_SURFACE_FLAGS_TEXTURE = 0x00001000, // DDSCAPS_TEXTURE + DDS_SURFACE_FLAGS_MIPMAP = 0x00400008, // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP + DDS_SURFACE_FLAGS_CUBEMAP = 0x00000008, // DDSCAPS_COMPLEX + } + + public enum CubemapFlags + { + DDS_CUBEMAP_POSITIVEX = 0x00000600, // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX + DDS_CUBEMAP_NEGATIVEX = 0x00000a00, // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX + DDS_CUBEMAP_POSITIVEY = 0x00001200, // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY + DDS_CUBEMAP_NEGATIVEY = 0x00002200, // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY + DDS_CUBEMAP_POSITIVEZ = 0x00004200, // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ + DDS_CUBEMAP_NEGATIVEZ = 0x00008200, // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + + DDS_CUBEMAP_ALLFACES = ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX | + DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY | + DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ ) + } + + public enum VolumeFlags + { + DDS_FLAGS_VOLUME = 0x00200000, // DDSCAPS2_VOLUME + } + + public DdsHeader() + { + m_pixelFormat = new DdsPixelFormat(); + } + + public uint Size() + { + return ( 18 * 4 ) + m_pixelFormat.Size() + ( 5 * 4 ); + } + + public uint m_size; + public uint m_headerFlags; + public uint m_height; + public uint m_width; + public uint m_pitchOrLinearSize; + public uint m_depth; + public uint m_mipMapCount; + public uint m_reserved1_0; + public uint m_reserved1_1; + public uint m_reserved1_2; + public uint m_reserved1_3; + public uint m_reserved1_4; + public uint m_reserved1_5; + public uint m_reserved1_6; + public uint m_reserved1_7; + public uint m_reserved1_8; + public uint m_reserved1_9; + public uint m_reserved1_10; + public DdsPixelFormat m_pixelFormat; + public uint m_surfaceFlags; + public uint m_cubemapFlags; + public uint m_reserved2_0; + public uint m_reserved2_1; + public uint m_reserved2_2; + + public void Read( System.IO.Stream input ) + { + this.m_size = ( uint )Utility.ReadUInt32( input ); + this.m_headerFlags = ( uint )Utility.ReadUInt32( input ); + this.m_height = ( uint )Utility.ReadUInt32( input ); + this.m_width = ( uint )Utility.ReadUInt32( input ); + this.m_pitchOrLinearSize = ( uint )Utility.ReadUInt32( input ); + this.m_depth = ( uint )Utility.ReadUInt32( input ); + this.m_mipMapCount = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_0 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_1 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_2 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_3 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_4 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_5 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_6 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_7 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_8 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_9 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved1_10 = ( uint )Utility.ReadUInt32( input ); + this.m_pixelFormat.Read( input ); + this.m_surfaceFlags = ( uint )Utility.ReadUInt32( input ); + this.m_cubemapFlags = ( uint )Utility.ReadUInt32( input ); + this.m_reserved2_0 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved2_1 = ( uint )Utility.ReadUInt32( input ); + this.m_reserved2_2 = ( uint )Utility.ReadUInt32( input ); + } + + public void Write( System.IO.Stream output ) + { + Utility.WriteUInt32( output, this.m_size ); + Utility.WriteUInt32( output, this.m_headerFlags ); + Utility.WriteUInt32( output, this.m_height ); + Utility.WriteUInt32( output, this.m_width ); + Utility.WriteUInt32( output, this.m_pitchOrLinearSize ); + Utility.WriteUInt32( output, this.m_depth ); + Utility.WriteUInt32( output, this.m_mipMapCount ); + Utility.WriteUInt32( output, this.m_reserved1_0 ); + Utility.WriteUInt32( output, this.m_reserved1_1 ); + Utility.WriteUInt32( output, this.m_reserved1_2 ); + Utility.WriteUInt32( output, this.m_reserved1_3 ); + Utility.WriteUInt32( output, this.m_reserved1_4 ); + Utility.WriteUInt32( output, this.m_reserved1_5 ); + Utility.WriteUInt32( output, this.m_reserved1_6 ); + Utility.WriteUInt32( output, this.m_reserved1_7 ); + Utility.WriteUInt32( output, this.m_reserved1_8 ); + Utility.WriteUInt32( output, this.m_reserved1_9 ); + Utility.WriteUInt32( output, this.m_reserved1_10 ); + this.m_pixelFormat.Write( output ); + Utility.WriteUInt32( output, this.m_surfaceFlags ); + Utility.WriteUInt32( output, this.m_cubemapFlags ); + Utility.WriteUInt32( output, this.m_reserved2_0 ); + Utility.WriteUInt32( output, this.m_reserved2_1 ); + Utility.WriteUInt32( output, this.m_reserved2_2 ); + } + + } + + public class DdsFile + { + public DdsFile() + { + m_header = new DdsHeader(); + } + + public void Save( System.IO.Stream output, Surface surface, DdsSaveConfigToken ddsToken, ProgressEventHandler progressCallback ) + { + // For non-compressed textures, we need pixel width. + int pixelWidth = 0; + + // Identify if we're a compressed image + bool isCompressed = ( ( ddsToken.m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT1 ) || + ( ddsToken.m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT3 ) || + ( ddsToken.m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT5 ) ); + + // Compute mip map count.. + int mipCount = 1; + int mipWidth = surface.Width; + int mipHeight = surface.Height; + + if ( ddsToken.m_generateMipMaps ) + { + // This breaks! + + while ( ( mipWidth > 1 ) || ( mipHeight > 1 ) ) + { + mipCount++; + mipWidth /= 2; + mipHeight /= 2; + } + } + + // Populate bulk of our DdsHeader + m_header.m_size = m_header.Size(); + m_header.m_headerFlags = ( uint )( DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_TEXTURE ); + + if ( isCompressed ) + m_header.m_headerFlags |= ( uint )( DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_LINEARSIZE ); + else + m_header.m_headerFlags |= ( uint )( DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_PITCH ); + + if ( mipCount > 1 ) + m_header.m_headerFlags |= ( uint )( DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_MIPMAP ); + + m_header.m_height = ( uint )surface.Height; + m_header.m_width = ( uint )surface.Width; + + if ( isCompressed ) + { + // Compresssed textures have the linear flag set.So pitchOrLinearSize + // needs to contain the entire size of the DXT block. + int blockCount = ( ( surface.Width + 3 )/4 ) * ( ( surface.Height + 3 )/4 ); + int blockSize = ( ddsToken.m_fileFormat == 0 ) ? 8 : 16; + m_header.m_pitchOrLinearSize = ( uint )( blockCount * blockSize ); + } + else + { + // Non-compressed textures have the pitch flag set. So pitchOrLinearSize + // needs to contain the row pitch of the main image. DWORD aligned too. + switch ( ddsToken.m_fileFormat ) + { + case DdsFileFormat.DDS_FORMAT_A8R8G8B8: + case DdsFileFormat.DDS_FORMAT_X8R8G8B8: + case DdsFileFormat.DDS_FORMAT_A8B8G8R8: + case DdsFileFormat.DDS_FORMAT_X8B8G8R8: + pixelWidth = 4; // 32bpp + break; + + case DdsFileFormat.DDS_FORMAT_A1R5G5B5: + case DdsFileFormat.DDS_FORMAT_A4R4G4B4: + case DdsFileFormat.DDS_FORMAT_R5G6B5: + pixelWidth = 2; // 16bpp + break; + + case DdsFileFormat.DDS_FORMAT_R8G8B8: + pixelWidth = 3; // 24bpp + break; + } + + // Compute row pitch + m_header.m_pitchOrLinearSize = ( uint )( ( int )m_header.m_width * pixelWidth ); + +#if APPLY_PITCH_ALIGNMENT + // Align to DWORD, if we need to.. (see notes about pitch alignment all over this code) + m_header.m_pitchOrLinearSize = ( uint )( ( ( int )m_header.m_pitchOrLinearSize + 3 ) & ( ~3 ) ); +#endif //APPLY_PITCH_ALIGNMENT + } + + m_header.m_depth = 0; + m_header.m_mipMapCount = ( mipCount == 1 ) ? 0 : ( uint )mipCount; + m_header.m_reserved1_0 = 0; + m_header.m_reserved1_1 = 0; + m_header.m_reserved1_2 = 0; + m_header.m_reserved1_3 = 0; + m_header.m_reserved1_4 = 0; + m_header.m_reserved1_5 = 0; + m_header.m_reserved1_6 = 0; + m_header.m_reserved1_7 = 0; + m_header.m_reserved1_8 = 0; + m_header.m_reserved1_9 = 0; + m_header.m_reserved1_10 = 0; + + // Populate our DdsPixelFormat object + m_header.m_pixelFormat.Initialise( ddsToken.m_fileFormat ); + + // Populate miscellanous header flags + m_header.m_surfaceFlags = ( uint )DdsHeader.SurfaceFlags.DDS_SURFACE_FLAGS_TEXTURE; + + if ( mipCount > 1 ) + m_header.m_surfaceFlags |= ( uint )DdsHeader.SurfaceFlags.DDS_SURFACE_FLAGS_MIPMAP; + + m_header.m_cubemapFlags = 0; + m_header.m_reserved2_0 = 0; + m_header.m_reserved2_1 = 0; + m_header.m_reserved2_2 = 0; + + // Write out our DDS tag + Utility.WriteUInt32( output, 0x20534444 ); // 'DDS ' + + // Write out the header + m_header.Write( output ); + + int squishFlags = ddsToken.GetSquishFlags(); + + // Our output data array will be sized as necessary + byte[] outputData; + + // Reset our mip width & height variables... + mipWidth = surface.Width; + mipHeight = surface.Height; + + // Figure out how much total work each mip map is + Size[] writeSizes = new Size[mipCount]; + int[] mipPixels = new int[mipCount]; + int[] pixelsCompleted = new int[mipCount]; // # pixels completed once we have reached this mip + long totalPixels = 0; + for (int mipLoop = 0; mipLoop < mipCount; mipLoop++) + { + Size writeSize = new Size((mipWidth > 0) ? mipWidth : 1, (mipHeight > 0) ? mipHeight : 1); + writeSizes[mipLoop] = writeSize; + + int thisMipPixels = writeSize.Width * writeSize.Height; + mipPixels[mipLoop] = thisMipPixels; + + if (mipLoop == 0) + { + pixelsCompleted[mipLoop] = 0; + } + else + { + pixelsCompleted[mipLoop] = pixelsCompleted[mipLoop - 1] + mipPixels[mipLoop - 1]; + } + + totalPixels += thisMipPixels; + mipWidth /= 2; + mipHeight /= 2; + } + + mipWidth = surface.Width; + mipHeight = surface.Height; + + for (int mipLoop = 0; mipLoop < mipCount; mipLoop++) + { + Size writeSize = writeSizes[mipLoop]; + Surface writeSurface = new Surface(writeSize); + + if ( mipLoop == 0 ) + { + // No point resampling the first level.. it's got exactly what we want. + writeSurface = surface; + } + else + { + // I'd love to have a UI component to select what kind of resampling, but + // there's hardly any space for custom UI stuff in the Save Dialog. And I'm + // not having any scrollbars in there..! + // Also, note that each mip level is formed from the main level, to reduce + // compounded errors when generating mips. + writeSurface.SuperSamplingFitSurface( surface ); + } + + DdsSquish.ProgressFn progressFn = + delegate(int workDone, int workTotal) + { + long thisMipPixelsDone = workDone * (long)mipWidth; + long previousMipsPixelsDone = pixelsCompleted[mipLoop]; + double progress = (double)((double)thisMipPixelsDone + (double)previousMipsPixelsDone) / (double)totalPixels; + progressCallback(this, new ProgressEventArgs(100.0 * progress)); + }; + + if ( ( ddsToken.m_fileFormat >= DdsFileFormat.DDS_FORMAT_DXT1 ) && ( ddsToken.m_fileFormat <= DdsFileFormat.DDS_FORMAT_DXT5 ) ) + outputData = DdsSquish.CompressImage( writeSurface, squishFlags, (progressCallback == null) ? null : progressFn ); + else + { + int mipPitch = pixelWidth * writeSurface.Width; + + // From the DDS documents I read, I'd expected the pitch of each mip level to be + // DWORD aligned. As it happens, that's not the case. Re-aligning the pitch of + // each level results in later mips getting sheared as the pitch is incorrect. + // So, the following line is intentionally optional. Maybe the documentation + // is referring to the pitch when accessing the mip directly.. who knows. + // + // Infact, all the talk of non-compressed textures having DWORD alignment of pitch + // seems to be bollocks.. If I apply alignment, then they fail to load in 3rd Party + // or Microsoft DDS viewing applications. + // + +#if APPLY_PITCH_ALIGNMENT + mipPitch = ( mipPitch + 3 ) & ( ~3 ); +#endif // APPLY_PITCH_ALIGNMENT + + outputData = new byte[ mipPitch * writeSurface.Height ]; + outputData.Initialize(); + + for ( int y = 0; y < writeSurface.Height; y++ ) + { + for ( int x = 0; x < writeSurface.Width; x++ ) + { + // Get colour from surface + ColorBgra pixelColour = writeSurface.GetPoint( x, y ); + uint pixelData = 0; + + switch( ddsToken.m_fileFormat ) + { + case DdsFileFormat.DDS_FORMAT_A8R8G8B8: + { + pixelData = ( ( uint )pixelColour.A << 24 ) | + ( ( uint )pixelColour.R << 16 ) | + ( ( uint )pixelColour.G << 8 ) | + ( ( uint )pixelColour.B << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_X8R8G8B8: + { + pixelData = ( ( uint )pixelColour.R << 16 ) | + ( ( uint )pixelColour.G << 8 ) | + ( ( uint )pixelColour.B << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_A8B8G8R8: + { + pixelData = ( ( uint )pixelColour.A << 24 ) | + ( ( uint )pixelColour.B << 16 ) | + ( ( uint )pixelColour.G << 8 ) | + ( ( uint )pixelColour.R << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_X8B8G8R8: + { + pixelData = ( ( uint )pixelColour.B << 16 ) | + ( ( uint )pixelColour.G << 8 ) | + ( ( uint )pixelColour.R << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_A1R5G5B5: + { + pixelData = ( ( uint )( ( pixelColour.A != 0 ) ? 1 : 0 ) << 15 ) | + ( ( uint )( pixelColour.R >> 3 ) << 10 ) | + ( ( uint )( pixelColour.G >> 3 ) << 5 ) | + ( ( uint )( pixelColour.B >> 3 ) << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_A4R4G4B4: + { + pixelData = ( ( uint )( pixelColour.A >> 4 ) << 12 ) | + ( ( uint )( pixelColour.R >> 4 ) << 8 ) | + ( ( uint )( pixelColour.G >> 4 ) << 4 ) | + ( ( uint )( pixelColour.B >> 4 ) << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_R8G8B8: + { + pixelData = ( ( uint )pixelColour.R << 16 ) | + ( ( uint )pixelColour.G << 8 ) | + ( ( uint )pixelColour.B << 0 ); + break; + } + + case DdsFileFormat.DDS_FORMAT_R5G6B5: + { + pixelData = ( ( uint )( pixelColour.R >> 3 ) << 11 ) | + ( ( uint )( pixelColour.G >> 2 ) << 5 ) | + ( ( uint )( pixelColour.B >> 3 ) << 0 ); + break; + } + } + + // pixelData contains our target data.. so now set the pixel bytes + int pixelOffset = ( y * mipPitch ) + ( x * pixelWidth ); + for ( int loop = 0; loop < pixelWidth; loop++ ) + { + outputData[ pixelOffset + loop ] = ( byte )( ( pixelData >> ( 8 * loop ) ) & 0xff ); + } + } + + if (progressCallback != null) + { + long thisMipPixelsDone = (y + 1) * (long)mipWidth; + long previousMipsPixelsDone = pixelsCompleted[mipLoop]; + double progress = (double)((double)thisMipPixelsDone + (double)previousMipsPixelsDone) / (double)totalPixels; + progressCallback(this, new ProgressEventArgs(100.0 * progress)); + } + } + } + + // Write the data for this mip level out.. + output.Write( outputData, 0, outputData.GetLength( 0 ) ); + + mipWidth = mipWidth / 2; + mipHeight = mipHeight / 2; + } + } + + public void Load( System.IO.Stream input ) + { + // Read the DDS tag. If it's not right, then bail.. + uint ddsTag = ( uint )Utility.ReadUInt32( input ); + if ( ddsTag != 0x20534444 ) + throw new FormatException( "File does not appear to be a DDS image" ); + + // Read everything in.. for now assume it worked like a charm.. + m_header.Read( input ); + + if ( ( m_header.m_pixelFormat.m_flags & ( int )DdsPixelFormat.PixelFormatFlags.DDS_FOURCC ) != 0 ) + { + int squishFlags = 0; + + switch ( m_header.m_pixelFormat.m_fourCC ) + { + case 0x31545844: + squishFlags = ( int )DdsSquish.SquishFlags.kDxt1; + break; + + case 0x33545844: + squishFlags = ( int )DdsSquish.SquishFlags.kDxt3; + break; + + case 0x35545844: + squishFlags = ( int )DdsSquish.SquishFlags.kDxt5; + break; + + default: + throw new FormatException( "File is not a supported DDS format" ); + } + + // Compute size of compressed block area + int blockCount = ( ( GetWidth() + 3 )/4 ) * ( ( GetHeight() + 3 )/4 ); + int blockSize = ( ( squishFlags & ( int )DdsSquish.SquishFlags.kDxt1 ) != 0 ) ? 8 : 16; + + // Allocate room for compressed blocks, and read data into it. + byte[] compressedBlocks = new byte[ blockCount * blockSize ]; + input.Read( compressedBlocks, 0, compressedBlocks.GetLength( 0 ) ); + + // Now decompress.. + m_pixelData = DdsSquish.DecompressImage( compressedBlocks, GetWidth(), GetHeight(), squishFlags ); + } + else + { + // We can only deal with the non-DXT formats we know about.. this is a bit of a mess.. + // Sorry.. + DdsFileFormat fileFormat = DdsFileFormat.DDS_FORMAT_INVALID; + + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 32 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x00ff0000 ) && ( m_header.m_pixelFormat.m_gBitMask == 0x0000ff00 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x000000ff ) && ( m_header.m_pixelFormat.m_aBitMask == 0xff000000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_A8R8G8B8; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 32 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x00ff0000 ) && ( m_header.m_pixelFormat.m_gBitMask == 0x0000ff00 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x000000ff ) && ( m_header.m_pixelFormat.m_aBitMask == 0x00000000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_X8R8G8B8; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 32 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x000000ff ) && ( m_header.m_pixelFormat.m_gBitMask == 0x0000ff00 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x00ff0000 ) && ( m_header.m_pixelFormat.m_aBitMask == 0xff000000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_A8B8G8R8; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 32 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x000000ff ) && ( m_header.m_pixelFormat.m_gBitMask == 0x0000ff00 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x00ff0000 ) && ( m_header.m_pixelFormat.m_aBitMask == 0x00000000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_X8B8G8R8; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 16 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x00007c00 ) && ( m_header.m_pixelFormat.m_gBitMask == 0x000003e0 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x0000001f ) && ( m_header.m_pixelFormat.m_aBitMask == 0x00008000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_A1R5G5B5; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGBA ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 16 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x00000f00 ) && ( m_header.m_pixelFormat.m_gBitMask == 0x000000f0 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x0000000f ) && ( m_header.m_pixelFormat.m_aBitMask == 0x0000f000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_A4R4G4B4; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 24 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x00ff0000 ) && ( m_header.m_pixelFormat.m_gBitMask == 0x0000ff00 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x000000ff ) && ( m_header.m_pixelFormat.m_aBitMask == 0x00000000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_R8G8B8; + else + if ( ( m_header.m_pixelFormat.m_flags == ( int )DdsPixelFormat.PixelFormatFlags.DDS_RGB ) && + ( m_header.m_pixelFormat.m_rgbBitCount == 16 ) && + ( m_header.m_pixelFormat.m_rBitMask == 0x0000f800 ) && ( m_header.m_pixelFormat.m_gBitMask == 0x000007e0 ) && + ( m_header.m_pixelFormat.m_bBitMask == 0x0000001f ) && ( m_header.m_pixelFormat.m_aBitMask == 0x00000000 ) ) + fileFormat = DdsFileFormat.DDS_FORMAT_R5G6B5; + + // If fileFormat is still invalid, then it's an unsupported format. + if ( fileFormat == DdsFileFormat.DDS_FORMAT_INVALID ) + throw new FormatException( "File is not a supported DDS format" ); + + // Size of a source pixel, in bytes + int srcPixelSize = ( ( int )m_header.m_pixelFormat.m_rgbBitCount / 8 ); + + // We need the pitch for a row, so we can allocate enough memory for the load. + int rowPitch = 0; + + if ( ( m_header.m_headerFlags & ( int )DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_PITCH ) != 0 ) + { + // Pitch specified.. so we can use directly + rowPitch = ( int )m_header.m_pitchOrLinearSize; + } + else + if ( ( m_header.m_headerFlags & ( int )DdsHeader.HeaderFlags.DDS_HEADER_FLAGS_LINEARSIZE ) != 0 ) + { + // Linear size specified.. compute row pitch. Of course, this should never happen + // as linear size is *supposed* to be for compressed textures. But Microsoft don't + // always play by the rules when it comes to DDS output. + rowPitch = ( int )m_header.m_pitchOrLinearSize / ( int )m_header.m_height; + } + else + { + // Another case of Microsoft not obeying their standard is the 'Convert to..' shell extension + // that ships in the DirectX SDK. Seems to always leave flags empty..so no indication of pitch + // or linear size. And - to cap it all off - they leave pitchOrLinearSize as *zero*. Zero??? If + // we get this bizarre set of inputs, we just go 'screw it' and compute row pitch ourselves, + // making sure we DWORD align it (if that code path is enabled). + rowPitch = ( ( int )m_header.m_width * srcPixelSize ); + +#if APPLY_PITCH_ALIGNMENT + rowPitch = ( ( ( int )rowPitch + 3 ) & ( ~3 ) ); +#endif // APPLY_PITCH_ALIGNMENT + } + +// System.Diagnostics.Debug.WriteLine( "Image width : " + m_header.m_width + ", rowPitch = " + rowPitch ); + + // Ok.. now, we need to allocate room for the bytes to read in from.. it's rowPitch bytes * height + byte[] readPixelData = new byte[ rowPitch * m_header.m_height ]; + input.Read( readPixelData, 0, readPixelData.GetLength( 0 ) ); + + // We now need space for the real pixel data.. that's width * height * 4.. + m_pixelData = new byte[ m_header.m_width * m_header.m_height * 4 ]; + + // And now we have the arduous task of filling that up with stuff.. + for ( int destY = 0; destY < ( int )m_header.m_height; destY++ ) + { + for ( int destX = 0; destX < ( int )m_header.m_width; destX++ ) + { + // Compute source pixel offset + int srcPixelOffset = ( destY * rowPitch ) + ( destX * srcPixelSize ); + + // Read our pixel + uint pixelColour = 0; + uint pixelRed = 0; + uint pixelGreen = 0; + uint pixelBlue = 0; + uint pixelAlpha = 0; + + // Build our pixel colour as a DWORD + for ( int loop = 0; loop < srcPixelSize; loop++ ) + { + pixelColour |= ( uint )( readPixelData[ srcPixelOffset + loop ] << ( 8 * loop ) ); + } + + if ( fileFormat == DdsFileFormat.DDS_FORMAT_A8R8G8B8 ) + { + pixelAlpha = ( pixelColour >> 24 ) & 0xff; + pixelRed = ( pixelColour >> 16 ) & 0xff; + pixelGreen = ( pixelColour >> 8 ) & 0xff; + pixelBlue = ( pixelColour >> 0 ) & 0xff; + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_X8R8G8B8 ) + { + pixelAlpha = 0xff; + pixelRed = ( pixelColour >> 16 ) & 0xff; + pixelGreen = ( pixelColour >> 8 ) & 0xff; + pixelBlue = ( pixelColour >> 0 ) & 0xff; + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_A8B8G8R8 ) + { + pixelAlpha = ( pixelColour >> 24 ) & 0xff; + pixelRed = ( pixelColour >> 0 ) & 0xff; + pixelGreen = ( pixelColour >> 8 ) & 0xff; + pixelBlue = ( pixelColour >> 16 ) & 0xff; + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_X8B8G8R8 ) + { + pixelAlpha = 0xff; + pixelRed = ( pixelColour >> 0 ) & 0xff; + pixelGreen = ( pixelColour >> 8 ) & 0xff; + pixelBlue = ( pixelColour >> 16 ) & 0xff; + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_A1R5G5B5 ) + { + pixelAlpha = ( pixelColour >> 15 ) * 0xff; + pixelRed = ( pixelColour >> 10 ) & 0x1f; + pixelGreen = ( pixelColour >> 5 ) & 0x1f; + pixelBlue = ( pixelColour >> 0 ) & 0x1f; + + pixelRed = ( pixelRed << 3 ) | ( pixelRed >> 2 ); + pixelGreen = ( pixelGreen << 3 ) | ( pixelGreen >> 2 ); + pixelBlue = ( pixelBlue << 3 ) | ( pixelBlue >> 2 ); + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_A4R4G4B4 ) + { + pixelAlpha = ( pixelColour >> 12 ) & 0xff; + pixelRed = ( pixelColour >> 8 ) & 0x0f; + pixelGreen = ( pixelColour >> 4 ) & 0x0f; + pixelBlue = ( pixelColour >> 0 ) & 0x0f; + + pixelAlpha = ( pixelAlpha << 4 ) | ( pixelAlpha >> 0 ); + pixelRed = ( pixelRed << 4 ) | ( pixelRed >> 0 ); + pixelGreen = ( pixelGreen << 4 ) | ( pixelGreen >> 0 ); + pixelBlue = ( pixelBlue << 4 ) | ( pixelBlue >> 0 ); + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_R8G8B8 ) + { + pixelAlpha = 0xff; + pixelRed = ( pixelColour >> 16 ) & 0xff; + pixelGreen = ( pixelColour >> 8 ) & 0xff; + pixelBlue = ( pixelColour >> 0 ) & 0xff; + } + else + if ( fileFormat == DdsFileFormat.DDS_FORMAT_R5G6B5 ) + { + pixelAlpha = 0xff; + pixelRed = ( pixelColour >> 11 ) & 0x1f; + pixelGreen = ( pixelColour >> 5 ) & 0x3f; + pixelBlue = ( pixelColour >> 0 ) & 0x1f; + + pixelRed = ( pixelRed << 3 ) | ( pixelRed >> 2 ); + pixelGreen = ( pixelGreen << 2 ) | ( pixelGreen >> 4 ); + pixelBlue = ( pixelBlue << 3 ) | ( pixelBlue >> 2 ); + } + + // Write the colours away.. + int destPixelOffset = ( destY * ( int )m_header.m_width * 4 ) + ( destX * 4 ); + m_pixelData[ destPixelOffset + 0 ] = ( byte )pixelRed; + m_pixelData[ destPixelOffset + 1 ] = ( byte )pixelGreen; + m_pixelData[ destPixelOffset + 2 ] = ( byte )pixelBlue; + m_pixelData[ destPixelOffset + 3 ] = ( byte )pixelAlpha; + } + } + } + } + + public int GetWidth() + { + return ( int )m_header.m_width; + } + + public int GetHeight() + { + return ( int )m_header.m_height; + } + + public byte[] GetPixelData() + { + return m_pixelData; + } + + // Loaded DDS header (also uses storage for save) + public DdsHeader m_header; + + // Pixel data + byte[] m_pixelData; + + } +} \ No newline at end of file diff --git a/src/DdsFileType/DdsFileType/DdsFileType.cs b/src/DdsFileType/DdsFileType/DdsFileType.cs new file mode 100644 index 0000000..0f713d5 --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsFileType.cs @@ -0,0 +1,129 @@ +//------------------------------------------------------------------------------ +/* + @brief DDS File Type Plugin for Paint.NET + + @note Copyright (c) 2007 Dean Ashton http://www.dmashton.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**/ +//------------------------------------------------------------------------------ + +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; + +namespace DdsFileTypePlugin +{ + // -------------------------------------------------------- + + // We need this to register our DdsFileType object + public class DDSFileTypes : IFileTypeFactory + { + public static readonly FileType Dds = new DdsFileType(); + private static FileType[] fileTypes = new FileType[] { Dds }; + + internal FileTypeCollection GetFileTypeCollection() + { + return new FileTypeCollection( fileTypes ); + } + + public FileType[] GetFileTypeInstances() + { + DdsSquish.Initialize(); + return (FileType[])fileTypes.Clone(); + } + } + + // This is the core of the application.. + [Guid("77511FB1-CA18-4424-8957-4C5F86EB7CD0")] + public class DdsFileType : FileType + { + + public DdsFileType() + : base(PdnResources.GetString("DdsFileType.Name"), + FileTypeFlags.SupportsLoading | FileTypeFlags.SupportsSaving | FileTypeFlags.SavesWithProgress, + new string[] { ".dds" } ) + { + } + + public override SaveConfigWidget CreateSaveConfigWidget() + { + return new DdsSaveConfigWidget(); + } + + protected override SaveConfigToken OnCreateDefaultSaveConfigToken() + { + return new DdsSaveConfigToken( 0, 0, 0, false, false ); + } + + protected override unsafe void OnSave( Document input, Stream output, SaveConfigToken token, Surface scratchSurface, ProgressEventHandler callback ) + { + DdsSaveConfigToken ddsToken = ( DdsSaveConfigToken )token; + + // We need to be able to feast on the goo inside.. + scratchSurface.Clear( ColorBgra.Transparent ); + + using ( RenderArgs ra = new RenderArgs( scratchSurface ) ) + { + input.Render( ra, true ); + } + + // Create the DDS file, and save it.. + DdsFile ddsFile = new DdsFile(); + ddsFile.Save( output, scratchSurface, ddsToken, callback ); + } + + protected override Document OnLoad( Stream input ) + { + DdsFile ddsFile = new DdsFile(); + ddsFile.Load( input ); + + BitmapLayer layer = Layer.CreateBackgroundLayer( ddsFile.GetWidth(), ddsFile.GetHeight() ); + Surface surface = layer.Surface; + ColorBgra writeColour = new ColorBgra(); + + byte[] readPixelData = ddsFile.GetPixelData(); + + for ( int y = 0; y < ddsFile.GetHeight(); y++ ) + { + for ( int x = 0; x < ddsFile.GetWidth(); x++ ) + { + int readPixelOffset = ( y * ddsFile.GetWidth() * 4 ) + ( x * 4 ); + + writeColour.R = readPixelData[ readPixelOffset + 0 ]; + writeColour.G = readPixelData[ readPixelOffset + 1 ]; + writeColour.B = readPixelData[ readPixelOffset + 2 ]; + writeColour.A = readPixelData[ readPixelOffset + 3 ]; + + surface[ x, y ] = writeColour; + } + } + + // Create a document, add the surface layer to it, and return to caller. + Document document = new Document( surface.Width, surface.Height ); + document.Layers.Add( layer ); + return document; + } + } +} diff --git a/src/DdsFileType/DdsFileType/DdsFileType.csproj b/src/DdsFileType/DdsFileType/DdsFileType.csproj new file mode 100644 index 0000000..317c551 --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsFileType.csproj @@ -0,0 +1,96 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81} + Library + Properties + DdsFileTypePlugin + DdsFileType + + + 2.0 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + + + + + + + + UserControl + + + + + + + Designer + DdsSaveConfigWidget.cs + + + + + + + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + {1EADE568-A866-4DD4-9898-0A151E3F0E26} + Core + + + {66681BB0-955D-451D-A466-94C045B1CF4A} + Data + + + {0B173113-1F9B-4939-A62F-A176336F13AC} + Resources + + + {80572820-93A5-4278-A513-D902BEA2639C} + SystemLayer + + + + + + + + + \ No newline at end of file diff --git a/src/DdsFileType/DdsFileType/DdsFileType.sln b/src/DdsFileType/DdsFileType/DdsFileType.sln new file mode 100644 index 0000000..244887a --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsFileType.sln @@ -0,0 +1,38 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DdsFileType", "DdsFileType.csproj", "{8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Win32.ActiveCfg = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Win32.Build.0 = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|x64.ActiveCfg = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|x64.Build.0 = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Any CPU.Build.0 = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Win32.ActiveCfg = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Win32.Build.0 = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|x64.ActiveCfg = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/DdsFileType/DdsFileType/DdsSaveConfigToken.cs b/src/DdsFileType/DdsFileType/DdsSaveConfigToken.cs new file mode 100644 index 0000000..3f6eef7 --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsSaveConfigToken.cs @@ -0,0 +1,119 @@ +//------------------------------------------------------------------------------ +/* + @brief DDS File Type Plugin for Paint.NET + + @note Copyright (c) 2007 Dean Ashton http://www.dmashton.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**/ +//------------------------------------------------------------------------------ + +using System; +using PaintDotNet; + +namespace DdsFileTypePlugin +{ + [Serializable] + public class DdsSaveConfigToken : SaveConfigToken + { + public override object Clone() + { + return new DdsSaveConfigToken( this ); + } + + public DdsSaveConfigToken( DdsFileFormat fileFormat, int compressorType, int errorMetric, bool weightColourByAlpha, bool generateMipMaps ) + { + m_fileFormat = fileFormat; + m_compressorType = compressorType; + m_errorMetric = errorMetric; + m_weightColourByAlpha = weightColourByAlpha; + m_generateMipMaps = generateMipMaps; + } + + // Converts token information into a form ready for passing on to Squish. + public int GetSquishFlags() + { + int squishFlags = 0; + + // Translate file format + if ( m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT1 ) + squishFlags |= ( int )DdsSquish.SquishFlags.kDxt1; + else + if ( m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT3 ) + squishFlags |= ( int )DdsSquish.SquishFlags.kDxt3; + else + if ( m_fileFormat == DdsFileFormat.DDS_FORMAT_DXT5 ) + squishFlags |= ( int )DdsSquish.SquishFlags.kDxt5; + + // If this isn't a DXT file, then no flags + if ( squishFlags == 0 ) + return squishFlags; + + // Translate compressor type + if ( m_compressorType == 0 ) + squishFlags |= ( int )DdsSquish.SquishFlags.kColourClusterFit; + else + if ( m_compressorType == 1 ) + squishFlags |= ( int )DdsSquish.SquishFlags.kColourRangeFit; + else + squishFlags |= ( int )DdsSquish.SquishFlags.kColourIterativeClusterFit; + + // Translate error metric + if ( m_errorMetric == 0 ) + squishFlags |= ( int )DdsSquish.SquishFlags.kColourMetricPerceptual; + else + squishFlags |= ( int )DdsSquish.SquishFlags.kColourMetricUniform; + + // Now the colour weighting state (only valid for cluster fit) + if ( ( m_compressorType == 0 )&& ( m_weightColourByAlpha ) ) + squishFlags |= ( int )DdsSquish.SquishFlags.kWeightColourByAlpha; + + return squishFlags; + } + + protected DdsSaveConfigToken( DdsSaveConfigToken copyMe ) + { + m_fileFormat = copyMe.m_fileFormat; + m_compressorType = copyMe.m_compressorType; + m_errorMetric = copyMe.m_errorMetric; + m_weightColourByAlpha = copyMe.m_weightColourByAlpha; + m_generateMipMaps = copyMe.m_generateMipMaps; + } + + public override void Validate() + { + if ( ( m_compressorType != 0 ) && ( m_compressorType != 1 ) && ( m_compressorType != 2 ) ) + { + throw new ArgumentOutOfRangeException( "Unrecognised compressor type (" + m_compressorType + ")" ); + } + + if ( ( m_errorMetric != 0 ) && ( m_errorMetric != 1 ) ) + { + throw new ArgumentOutOfRangeException( "Unrecognised error metric type (" + m_errorMetric + ")" ); + } + } + + public DdsFileFormat m_fileFormat; + public int m_compressorType; + public int m_errorMetric; + public bool m_weightColourByAlpha; + public bool m_generateMipMaps; + } +} diff --git a/src/DdsFileType/DdsFileType/DdsSaveConfigWidget.cs b/src/DdsFileType/DdsFileType/DdsSaveConfigWidget.cs new file mode 100644 index 0000000..04a3b23 --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsSaveConfigWidget.cs @@ -0,0 +1,415 @@ +//------------------------------------------------------------------------------ +/* + @brief DDS File Type Plugin for Paint.NET + + @note Copyright (c) 2007 Dean Ashton http://www.dmashton.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**/ +//------------------------------------------------------------------------------ + +using PaintDotNet; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace DdsFileTypePlugin +{ + class DdsSaveConfigWidget : PaintDotNet.SaveConfigWidget + { + private System.Windows.Forms.RadioButton rangeFit; + private System.Windows.Forms.RadioButton clusterFit; + private System.Windows.Forms.RadioButton iterativeFit; + private System.Windows.Forms.RadioButton uniformMetric; + private System.Windows.Forms.RadioButton perceptualMetric; + private System.Windows.Forms.CheckBox weightColourByAlpha; + private System.Windows.Forms.ComboBox fileFormatList; + private System.Windows.Forms.CheckBox generateMipMaps; + private PaintDotNet.HeaderLabel compressorTypeLabel; + private PaintDotNet.HeaderLabel errorMetricLabel; + private PaintDotNet.HeaderLabel additionalOptionsLabel; + private System.Windows.Forms.Panel compressorTypePanel; + private System.Windows.Forms.Panel errorMetricPanel; + private System.Windows.Forms.Panel additionalOptionsPanel; + + public DdsSaveConfigWidget() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + } + + protected override void InitFileType() + { + this.fileType = new DdsFileType(); + } + + protected override void InitTokenFromWidget() + { + ((DdsSaveConfigToken)this.token).m_fileFormat = ( DdsFileFormat)this.fileFormatList.SelectedIndex; + if ( this.clusterFit.Checked ) + ((DdsSaveConfigToken)this.token).m_compressorType = 0; + else + if ( this.rangeFit.Checked ) + ((DdsSaveConfigToken)this.token).m_compressorType = 1; + else + ((DdsSaveConfigToken)this.token).m_compressorType = 2; + + ((DdsSaveConfigToken)this.token).m_errorMetric = this.perceptualMetric.Checked ? 0 : 1; + ((DdsSaveConfigToken)this.token).m_weightColourByAlpha = this.weightColourByAlpha.Checked; + ((DdsSaveConfigToken)this.token).m_generateMipMaps = this.generateMipMaps.Checked; + } + + protected override void InitWidgetFromToken(SaveConfigToken token) + { + if (token is DdsSaveConfigToken) + { + DdsSaveConfigToken ddsToken = (DdsSaveConfigToken)token; + this.fileFormatList.SelectedIndex = ( int )ddsToken.m_fileFormat; + + this.clusterFit.Checked = ( ddsToken.m_compressorType == 0 ); + this.rangeFit.Checked = ( ddsToken.m_compressorType == 1 ); + this.iterativeFit.Checked = ( ddsToken.m_compressorType == 2 ); + + this.perceptualMetric.Checked = ( ddsToken.m_errorMetric == 0 ); + this.uniformMetric.Checked = !this.perceptualMetric.Checked; + + this.weightColourByAlpha.Checked = ddsToken.m_weightColourByAlpha; + + this.generateMipMaps.Checked = ddsToken.m_generateMipMaps; + } + else + { + this.fileFormatList.SelectedIndex = 0; + + this.clusterFit.Checked = true; + this.rangeFit.Checked = false; + this.iterativeFit.Checked = false; + + this.perceptualMetric.Checked = true; + this.uniformMetric.Checked = false; + + this.weightColourByAlpha.Checked = false; + + this.generateMipMaps.Checked = false; + } + } + + private void InitializeComponent() + { + this.rangeFit = new System.Windows.Forms.RadioButton(); + this.clusterFit = new System.Windows.Forms.RadioButton(); + this.iterativeFit = new System.Windows.Forms.RadioButton(); + this.uniformMetric = new System.Windows.Forms.RadioButton(); + this.perceptualMetric = new System.Windows.Forms.RadioButton(); + this.generateMipMaps = new System.Windows.Forms.CheckBox(); + this.weightColourByAlpha = new System.Windows.Forms.CheckBox(); + this.fileFormatList = new System.Windows.Forms.ComboBox(); + this.compressorTypeLabel = new PaintDotNet.HeaderLabel(); + this.errorMetricLabel = new PaintDotNet.HeaderLabel(); + this.additionalOptionsLabel = new PaintDotNet.HeaderLabel(); + this.compressorTypePanel = new System.Windows.Forms.Panel(); + this.errorMetricPanel = new System.Windows.Forms.Panel(); + this.additionalOptionsPanel = new System.Windows.Forms.Panel(); + this.compressorTypePanel.SuspendLayout(); + this.errorMetricPanel.SuspendLayout(); + this.additionalOptionsPanel.SuspendLayout(); + this.SuspendLayout(); + // + // rangeFit + // + this.rangeFit.AutoSize = false; + this.rangeFit.Name = "rangeFit"; + this.rangeFit.TabIndex = 0; + this.rangeFit.TabStop = true; + this.rangeFit.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.RangeFit.Text"); // "Range fit (Fast/LQ)" + this.rangeFit.UseVisualStyleBackColor = true; + this.rangeFit.CheckedChanged += new System.EventHandler(this.rangeFit_CheckedChanged); + this.rangeFit.FlatStyle = FlatStyle.System; + // + // clusterFit + // + this.clusterFit.AutoSize = false; + this.clusterFit.Name = "clusterFit"; + this.clusterFit.TabIndex = 1; + this.clusterFit.TabStop = true; + this.clusterFit.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.ClusterFit.Text"); // "Cluster fit (Slow/HQ)" + this.clusterFit.UseVisualStyleBackColor = true; + this.clusterFit.CheckedChanged += new System.EventHandler(this.clusterFit_CheckedChanged); + this.clusterFit.FlatStyle = FlatStyle.System; + // + // iterativeFit + // + this.iterativeFit.AutoSize = false; + this.iterativeFit.Name = "iterativeFit"; + this.iterativeFit.TabIndex = 2; + this.iterativeFit.TabStop = true; + this.iterativeFit.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.IterativeFit.Text"); // "Iterative fit (Slowest/HQ)"; + this.iterativeFit.UseVisualStyleBackColor = true; + this.iterativeFit.CheckedChanged += new System.EventHandler(this.iterativeFit_CheckedChanged); + this.iterativeFit.FlatStyle = FlatStyle.System; + // + // uniformMetric + // + this.uniformMetric.AutoSize = false; + this.uniformMetric.Name = "uniformMetric"; + this.uniformMetric.TabIndex = 0; + this.uniformMetric.TabStop = true; + this.uniformMetric.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.Uniform.Text"); // "Uniform"; + this.uniformMetric.UseVisualStyleBackColor = true; + this.uniformMetric.CheckedChanged += new System.EventHandler(this.uniformMetric_CheckedChanged); + this.uniformMetric.FlatStyle = FlatStyle.System; + // + // perceptualMetric + // + this.perceptualMetric.AutoSize = false; + this.perceptualMetric.Name = "perceptualMetric"; + this.perceptualMetric.TabIndex = 1; + this.perceptualMetric.TabStop = true; + this.perceptualMetric.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.Perceptual.Text"); // "Perceptual"; + this.perceptualMetric.UseVisualStyleBackColor = true; + this.perceptualMetric.CheckedChanged += new System.EventHandler(this.perceptualMetric_CheckedChanged); + this.perceptualMetric.FlatStyle = FlatStyle.System; + // + // generateMipMaps + // + this.generateMipMaps.AutoSize = false; + this.generateMipMaps.Name = "generateMipMaps"; + this.generateMipMaps.TabIndex = 1; + this.generateMipMaps.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.GenerateMipMaps.Text"); // "Generate Mip Maps"; + this.generateMipMaps.UseVisualStyleBackColor = true; + this.generateMipMaps.CheckedChanged += new System.EventHandler(this.generateMipLevels_CheckedChanged); + this.generateMipMaps.FlatStyle = FlatStyle.System; + // + // weightColourByAlpha + // + this.weightColourByAlpha.AutoSize = false; + this.weightColourByAlpha.Name = "weightColourByAlpha"; + this.weightColourByAlpha.TabIndex = 0; + this.weightColourByAlpha.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.WeightColourByAlpha"); // "Weight Colour By Alpha"; + this.weightColourByAlpha.UseVisualStyleBackColor = true; + this.weightColourByAlpha.CheckedChanged += new System.EventHandler(this.weightColourByAlpha_CheckedChanged); + this.weightColourByAlpha.FlatStyle = FlatStyle.System; + // + // fileFormatList + // + this.fileFormatList.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.fileFormatList.FormattingEnabled = true; + this.fileFormatList.Items.AddRange(new object[] { + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.DXT1"), // "DXT1 (Opaque/1-bit Alpha)", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.DXT3"), // "DXT3 (Explicit Alpha)", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.DXT5"), // "DXT5 (Interpolated Alpha)", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.A8R8G8B8"), // "A8R8G8B8", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.X8R8G8B8"), // "X8R8G8B8", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.A8B8G8R8"), // "A8B8G8R8", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.X8B8G8R8"), // "X8B8G8R8", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.A1R5G5B5"), // "A1R5G5B5", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.A4R4G4B4"), // "A4R4G4B4", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.R8G8B8"), // "R8G8B8", + PdnResources.GetString("DdsFileType.SaveConfigWidget.FileFormatList.R5G6B5") // "R5G6B5" + }); + this.fileFormatList.Name = "fileFormatList"; + this.fileFormatList.TabIndex = 0; + this.fileFormatList.SelectedIndexChanged += new System.EventHandler(this.fileFormatList_SelectedIndexChanged); + this.fileFormatList.FlatStyle = FlatStyle.System; + // + // compressorTypeLabel + // + this.compressorTypeLabel.Name = "compressorTypeLabel"; + this.compressorTypeLabel.RightMargin = 0; + this.compressorTypeLabel.TabIndex = 1; + this.compressorTypeLabel.TabStop = false; + this.compressorTypeLabel.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.CompressorTypeLabel.Text"); // "Compressor Type"; + // + // errorMetricLabel + // + this.errorMetricLabel.Name = "errorMetricLabel"; + this.errorMetricLabel.RightMargin = 0; + this.errorMetricLabel.TabIndex = 3; + this.errorMetricLabel.TabStop = false; + this.errorMetricLabel.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.ErrorMetricLabel.Text"); // "Error Metric"; + // + // additionalOptionsLabel + // + this.additionalOptionsLabel.Name = "additionalOptionsLabel"; + this.additionalOptionsLabel.RightMargin = 0; + this.additionalOptionsLabel.TabIndex = 5; + this.additionalOptionsLabel.TabStop = false; + this.additionalOptionsLabel.Text = PdnResources.GetString("DdsFileType.SaveConfigWidget.AdditionalOptions.Text"); // "Additional Options"; + // + // compressorTypePanel + // + this.compressorTypePanel.Controls.Add(this.rangeFit); + this.compressorTypePanel.Controls.Add(this.clusterFit); + this.compressorTypePanel.Controls.Add(this.iterativeFit); + this.compressorTypePanel.Name = "compressorTypePanel"; + this.compressorTypePanel.TabIndex = 2; + // + // errorMetricPanel + // + this.errorMetricPanel.Controls.Add(this.uniformMetric); + this.errorMetricPanel.Controls.Add(this.perceptualMetric); + this.errorMetricPanel.Name = "errorMetricPanel"; + this.errorMetricPanel.TabIndex = 4; + // + // additionalOptionsPanel + // + this.additionalOptionsPanel.Controls.Add(this.generateMipMaps); + this.additionalOptionsPanel.Controls.Add(this.weightColourByAlpha); + this.additionalOptionsPanel.Name = "additionalOptionsPanel"; + this.additionalOptionsPanel.TabIndex = 6; + // + // DdsSaveConfigWidget + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.Controls.Add(this.fileFormatList); + this.Controls.Add(this.compressorTypePanel); + this.Controls.Add(this.compressorTypeLabel); + this.Controls.Add(this.errorMetricPanel); + this.Controls.Add(this.errorMetricLabel); + this.Controls.Add(this.additionalOptionsPanel); + this.Controls.Add(this.additionalOptionsLabel); + this.Name = "DdsSaveConfigWidget"; + this.compressorTypePanel.ResumeLayout(false); + this.compressorTypePanel.PerformLayout(); + this.errorMetricPanel.ResumeLayout(false); + this.errorMetricPanel.PerformLayout(); + this.additionalOptionsPanel.ResumeLayout(false); + this.additionalOptionsPanel.PerformLayout(); + this.ResumeLayout(false); + } + + protected override void OnLayout(LayoutEventArgs e) + { + int vMargin = UI.ScaleHeight(4); + int hInset = UI.ScaleWidth(16); + + AutoSizeStrategy autoSizeStrategy = AutoSizeStrategy.ExpandHeightToContentAndKeepWidth; + EdgeSnapOptions edgeSnapOptions = + EdgeSnapOptions.SnapLeftEdgeToContainerLeftEdge | + EdgeSnapOptions.SnapRightEdgeToContainerRightEdge; + + this.fileFormatList.Location = new Point(0, 0); + this.fileFormatList.Width = ClientSize.Width; + this.fileFormatList.PerformLayout(); + + this.compressorTypeLabel.Location = new Point(0, this.fileFormatList.Bottom + vMargin * 2); + this.compressorTypeLabel.Size = this.compressorTypeLabel.GetPreferredSize(new Size(ClientSize.Width - this.compressorTypeLabel.Left, 1)); + this.compressorTypeLabel.PerformLayout(); + + this.compressorTypePanel.SuspendLayout(); + this.compressorTypePanel.Location = new Point(hInset, this.compressorTypeLabel.Bottom + vMargin); + this.compressorTypePanel.Width = ClientSize.Width - this.compressorTypePanel.Left; + this.rangeFit.Location = new Point(0, 0); + LayoutUtility.PerformAutoLayout(this.rangeFit, autoSizeStrategy, edgeSnapOptions); + this.clusterFit.Location = new Point(0, this.rangeFit.Bottom + vMargin); + LayoutUtility.PerformAutoLayout(this.clusterFit, autoSizeStrategy, edgeSnapOptions); + this.iterativeFit.Location = new Point(0, this.clusterFit.Bottom + vMargin); + LayoutUtility.PerformAutoLayout(this.iterativeFit, autoSizeStrategy, edgeSnapOptions); + this.compressorTypePanel.Height = this.iterativeFit.Bottom; + this.compressorTypePanel.ResumeLayout(true); + + this.errorMetricLabel.Location = new Point(0, this.compressorTypePanel.Bottom + vMargin * 2); + this.errorMetricLabel.Size = this.errorMetricLabel.GetPreferredSize(new Size(ClientSize.Width - this.errorMetricLabel.Left, 1)); + this.errorMetricLabel.PerformLayout(); + + this.errorMetricPanel.SuspendLayout(); + this.errorMetricPanel.Location = new Point(hInset, this.errorMetricLabel.Bottom + vMargin); + this.errorMetricPanel.Width = ClientSize.Width - this.errorMetricPanel.Left; + this.uniformMetric.Location = new Point(0, 0); + LayoutUtility.PerformAutoLayout(this.uniformMetric, autoSizeStrategy, edgeSnapOptions); + this.perceptualMetric.Location = new Point(0, this.uniformMetric.Bottom + vMargin); + LayoutUtility.PerformAutoLayout(this.perceptualMetric, autoSizeStrategy, edgeSnapOptions); + this.errorMetricPanel.Height = this.perceptualMetric.Bottom; + this.errorMetricPanel.ResumeLayout(true); + + this.additionalOptionsLabel.Location = new Point(0, this.errorMetricPanel.Bottom + vMargin * 2); + this.additionalOptionsLabel.Size = this.additionalOptionsLabel.GetPreferredSize(new Size(ClientSize.Width - this.additionalOptionsLabel.Left, 1)); + this.additionalOptionsLabel.PerformLayout(); + + this.additionalOptionsPanel.SuspendLayout(); + this.additionalOptionsPanel.Location = new Point(hInset, this.additionalOptionsLabel.Bottom + vMargin); + this.additionalOptionsPanel.Width = ClientSize.Width - this.additionalOptionsPanel.Left; + this.weightColourByAlpha.Location = new Point(0, 0); + LayoutUtility.PerformAutoLayout(this.weightColourByAlpha, autoSizeStrategy, edgeSnapOptions); + this.generateMipMaps.Location = new Point(0, this.weightColourByAlpha.Bottom + vMargin); + LayoutUtility.PerformAutoLayout(this.generateMipMaps, autoSizeStrategy, edgeSnapOptions); + this.additionalOptionsPanel.Height = this.generateMipMaps.Bottom; + this.additionalOptionsPanel.ResumeLayout(true); + + this.ClientSize = new Size(ClientSize.Width, this.additionalOptionsPanel.Bottom); + base.OnLayout(e); + } + + private void CommonCompressorTypeChangeHandling(object sender, EventArgs e) + { + this.clusterFit.Enabled = ( this.fileFormatList.SelectedIndex < 3 ); + this.rangeFit.Enabled = ( this.fileFormatList.SelectedIndex < 3 ); + this.iterativeFit.Enabled = ( this.fileFormatList.SelectedIndex < 3 ); + this.weightColourByAlpha.Enabled = ( this.clusterFit.Checked || this.iterativeFit.Checked ) && ( this.fileFormatList.SelectedIndex < 3 ); + this.uniformMetric.Enabled = ( this.fileFormatList.SelectedIndex < 3 ); + this.perceptualMetric.Enabled = ( this.fileFormatList.SelectedIndex < 3 ); + this.UpdateToken(); + } + + private void fileFormatList_SelectedIndexChanged(object sender, EventArgs e) + { + CommonCompressorTypeChangeHandling( sender, e ); + } + + private void clusterFit_CheckedChanged(object sender, EventArgs e) + { + CommonCompressorTypeChangeHandling( sender, e ); + } + + private void rangeFit_CheckedChanged(object sender, EventArgs e) + { + CommonCompressorTypeChangeHandling( sender, e ); + } + + private void iterativeFit_CheckedChanged(object sender, EventArgs e) + { + CommonCompressorTypeChangeHandling( sender, e ); + } + + private void perceptualMetric_CheckedChanged(object sender, EventArgs e) + { + this.UpdateToken(); + } + + private void uniformMetric_CheckedChanged(object sender, EventArgs e) + { + this.UpdateToken(); + } + + private void weightColourByAlpha_CheckedChanged(object sender, EventArgs e) + { + this.UpdateToken(); + } + + private void generateMipLevels_CheckedChanged(object sender, EventArgs e) + { + this.UpdateToken(); + } + } +} diff --git a/src/DdsFileType/DdsFileType/DdsSaveConfigWidget.resx b/src/DdsFileType/DdsFileType/DdsSaveConfigWidget.resx new file mode 100644 index 0000000..ff31a6d --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsSaveConfigWidget.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/DdsFileType/DdsFileType/DdsSquish.cs b/src/DdsFileType/DdsFileType/DdsSquish.cs new file mode 100644 index 0000000..4ec4ddc --- /dev/null +++ b/src/DdsFileType/DdsFileType/DdsSquish.cs @@ -0,0 +1,231 @@ +//------------------------------------------------------------------------------ +/* + @brief DDS File Type Plugin for Paint.NET + + @note Copyright (c) 2007 Dean Ashton http://www.dmashton.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**/ +//------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Drawing; +using System.Runtime.InteropServices; +using PaintDotNet; +using PaintDotNet.SystemLayer; + +namespace DdsFileTypePlugin +{ + internal sealed class DdsSquish + { + public enum SquishFlags + { + kDxt1 = ( 1 << 0 ), // Use DXT1 compression. + kDxt3 = ( 1 << 1 ), // Use DXT3 compression. + kDxt5 = ( 1 << 2 ), // Use DXT5 compression. + + kColourClusterFit = ( 1 << 3 ), // Use a slow but high quality colour compressor (the default). + kColourRangeFit = ( 1 << 4 ), // Use a fast but low quality colour compressor. + + kColourMetricPerceptual = ( 1 << 5 ), // Use a perceptual metric for colour error (the default). + kColourMetricUniform = ( 1 << 6 ), // Use a uniform metric for colour error. + + kWeightColourByAlpha = ( 1 << 7 ), // Weight the colour by alpha during cluster fit (disabled by default). + + kColourIterativeClusterFit = ( 1 << 8 ), // Use a very slow but very high quality colour compressor. + } + + private static bool Is64Bit() + { + return ( Marshal.SizeOf( IntPtr.Zero ) == 8 ); + } + + internal delegate void ProgressFn(int workDone, int workTotal); + + private sealed class SquishInterface_32 + { + [DllImport("Squish_x86.dll")] + internal static extern unsafe void SquishCompressImage(byte* rgba, int width, int height, byte* blocks, int flags, + [MarshalAs(UnmanagedType.FunctionPtr)] ProgressFn progressFn); + + [DllImport("Squish_x86.dll")] + internal static extern unsafe void SquishDecompressImage(byte* rgba, int width, int height, byte* blocks, int flags, + [MarshalAs(UnmanagedType.FunctionPtr)] ProgressFn progressFn); + + [DllImport("Squish_x86.dll")] + internal static extern void SquishInitialize(); + } + + private sealed class SquishInterface_32_SSE2 + { + [DllImport("Squish_x86_SSE2.dll")] + internal static extern unsafe void SquishCompressImage(byte* rgba, int width, int height, byte* blocks, int flags, + [MarshalAs(UnmanagedType.FunctionPtr)] ProgressFn progressFn); + + [DllImport("Squish_x86_SSE2.dll")] + internal static extern unsafe void SquishDecompressImage(byte* rgba, int width, int height, byte* blocks, int flags, + [MarshalAs(UnmanagedType.FunctionPtr)] ProgressFn progressFn); + + [DllImport("Squish_x86_SSE2.dll")] + internal static extern void SquishInitialize(); + } + + private sealed class SquishInterface_64 + { + [DllImport("Squish_x64.dll")] + internal static extern unsafe void SquishCompressImage( byte* rgba, int width, int height, byte* blocks, int flags, + [MarshalAs(UnmanagedType.FunctionPtr)] ProgressFn progressFn); + + [DllImport("Squish_x64.dll")] + internal static extern unsafe void SquishDecompressImage( byte* rgba, int width, int height, byte* blocks, int flags, + [MarshalAs(UnmanagedType.FunctionPtr)] ProgressFn progressFn); + + [DllImport("Squish_x64.dll")] + internal static extern void SquishInitialize(); + } + + private static unsafe void CallCompressImage( byte[] rgba, int width, int height, byte[] blocks, int flags, ProgressFn progressFn ) + { + fixed ( byte* pRGBA = rgba ) + { + fixed ( byte* pBlocks = blocks ) + { + if ( Processor.Architecture == ProcessorArchitecture.X64 ) + SquishInterface_64.SquishCompressImage( pRGBA, width, height, pBlocks, flags, progressFn ); + else if ( Processor.IsFeaturePresent(ProcessorFeature.SSE2) ) + SquishInterface_32_SSE2.SquishCompressImage(pRGBA, width, height, pBlocks, flags, progressFn); + else + SquishInterface_32.SquishCompressImage( pRGBA, width, height, pBlocks, flags, progressFn ); + } + } + + GC.KeepAlive(progressFn); + } + + private static unsafe void CallDecompressImage( byte[] rgba, int width, int height, byte[] blocks, int flags, ProgressFn progressFn ) + { + fixed ( byte* pRGBA = rgba ) + { + fixed ( byte* pBlocks = blocks ) + { + if ( Processor.Architecture == ProcessorArchitecture.X64 ) + SquishInterface_64.SquishDecompressImage(pRGBA, width, height, pBlocks, flags, progressFn); + else if ( Processor.IsFeaturePresent(ProcessorFeature.SSE2) ) + SquishInterface_32_SSE2.SquishDecompressImage( pRGBA, width, height, pBlocks, flags, progressFn ); + else + SquishInterface_32.SquishDecompressImage( pRGBA, width, height, pBlocks, flags, progressFn ); + } + } + + GC.KeepAlive(progressFn); + } + + public static void Initialize() + { + if (Processor.Architecture == ProcessorArchitecture.X64) + { + SquishInterface_64.SquishInitialize(); + } + else if (Processor.IsFeaturePresent(ProcessorFeature.SSE2)) + { + SquishInterface_32_SSE2.SquishInitialize(); + } + else + { + SquishInterface_32.SquishInitialize(); + } + } + + // --------------------------------------------------------------------------------------- + // CompressImage + // --------------------------------------------------------------------------------------- + // + // Params + // inputSurface : Source byte array containing RGBA pixel data + // flags : Flags for squish compression control + // + // Return + // blockData : Array of bytes containing compressed blocks + // + // --------------------------------------------------------------------------------------- + + internal static byte[] CompressImage( Surface inputSurface, int squishFlags, ProgressFn progressFn ) + { + // We need the input to be in a byte array for squish.. so create one. + byte[] pixelData = new byte[ inputSurface.Width * inputSurface.Height * 4 ]; + + for ( int y = 0; y < inputSurface.Height; y++ ) + { + for ( int x = 0; x < inputSurface.Width; x++ ) + { + ColorBgra pixelColour = inputSurface.GetPoint( x, y ); + int pixelOffset = ( y * inputSurface.Width * 4 ) + ( x * 4 ); + + pixelData[ pixelOffset + 0 ] = pixelColour.R; + pixelData[ pixelOffset + 1 ] = pixelColour.G; + pixelData[ pixelOffset + 2 ] = pixelColour.B; + pixelData[ pixelOffset + 3 ] = pixelColour.A; + } + } + + // Compute size of compressed block area, and allocate + int blockCount = ( ( inputSurface.Width + 3 )/4 ) * ( ( inputSurface.Height + 3 )/4 ); + int blockSize = ( ( squishFlags & ( int )DdsSquish.SquishFlags.kDxt1 ) != 0 ) ? 8 : 16; + + // Allocate room for compressed blocks + byte[] blockData = new byte[ blockCount * blockSize ]; + + // Invoke squish::CompressImage() with the required parameters + CallCompressImage( pixelData, inputSurface.Width, inputSurface.Height, blockData, squishFlags, progressFn ); + + // Return our block data to caller.. + return blockData; + } + + // --------------------------------------------------------------------------------------- + // DecompressImage + // --------------------------------------------------------------------------------------- + // + // Params + // inputSurface : Source byte array containing DXT block data + // width : Width of image in pixels + // height : Height of image in pixels + // flags : Flags for squish decompression control + // + // Return + // byte[] : Array of bytes containing decompressed blocks + // + // --------------------------------------------------------------------------------------- + + internal static byte[] DecompressImage( byte[] blocks, int width, int height, int flags ) + { + // Allocate room for decompressed output + byte[] pixelOutput = new byte[ width * height * 4 ]; + + // Invoke squish::DecompressImage() with the required parameters + CallDecompressImage( pixelOutput, width, height, blocks, flags, null ); + + // Return our pixel data to caller.. + return pixelOutput; + } + } +} diff --git a/src/DdsFileType/DdsFileType/License.txt b/src/DdsFileType/DdsFileType/License.txt new file mode 100644 index 0000000..91a577a --- /dev/null +++ b/src/DdsFileType/DdsFileType/License.txt @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +/* + @brief DDS File Type Plugin for Paint.NET + + @note Copyright (c) 2006 Dean Ashton http://www.dmashton.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +**/ +//------------------------------------------------------------------------------ \ No newline at end of file diff --git a/src/DdsFileType/DdsFileType/Properties/AssemblyInfo.cs b/src/DdsFileType/DdsFileType/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9a85079 --- /dev/null +++ b/src/DdsFileType/DdsFileType/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +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("DDSFileType")] +[assembly: AssemblyDescription("DDS file plug in for Paint.NET")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DDSFileType")] +[assembly: AssemblyCopyright("Copyright © 2007 Dean Ashton")] +[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("8a543b8c-769f-44a2-a59a-fdfe6e6a1f20")] + +// 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 Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.11.*")] diff --git a/src/DdsFileType/DdsFileType/ReadMe.txt b/src/DdsFileType/DdsFileType/ReadMe.txt new file mode 100644 index 0000000..f850cb8 --- /dev/null +++ b/src/DdsFileType/DdsFileType/ReadMe.txt @@ -0,0 +1,54 @@ +DdsFileType : A file type plugin for Paint.NET +---------------------------------------------- + +Overview +-------- + +This is a file type plugin for Paint.NET, which allows for the +loading & saving of a limited number of types of DDS file. +It supports DXT1, DXT3, DXT5, A8R8G8B8, X8R8G8B8, A8B8G8R8, X8B8G8R8, +A4R4G4B4, A1R5G5B5, R8G8B8, and R5G6B5 formats, and can generate +mipmaps for you, should you so wish. + +Options +------- + +When saving a DDS file, you can adjust options using the GUI. Use +the drop-down list to select the type of file you wish to output. + +You can customise the compressor type and colour error metric to use +when outputting DXT files. For compressor type you can choose from a +slow (but high quality) iterative fit, a slightly faster non-iterative +cluster fit, or a faster (but lower quality) range fit.The error +metric setting allows you to tune the compression behaviour based on +whether it's a texture that's going to be viewed normally (where the +eyes colour perception properties are taken into consideration), or +whether it's going to be used as input to some other function/process +(where the actual numeric values are more important than how it looks). + +An additional flag can also be set when using the cluster fit options, +that that weights the colour of each pixel by its alpha value. For +images rendered using alpha blending, this can significantly increase +the perceived quality. + +For all image formats, you can enable the generation of mipmaps. This +creates all levels down to 1x1. + +Installation +------------ + +Place the three DLLs in the binary package into the 'FileTypes' +directory in your Paint.NET installation location. + +License +------- + +This work is distributed under the terms and conditions of the MIT +license. This license is specified at the top of each source file and +must be preserved in its entirety. + +Feedback & Bug Reporting +------------------------ + +Feedback should be sent to me via the contact form on my homepage, +http://www.dmashton.co.uk, or via posts on the Paint.NET forums. diff --git a/src/DdsFileType/Squish/ChangeLog b/src/DdsFileType/Squish/ChangeLog new file mode 100644 index 0000000..ba03f4c --- /dev/null +++ b/src/DdsFileType/Squish/ChangeLog @@ -0,0 +1,52 @@ +1.10 +* Iterative cluster fit is now considered to be a new compression mode +* The core cluster fit is now 4x faster using contributions by Ignacio +Castano from NVIDIA +* The single colour lookup table has been halved by exploiting symmetry + +1.9 +* Added contributed SSE1 truncate implementation +* Changed use of SQUISH_USE_SSE to be 1 for SSE and 2 for SSE2 instructions +* Cluster fit is now iterative to further reduce image error + +1.8 +* Switched from using floor to trunc for much better SSE performance (again) +* Xcode build now expects libpng in /usr/local for extra/squishpng + +1.7 +* Fixed floating-point equality issue in clusterfit sort (x86 affected only) +* Implemented proper SSE(2) floor function for 50% speedup on SSE builds +* The range fit implementation now uses the correct colour metric + +1.6 +* Fixed bug in CompressImage where masked pixels were not skipped over +* DXT3 and DXT5 alpha compression now properly use the mask to ignore pixels +* Fixed major DXT1 bug that can generate unexpected transparent pixels + +1.5 +* Added CompressMasked function to handle incomplete DXT blocks more cleanly +* Added kWeightColourByAlpha flag for better quality images when alpha blending + +1.4 +* Fixed stack overflow in rangefit + +1.3 +* Worked around SSE floor implementation bug, proper fix needed! +* This release has visual studio and makefile builds that work + +1.2 +* Added provably optimal single colour compressor +* Added extra/squishgen.cpp that generates single colour lookup tables + +1.1 +* Fixed a DXT1 colour output bug +* Changed argument order for Decompress function to match Compress +* Added GetStorageRequirements function +* Added CompressImage function +* Added DecompressImage function +* Moved squishtool.cpp to extra/squishpng.cpp +* Added extra/squishtest.cpp + +1.0 +* Initial release + diff --git a/src/DdsFileType/Squish/Doxyfile b/src/DdsFileType/Squish/Doxyfile new file mode 100644 index 0000000..3ec51e4 --- /dev/null +++ b/src/DdsFileType/Squish/Doxyfile @@ -0,0 +1,223 @@ +# Doxyfile 1.4.6 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = squish +PROJECT_NUMBER = 1.1 +OUTPUT_DIRECTORY = docs +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = squish.h +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = /Applications/Graphviz.app/Contents/MacOS +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/src/DdsFileType/Squish/Makefile b/src/DdsFileType/Squish/Makefile new file mode 100644 index 0000000..75a72fe --- /dev/null +++ b/src/DdsFileType/Squish/Makefile @@ -0,0 +1,31 @@ + +include config + +SRC = alpha.cpp clusterfit.cpp colourblock.cpp colourfit.cpp colourset.cpp maths.cpp rangefit.cpp singlecolourfit.cpp squish.cpp + +OBJ = $(SRC:%.cpp=%.o) + +LIB = libsquish.a + +all : $(LIB) + +install : $(LIB) + install squish.h $(INSTALL_DIR)/include + install libsquish.a $(INSTALL_DIR)/lib + +uninstall: + $(RM) $(INSTALL_DIR)/include/squish.h + $(RM) $(INSTALL_DIR)/lib/libsquish.a + +$(LIB) : $(OBJ) + $(AR) cr $@ $? + ranlib $@ + +%.o : %.cpp + $(CXX) $(CPPFLAGS) -I. $(CXXFLAGS) -o$@ -c $< + +clean : + $(RM) $(OBJ) $(LIB) + + + diff --git a/src/DdsFileType/Squish/README b/src/DdsFileType/Squish/README new file mode 100644 index 0000000..d26b72e --- /dev/null +++ b/src/DdsFileType/Squish/README @@ -0,0 +1,35 @@ +LICENSE +------- + +The squish library is distributed under the terms and conditions of the MIT +license. This license is specified at the top of each source file and must be +preserved in its entirety. + +BUILDING AND INSTALLING THE LIBRARY +----------------------------------- + +If you are using Visual Studio 2003 or above under Windows then load the Visual +Studio 2003 project in the vs7 folder. By default, the library is built using +SSE2 optimisations. To change this either change or remove the SQUISH_USE_SSE=2 +from the preprocessor symbols. + +If you are using a Mac then load the Xcode 2.2 project in the distribution. By +default, the library is built using Altivec optimisations. To change this +either change or remove SQUISH_USE_ALTIVEC=1 from the preprocessor symbols. I +guess I'll have to think about changing this for the new Intel Macs that are +rolling out... + +If you are using unix then first edit the config file in the base directory of +the distribution, enabling Altivec or SSE with the USE_ALTIVEC or USE_SSE +variables, and editing the optimisation flags passed to the C++ compiler if +necessary. Then make can be used to build the library, and make install (from +the superuser account) can be used to install (into /usr/local by default). + +REPORTING BUGS OR FEATURE REQUESTS +---------------------------------- + +Feedback can be sent to Simon Brown (the developer) at si@sjbrown.co.uk + +New releases are announced on the squish library homepage at +http://sjbrown.co.uk/?code=squish + diff --git a/src/DdsFileType/Squish/Squish.aps b/src/DdsFileType/Squish/Squish.aps new file mode 100644 index 0000000..cce389f Binary files /dev/null and b/src/DdsFileType/Squish/Squish.aps differ diff --git a/src/DdsFileType/Squish/Squish.rc b/src/DdsFileType/Squish/Squish.rc new file mode 100644 index 0000000..3a58e54 --- /dev/null +++ b/src/DdsFileType/Squish/Squish.rc @@ -0,0 +1,101 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,10,2,0 + PRODUCTVERSION 1,10,2,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Customized for Paint.NET." + VALUE "FileDescription", "Squish" + VALUE "FileVersion", "1, 10, 2, 0" + VALUE "InternalName", "Squish" + VALUE "LegalCopyright", "Copyright (C) 2006 Simon Brown" + VALUE "ProductName", "Squish" + VALUE "ProductVersion", "1, 10, 2, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/DdsFileType/Squish/Squish_x64/Squish_x64.vcproj b/src/DdsFileType/Squish/Squish_x64/Squish_x64.vcproj new file mode 100644 index 0000000..538926d --- /dev/null +++ b/src/DdsFileType/Squish/Squish_x64/Squish_x64.vcproj @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DdsFileType/Squish/Squish_x86/Squish_x86.vcproj b/src/DdsFileType/Squish/Squish_x86/Squish_x86.vcproj new file mode 100644 index 0000000..8ee3b95 --- /dev/null +++ b/src/DdsFileType/Squish/Squish_x86/Squish_x86.vcproj @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DdsFileType/Squish/Squish_x86_SSE2/Squish_x86_SSE2.vcproj b/src/DdsFileType/Squish/Squish_x86_SSE2/Squish_x86_SSE2.vcproj new file mode 100644 index 0000000..2110637 --- /dev/null +++ b/src/DdsFileType/Squish/Squish_x86_SSE2/Squish_x86_SSE2.vcproj @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DdsFileType/Squish/alpha.cpp b/src/DdsFileType/Squish/alpha.cpp new file mode 100644 index 0000000..6eb4ffa --- /dev/null +++ b/src/DdsFileType/Squish/alpha.cpp @@ -0,0 +1,348 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "alpha.h" +#include + +namespace squish { + +static int FloatToInt( float a, int limit ) +{ + // use ANSI round-to-zero behaviour to get round-to-nearest + int i = ( int )( a + 0.5f ); + + // clamp to the limit + if( i < 0 ) + i = 0; + else if( i > limit ) + i = limit; + + // done + return i; +} + +void CompressAlphaDxt3( u8 const* rgba, int mask, void* block ) +{ + u8* bytes = reinterpret_cast< u8* >( block ); + + // quantise and pack the alpha values pairwise + for( int i = 0; i < 8; ++i ) + { + // quantise down to 4 bits + float alpha1 = ( float )rgba[8*i + 3] * ( 15.0f/255.0f ); + float alpha2 = ( float )rgba[8*i + 7] * ( 15.0f/255.0f ); + int quant1 = FloatToInt( alpha1, 15 ); + int quant2 = FloatToInt( alpha2, 15 ); + + // set alpha to zero where masked + int bit1 = 1 << ( 2*i ); + int bit2 = 1 << ( 2*i + 1 ); + if( ( mask & bit1 ) == 0 ) + quant1 = 0; + if( ( mask & bit2 ) == 0 ) + quant2 = 0; + + // pack into the byte + bytes[i] = ( u8 )( quant1 | ( quant2 << 4 ) ); + } +} + +void DecompressAlphaDxt3( u8* rgba, void const* block ) +{ + u8 const* bytes = reinterpret_cast< u8 const* >( block ); + + // unpack the alpha values pairwise + for( int i = 0; i < 8; ++i ) + { + // quantise down to 4 bits + u8 quant = bytes[i]; + + // unpack the values + u8 lo = quant & 0x0f; + u8 hi = quant & 0xf0; + + // convert back up to bytes + rgba[8*i + 3] = lo | ( lo << 4 ); + rgba[8*i + 7] = hi | ( hi >> 4 ); + } +} + +static void FixRange( int& min, int& max, int steps ) +{ + if( max - min < steps ) + max = std::min( min + steps, 255 ); + if( max - min < steps ) + min = std::max( 0, max - steps ); +} + +static int FitCodes( u8 const* rgba, int mask, u8 const* codes, u8* indices ) +{ + // fit each alpha value to the codebook + int err = 0; + for( int i = 0; i < 16; ++i ) + { + // check this pixel is valid + int bit = 1 << i; + if( ( mask & bit ) == 0 ) + { + // use the first code + indices[i] = 0; + continue; + } + + // find the least error and corresponding index + int value = rgba[4*i + 3]; + int least = INT_MAX; + int index = 0; + for( int j = 0; j < 8; ++j ) + { + // get the squared error from this code + int dist = ( int )value - ( int )codes[j]; + dist *= dist; + + // compare with the best so far + if( dist < least ) + { + least = dist; + index = j; + } + } + + // save this index and accumulate the error + indices[i] = ( u8 )index; + err += least; + } + + // return the total error + return err; +} + +static void WriteAlphaBlock( int alpha0, int alpha1, u8 const* indices, void* block ) +{ + u8* bytes = reinterpret_cast< u8* >( block ); + + // write the first two bytes + bytes[0] = ( u8 )alpha0; + bytes[1] = ( u8 )alpha1; + + // pack the indices with 3 bits each + u8* dest = bytes + 2; + u8 const* src = indices; + for( int i = 0; i < 2; ++i ) + { + // pack 8 3-bit values + int value = 0; + for( int j = 0; j < 8; ++j ) + { + int index = *src++; + value |= ( index << 3*j ); + } + + // store in 3 bytes + for( int j = 0; j < 3; ++j ) + { + int byte = ( value >> 8*j ) & 0xff; + *dest++ = ( u8 )byte; + } + } +} + +static void WriteAlphaBlock5( int alpha0, int alpha1, u8 const* indices, void* block ) +{ + // check the relative values of the endpoints + if( alpha0 > alpha1 ) + { + // swap the indices + u8 swapped[16]; + for( int i = 0; i < 16; ++i ) + { + u8 index = indices[i]; + if( index == 0 ) + swapped[i] = 1; + else if( index == 1 ) + swapped[i] = 0; + else if( index <= 5 ) + swapped[i] = 7 - index; + else + swapped[i] = index; + } + + // write the block + WriteAlphaBlock( alpha1, alpha0, swapped, block ); + } + else + { + // write the block + WriteAlphaBlock( alpha0, alpha1, indices, block ); + } +} + +static void WriteAlphaBlock7( int alpha0, int alpha1, u8 const* indices, void* block ) +{ + // check the relative values of the endpoints + if( alpha0 < alpha1 ) + { + // swap the indices + u8 swapped[16]; + for( int i = 0; i < 16; ++i ) + { + u8 index = indices[i]; + if( index == 0 ) + swapped[i] = 1; + else if( index == 1 ) + swapped[i] = 0; + else + swapped[i] = 9 - index; + } + + // write the block + WriteAlphaBlock( alpha1, alpha0, swapped, block ); + } + else + { + // write the block + WriteAlphaBlock( alpha0, alpha1, indices, block ); + } +} + +void CompressAlphaDxt5( u8 const* rgba, int mask, void* block ) +{ + // get the range for 5-alpha and 7-alpha interpolation + int min5 = 255; + int max5 = 0; + int min7 = 255; + int max7 = 0; + for( int i = 0; i < 16; ++i ) + { + // check this pixel is valid + int bit = 1 << i; + if( ( mask & bit ) == 0 ) + continue; + + // incorporate into the min/max + int value = rgba[4*i + 3]; + if( value < min7 ) + min7 = value; + if( value > max7 ) + max7 = value; + if( value != 0 && value < min5 ) + min5 = value; + if( value != 255 && value > max5 ) + max5 = value; + } + + // handle the case that no valid range was found + if( min5 > max5 ) + min5 = max5; + if( min7 > max7 ) + min7 = max7; + + // fix the range to be the minimum in each case + FixRange( min5, max5, 5 ); + FixRange( min7, max7, 7 ); + + // set up the 5-alpha code book + u8 codes5[8]; + codes5[0] = ( u8 )min5; + codes5[1] = ( u8 )max5; + for( int i = 1; i < 5; ++i ) + codes5[1 + i] = ( u8 )( ( ( 5 - i )*min5 + i*max5 )/5 ); + codes5[6] = 0; + codes5[7] = 255; + + // set up the 7-alpha code book + u8 codes7[8]; + codes7[0] = ( u8 )min7; + codes7[1] = ( u8 )max7; + for( int i = 1; i < 7; ++i ) + codes7[1 + i] = ( u8 )( ( ( 7 - i )*min7 + i*max7 )/7 ); + + // fit the data to both code books + u8 indices5[16]; + u8 indices7[16]; + int err5 = FitCodes( rgba, mask, codes5, indices5 ); + int err7 = FitCodes( rgba, mask, codes7, indices7 ); + + // save the block with least error + if( err5 <= err7 ) + WriteAlphaBlock5( min5, max5, indices5, block ); + else + WriteAlphaBlock7( min7, max7, indices7, block ); +} + +void DecompressAlphaDxt5( u8* rgba, void const* block ) +{ + // get the two alpha values + u8 const* bytes = reinterpret_cast< u8 const* >( block ); + int alpha0 = bytes[0]; + int alpha1 = bytes[1]; + + // compare the values to build the codebook + u8 codes[8]; + codes[0] = ( u8 )alpha0; + codes[1] = ( u8 )alpha1; + if( alpha0 <= alpha1 ) + { + // use 5-alpha codebook + for( int i = 1; i < 5; ++i ) + codes[1 + i] = ( u8 )( ( ( 5 - i )*alpha0 + i*alpha1 )/5 ); + codes[6] = 0; + codes[7] = 255; + } + else + { + // use 7-alpha codebook + for( int i = 1; i < 7; ++i ) + codes[1 + i] = ( u8 )( ( ( 7 - i )*alpha0 + i*alpha1 )/7 ); + } + + // decode the indices + u8 indices[16]; + u8 const* src = bytes + 2; + u8* dest = indices; + for( int i = 0; i < 2; ++i ) + { + // grab 3 bytes + int value = 0; + for( int j = 0; j < 3; ++j ) + { + int byte = *src++; + value |= ( byte << 8*j ); + } + + // unpack 8 3-bit values from it + for( int j = 0; j < 8; ++j ) + { + int index = ( value >> 3*j ) & 0x7; + *dest++ = ( u8 )index; + } + } + + // write out the indexed codebook values + for( int i = 0; i < 16; ++i ) + rgba[4*i + 3] = codes[indices[i]]; +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/alpha.h b/src/DdsFileType/Squish/alpha.h new file mode 100644 index 0000000..5736052 --- /dev/null +++ b/src/DdsFileType/Squish/alpha.h @@ -0,0 +1,41 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_ALPHA_H +#define SQUISH_ALPHA_H + +#include + +namespace squish { + +void CompressAlphaDxt3( u8 const* rgba, int mask, void* block ); +void CompressAlphaDxt5( u8 const* rgba, int mask, void* block ); + +void DecompressAlphaDxt3( u8* rgba, void const* block ); +void DecompressAlphaDxt5( u8* rgba, void const* block ); + +} // namespace squish + +#endif // ndef SQUISH_ALPHA_H diff --git a/src/DdsFileType/Squish/clusterfit.cpp b/src/DdsFileType/Squish/clusterfit.cpp new file mode 100644 index 0000000..afea848 --- /dev/null +++ b/src/DdsFileType/Squish/clusterfit.cpp @@ -0,0 +1,393 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + Copyright (c) 2007 Ignacio Castano icastano@nvidia.com + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "clusterfit.h" +#include "colourset.h" +#include "colourblock.h" +#include + +namespace squish { + +ClusterFit::ClusterFit( ColourSet const* colours, int flags ) + : ColourFit( colours, flags ) +{ + // set the iteration count + m_iterationCount = ( m_flags & kColourIterativeClusterFit ) ? kMaxIterations : 1; + + // initialise the best error + m_besterror = VEC4_CONST( FLT_MAX ); + + // initialise the metric + bool perceptual = ( ( m_flags & kColourMetricPerceptual ) != 0 ); + if( perceptual ) + m_metric = Vec4( 0.2126f, 0.7152f, 0.0722f, 0.0f ); + else + m_metric = VEC4_CONST( 1.0f ); + + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // get the covariance matrix + Sym3x3 covariance = ComputeWeightedCovariance( count, values, m_colours->GetWeights() ); + + // compute the principle component + m_principle = ComputePrincipleComponent( covariance ); +} + +bool ClusterFit::ConstructOrdering( Vec3 const& axis, int iteration ) +{ + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // build the list of dot products + float dps[16]; + u8* order = ( u8* )m_order + 16*iteration; + for( int i = 0; i < count; ++i ) + { + dps[i] = Dot( values[i], axis ); + order[i] = ( u8 )i; + } + + // stable sort using them + for( int i = 0; i < count; ++i ) + { + for( int j = i; j > 0 && dps[j] < dps[j - 1]; --j ) + { + std::swap( dps[j], dps[j - 1] ); + std::swap( order[j], order[j - 1] ); + } + } + + // check this ordering is unique + for( int it = 0; it < iteration; ++it ) + { + u8 const* prev = ( u8* )m_order + 16*it; + bool same = true; + for( int i = 0; i < count; ++i ) + { + if( order[i] != prev[i] ) + { + same = false; + break; + } + } + if( same ) + return false; + } + + // copy the ordering and weight all the points + Vec3 const* unweighted = m_colours->GetPoints(); + float const* weights = m_colours->GetWeights(); + m_xsum_wsum = VEC4_CONST( 0.0f ); + for( int i = 0; i < count; ++i ) + { + int j = order[i]; + Vec4 p( unweighted[j].X(), unweighted[j].Y(), unweighted[j].Z(), 1.0f ); + Vec4 w( weights[j] ); + Vec4 x = p*w; + m_points_weights[i] = x; + m_xsum_wsum += x; + } + return true; +} + +void ClusterFit::Compress3( void* block ) +{ + // declare variables + int const count = m_colours->GetCount(); + Vec4 const two = VEC4_CONST( 2.0 ); + Vec4 const one = VEC4_CONST( 1.0f ); + Vec4 const half_half2( 0.5f, 0.5f, 0.5f, 0.25f ); + Vec4 const zero = VEC4_CONST( 0.0f ); + Vec4 const half = VEC4_CONST( 0.5f ); + Vec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f ); + Vec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f ); + + // prepare an ordering using the principle axis + ConstructOrdering( m_principle, 0 ); + + // check all possible clusters and iterate on the total order + Vec4 beststart = VEC4_CONST( 0.0f ); + Vec4 bestend = VEC4_CONST( 0.0f ); + Vec4 besterror = m_besterror; + u8 bestindices[16]; + int bestiteration = 0; + int besti = 0, bestj = 0; + + // loop over iterations (we avoid the case that all points in first or last cluster) + for( int iterationIndex = 0;; ) + { + // first cluster [0,i) is at the start + Vec4 part0 = VEC4_CONST( 0.0f ); + for( int i = 0; i < count; ++i ) + { + // second cluster [i,j) is half along + Vec4 part1 = ( i == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f ); + int jmin = ( i == 0 ) ? 1 : i; + for( int j = jmin;; ) + { + // last cluster [j,count) is at the end + Vec4 part2 = m_xsum_wsum - part1 - part0; + + // compute least squares terms directly + Vec4 alphax_sum = MultiplyAdd( part1, half_half2, part0 ); + Vec4 alpha2_sum = alphax_sum.SplatW(); + + Vec4 betax_sum = MultiplyAdd( part1, half_half2, part2 ); + Vec4 beta2_sum = betax_sum.SplatW(); + + Vec4 alphabeta_sum = ( part1*half_half2 ).SplatW(); + + // compute the least-squares optimal points + Vec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) ); + Vec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor; + Vec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor; + + // clamp to the grid + a = Min( one, Max( zero, a ) ); + b = Min( one, Max( zero, b ) ); + a = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp; + b = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp; + + // compute the error (we skip the constant xxsum) + Vec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum ); + Vec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum ); + Vec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 ); + Vec4 e4 = MultiplyAdd( two, e3, e1 ); + + // apply the metric to the error term + Vec4 e5 = e4*m_metric; + Vec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ(); + + // keep the solution if it wins + if( CompareAnyLessThan( error, besterror ) ) + { + beststart = a; + bestend = b; + besti = i; + bestj = j; + besterror = error; + bestiteration = iterationIndex; + } + + // advance + if( j == count ) + break; + part1 += m_points_weights[j]; + ++j; + } + + // advance + part0 += m_points_weights[i]; + } + + // stop if we didn't improve in this iteration + if( bestiteration != iterationIndex ) + break; + + // advance if possible + ++iterationIndex; + if( iterationIndex == m_iterationCount ) + break; + + // stop if a new iteration is an ordering that has already been tried + Vec3 axis = ( bestend - beststart ).GetVec3(); + if( !ConstructOrdering( axis, iterationIndex ) ) + break; + } + + // save the block if necessary + if( CompareAnyLessThan( besterror, m_besterror ) ) + { + // remap the indices + u8 const* order = ( u8* )m_order + 16*bestiteration; + + u8 unordered[16]; + for( int m = 0; m < besti; ++m ) + unordered[order[m]] = 0; + for( int m = besti; m < bestj; ++m ) + unordered[order[m]] = 2; + for( int m = bestj; m < count; ++m ) + unordered[order[m]] = 1; + + m_colours->RemapIndices( unordered, bestindices ); + + // save the block + WriteColourBlock3( beststart.GetVec3(), bestend.GetVec3(), bestindices, block ); + + // save the error + m_besterror = besterror; + } +} + +void ClusterFit::Compress4( void* block ) +{ + // declare variables + int const count = m_colours->GetCount(); + Vec4 const two = VEC4_CONST( 2.0f ); + Vec4 const one = VEC4_CONST( 1.0f ); + Vec4 const onethird_onethird2( 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/9.0f ); + Vec4 const twothirds_twothirds2( 2.0f/3.0f, 2.0f/3.0f, 2.0f/3.0f, 4.0f/9.0f ); + Vec4 const twonineths = VEC4_CONST( 2.0f/9.0f ); + Vec4 const zero = VEC4_CONST( 0.0f ); + Vec4 const half = VEC4_CONST( 0.5f ); + Vec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f ); + Vec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f ); + + // prepare an ordering using the principle axis + ConstructOrdering( m_principle, 0 ); + + // check all possible clusters and iterate on the total order + Vec4 beststart = VEC4_CONST( 0.0f ); + Vec4 bestend = VEC4_CONST( 0.0f ); + Vec4 besterror = m_besterror; + u8 bestindices[16]; + int bestiteration = 0; + int besti = 0, bestj = 0, bestk = 0; + + // loop over iterations (we avoid the case that all points in first or last cluster) + for( int iterationIndex = 0;; ) + { + // first cluster [0,i) is at the start + Vec4 part0 = VEC4_CONST( 0.0f ); + for( int i = 0; i < count; ++i ) + { + // second cluster [i,j) is one third along + Vec4 part1 = VEC4_CONST( 0.0f ); + for( int j = i;; ) + { + // third cluster [j,k) is two thirds along + Vec4 part2 = ( j == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f ); + int kmin = ( j == 0 ) ? 1 : j; + for( int k = kmin;; ) + { + // last cluster [k,count) is at the end + Vec4 part3 = m_xsum_wsum - part2 - part1 - part0; + + // compute least squares terms directly + Vec4 const alphax_sum = MultiplyAdd( part2, onethird_onethird2, MultiplyAdd( part1, twothirds_twothirds2, part0 ) ); + Vec4 const alpha2_sum = alphax_sum.SplatW(); + + Vec4 const betax_sum = MultiplyAdd( part1, onethird_onethird2, MultiplyAdd( part2, twothirds_twothirds2, part3 ) ); + Vec4 const beta2_sum = betax_sum.SplatW(); + + Vec4 const alphabeta_sum = twonineths*( part1 + part2 ).SplatW(); + + // compute the least-squares optimal points + Vec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) ); + Vec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor; + Vec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor; + + // clamp to the grid + a = Min( one, Max( zero, a ) ); + b = Min( one, Max( zero, b ) ); + a = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp; + b = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp; + + // compute the error (we skip the constant xxsum) + Vec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum ); + Vec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum ); + Vec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 ); + Vec4 e4 = MultiplyAdd( two, e3, e1 ); + + // apply the metric to the error term + Vec4 e5 = e4*m_metric; + Vec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ(); + + // keep the solution if it wins + if( CompareAnyLessThan( error, besterror ) ) + { + beststart = a; + bestend = b; + besterror = error; + besti = i; + bestj = j; + bestk = k; + bestiteration = iterationIndex; + } + + // advance + if( k == count ) + break; + part2 += m_points_weights[k]; + ++k; + } + + // advance + if( j == count ) + break; + part1 += m_points_weights[j]; + ++j; + } + + // advance + part0 += m_points_weights[i]; + } + + // stop if we didn't improve in this iteration + if( bestiteration != iterationIndex ) + break; + + // advance if possible + ++iterationIndex; + if( iterationIndex == m_iterationCount ) + break; + + // stop if a new iteration is an ordering that has already been tried + Vec3 axis = ( bestend - beststart ).GetVec3(); + if( !ConstructOrdering( axis, iterationIndex ) ) + break; + } + + // save the block if necessary + if( CompareAnyLessThan( besterror, m_besterror ) ) + { + // remap the indices + u8 const* order = ( u8* )m_order + 16*bestiteration; + + u8 unordered[16]; + for( int m = 0; m < besti; ++m ) + unordered[order[m]] = 0; + for( int m = besti; m < bestj; ++m ) + unordered[order[m]] = 2; + for( int m = bestj; m < bestk; ++m ) + unordered[order[m]] = 3; + for( int m = bestk; m < count; ++m ) + unordered[order[m]] = 1; + + m_colours->RemapIndices( unordered, bestindices ); + + // save the block + WriteColourBlock4( beststart.GetVec3(), bestend.GetVec3(), bestindices, block ); + + // save the error + m_besterror = besterror; + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/clusterfit.h b/src/DdsFileType/Squish/clusterfit.h new file mode 100644 index 0000000..17db5d3 --- /dev/null +++ b/src/DdsFileType/Squish/clusterfit.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + Copyright (c) 2007 Ignacio Castano icastano@nvidia.com + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_CLUSTERFIT_H +#define SQUISH_CLUSTERFIT_H + +#include +#include "maths.h" +#include "simd.h" +#include "colourfit.h" + +namespace squish { + +class ClusterFit : public ColourFit +{ +public: + ClusterFit( ColourSet const* colours, int flags ); + +private: + bool ConstructOrdering( Vec3 const& axis, int iteration ); + + virtual void Compress3( void* block ); + virtual void Compress4( void* block ); + + enum { kMaxIterations = 8 }; + + int m_iterationCount; + Vec3 m_principle; + u8 m_order[16*kMaxIterations]; + Vec4 m_points_weights[16]; + Vec4 m_xsum_wsum; + Vec4 m_metric; + Vec4 m_besterror; +}; + +} // namespace squish + +#endif // ndef SQUISH_CLUSTERFIT_H diff --git a/src/DdsFileType/Squish/colourblock.cpp b/src/DdsFileType/Squish/colourblock.cpp new file mode 100644 index 0000000..e6a5788 --- /dev/null +++ b/src/DdsFileType/Squish/colourblock.cpp @@ -0,0 +1,214 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "colourblock.h" + +namespace squish { + +static int FloatToInt( float a, int limit ) +{ + // use ANSI round-to-zero behaviour to get round-to-nearest + int i = ( int )( a + 0.5f ); + + // clamp to the limit + if( i < 0 ) + i = 0; + else if( i > limit ) + i = limit; + + // done + return i; +} + +static int FloatTo565( Vec3::Arg colour ) +{ + // get the components in the correct range + int r = FloatToInt( 31.0f*colour.X(), 31 ); + int g = FloatToInt( 63.0f*colour.Y(), 63 ); + int b = FloatToInt( 31.0f*colour.Z(), 31 ); + + // pack into a single value + return ( r << 11 ) | ( g << 5 ) | b; +} + +static void WriteColourBlock( int a, int b, u8* indices, void* block ) +{ + // get the block as bytes + u8* bytes = ( u8* )block; + + // write the endpoints + bytes[0] = ( u8 )( a & 0xff ); + bytes[1] = ( u8 )( a >> 8 ); + bytes[2] = ( u8 )( b & 0xff ); + bytes[3] = ( u8 )( b >> 8 ); + + // write the indices + for( int i = 0; i < 4; ++i ) + { + u8 const* ind = indices + 4*i; + bytes[4 + i] = ind[0] | ( ind[1] << 2 ) | ( ind[2] << 4 ) | ( ind[3] << 6 ); + } +} + +void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ) +{ + // get the packed values + int a = FloatTo565( start ); + int b = FloatTo565( end ); + + // remap the indices + u8 remapped[16]; + if( a <= b ) + { + // use the indices directly + for( int i = 0; i < 16; ++i ) + remapped[i] = indices[i]; + } + else + { + // swap a and b + std::swap( a, b ); + for( int i = 0; i < 16; ++i ) + { + if( indices[i] == 0 ) + remapped[i] = 1; + else if( indices[i] == 1 ) + remapped[i] = 0; + else + remapped[i] = indices[i]; + } + } + + // write the block + WriteColourBlock( a, b, remapped, block ); +} + +void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ) +{ + // get the packed values + int a = FloatTo565( start ); + int b = FloatTo565( end ); + + // remap the indices + u8 remapped[16]; + if( a < b ) + { + // swap a and b + std::swap( a, b ); + for( int i = 0; i < 16; ++i ) + remapped[i] = ( indices[i] ^ 0x1 ) & 0x3; + } + else if( a == b ) + { + // use index 0 + for( int i = 0; i < 16; ++i ) + remapped[i] = 0; + } + else + { + // use the indices directly + for( int i = 0; i < 16; ++i ) + remapped[i] = indices[i]; + } + + // write the block + WriteColourBlock( a, b, remapped, block ); +} + +static int Unpack565( u8 const* packed, u8* colour ) +{ + // build the packed value + int value = ( int )packed[0] | ( ( int )packed[1] << 8 ); + + // get the components in the stored range + u8 red = ( u8 )( ( value >> 11 ) & 0x1f ); + u8 green = ( u8 )( ( value >> 5 ) & 0x3f ); + u8 blue = ( u8 )( value & 0x1f ); + + // scale up to 8 bits + colour[0] = ( red << 3 ) | ( red >> 2 ); + colour[1] = ( green << 2 ) | ( green >> 4 ); + colour[2] = ( blue << 3 ) | ( blue >> 2 ); + colour[3] = 255; + + // return the value + return value; +} + +void DecompressColour( u8* rgba, void const* block, bool isDxt1 ) +{ + // get the block bytes + u8 const* bytes = reinterpret_cast< u8 const* >( block ); + + // unpack the endpoints + u8 codes[16]; + int a = Unpack565( bytes, codes ); + int b = Unpack565( bytes + 2, codes + 4 ); + + // generate the midpoints + for( int i = 0; i < 3; ++i ) + { + int c = codes[i]; + int d = codes[4 + i]; + + if( isDxt1 && a <= b ) + { + codes[8 + i] = ( u8 )( ( c + d )/2 ); + codes[12 + i] = 0; + } + else + { + codes[8 + i] = ( u8 )( ( 2*c + d )/3 ); + codes[12 + i] = ( u8 )( ( c + 2*d )/3 ); + } + } + + // fill in alpha for the intermediate values + codes[8 + 3] = 255; + codes[12 + 3] = ( isDxt1 && a <= b ) ? 0 : 255; + + // unpack the indices + u8 indices[16]; + for( int i = 0; i < 4; ++i ) + { + u8* ind = indices + 4*i; + u8 packed = bytes[4 + i]; + + ind[0] = packed & 0x3; + ind[1] = ( packed >> 2 ) & 0x3; + ind[2] = ( packed >> 4 ) & 0x3; + ind[3] = ( packed >> 6 ) & 0x3; + } + + // store out the colours + for( int i = 0; i < 16; ++i ) + { + u8 offset = 4*indices[i]; + for( int j = 0; j < 4; ++j ) + rgba[4*i + j] = codes[offset + j]; + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/colourblock.h b/src/DdsFileType/Squish/colourblock.h new file mode 100644 index 0000000..df0a472 --- /dev/null +++ b/src/DdsFileType/Squish/colourblock.h @@ -0,0 +1,41 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_COLOURBLOCK_H +#define SQUISH_COLOURBLOCK_H + +#include +#include "maths.h" + +namespace squish { + +void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ); +void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ); + +void DecompressColour( u8* rgba, void const* block, bool isDxt1 ); + +} // namespace squish + +#endif // ndef SQUISH_COLOURBLOCK_H diff --git a/src/DdsFileType/Squish/colourfit.cpp b/src/DdsFileType/Squish/colourfit.cpp new file mode 100644 index 0000000..dba2b87 --- /dev/null +++ b/src/DdsFileType/Squish/colourfit.cpp @@ -0,0 +1,50 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "colourfit.h" +#include "colourset.h" + +namespace squish { + +ColourFit::ColourFit( ColourSet const* colours, int flags ) + : m_colours( colours ), + m_flags( flags ) +{ +} + +void ColourFit::Compress( void* block ) +{ + bool isDxt1 = ( ( m_flags & kDxt1 ) != 0 ); + if( isDxt1 ) + { + Compress3( block ); + if( !m_colours->IsTransparent() ) + Compress4( block ); + } + else + Compress4( block ); +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/colourfit.h b/src/DdsFileType/Squish/colourfit.h new file mode 100644 index 0000000..a2d0559 --- /dev/null +++ b/src/DdsFileType/Squish/colourfit.h @@ -0,0 +1,53 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_COLOURFIT_H +#define SQUISH_COLOURFIT_H + +#include +#include "maths.h" + +namespace squish { + +class ColourSet; + +class ColourFit +{ +public: + ColourFit( ColourSet const* colours, int flags ); + + void Compress( void* block ); + +protected: + virtual void Compress3( void* block ) = 0; + virtual void Compress4( void* block ) = 0; + + ColourSet const* m_colours; + int m_flags; +}; + +} // namespace squish + +#endif // ndef SQUISH_COLOURFIT_H diff --git a/src/DdsFileType/Squish/colourset.cpp b/src/DdsFileType/Squish/colourset.cpp new file mode 100644 index 0000000..97d29d9 --- /dev/null +++ b/src/DdsFileType/Squish/colourset.cpp @@ -0,0 +1,121 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "colourset.h" + +namespace squish { + +ColourSet::ColourSet( u8 const* rgba, int mask, int flags ) + : m_count( 0 ), + m_transparent( false ) +{ + // check the compression mode for dxt1 + bool isDxt1 = ( ( flags & kDxt1 ) != 0 ); + bool weightByAlpha = ( ( flags & kWeightColourByAlpha ) != 0 ); + + // create the minimal set + for( int i = 0; i < 16; ++i ) + { + // check this pixel is enabled + int bit = 1 << i; + if( ( mask & bit ) == 0 ) + { + m_remap[i] = -1; + continue; + } + + // check for transparent pixels when using dxt1 + if( isDxt1 && rgba[4*i + 3] < 128 ) + { + m_remap[i] = -1; + m_transparent = true; + continue; + } + + // loop over previous points for a match + for( int j = 0;; ++j ) + { + // allocate a new point + if( j == i ) + { + // normalise coordinates to [0,1] + float x = ( float )rgba[4*i] / 255.0f; + float y = ( float )rgba[4*i + 1] / 255.0f; + float z = ( float )rgba[4*i + 2] / 255.0f; + + // ensure there is always non-zero weight even for zero alpha + float w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f; + + // add the point + m_points[m_count] = Vec3( x, y, z ); + m_weights[m_count] = ( weightByAlpha ? w : 1.0f ); + m_remap[i] = m_count; + + // advance + ++m_count; + break; + } + + // check for a match + int oldbit = 1 << j; + bool match = ( ( mask & oldbit ) != 0 ) + && ( rgba[4*i] == rgba[4*j] ) + && ( rgba[4*i + 1] == rgba[4*j + 1] ) + && ( rgba[4*i + 2] == rgba[4*j + 2] ) + && ( rgba[4*j + 3] >= 128 || !isDxt1 ); + if( match ) + { + // get the index of the match + int index = m_remap[j]; + + // ensure there is always non-zero weight even for zero alpha + float w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f; + + // map to this point and increase the weight + m_weights[index] += ( weightByAlpha ? w : 1.0f ); + m_remap[i] = index; + break; + } + } + } + + // square root the weights + for( int i = 0; i < m_count; ++i ) + m_weights[i] = std::sqrt( m_weights[i] ); +} + +void ColourSet::RemapIndices( u8 const* source, u8* target ) const +{ + for( int i = 0; i < 16; ++i ) + { + int j = m_remap[i]; + if( j == -1 ) + target[i] = 3; + else + target[i] = source[j]; + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/colourset.h b/src/DdsFileType/Squish/colourset.h new file mode 100644 index 0000000..dcf56ae --- /dev/null +++ b/src/DdsFileType/Squish/colourset.h @@ -0,0 +1,58 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_COLOURSET_H +#define SQUISH_COLOURSET_H + +#include +#include "maths.h" + +namespace squish { + +/*! @brief Represents a set of block colours +*/ +class ColourSet +{ +public: + ColourSet( u8 const* rgba, int mask, int flags ); + + int GetCount() const { return m_count; } + Vec3 const* GetPoints() const { return m_points; } + float const* GetWeights() const { return m_weights; } + bool IsTransparent() const { return m_transparent; } + + void RemapIndices( u8 const* source, u8* target ) const; + +private: + int m_count; + Vec3 m_points[16]; + float m_weights[16]; + int m_remap[16]; + bool m_transparent; +}; + +} // namespace sqish + +#endif // ndef SQUISH_COLOURSET_H diff --git a/src/DdsFileType/Squish/config b/src/DdsFileType/Squish/config new file mode 100644 index 0000000..3d9a477 --- /dev/null +++ b/src/DdsFileType/Squish/config @@ -0,0 +1,22 @@ +# config file used for the Makefile only + +# define to 1 to use Altivec instructions +USE_ALTIVEC ?= 0 + +# define to 1 to use SSE2 instructions +USE_SSE ?= 0 + +# default flags +CXXFLAGS ?= -O2 +ifeq ($(USE_ALTIVEC),1) +CPPFLAGS += -DSQUISH_USE_ALTIVEC=1 +CXXFLAGS += -maltivec +endif +ifeq ($(USE_SSE),1) +CPPFLAGS += -DSQUISH_USE_SSE=2 +CXXFLAGS += -msse +endif + +# where should we install to +INSTALL_DIR ?= /usr/local + diff --git a/src/DdsFileType/Squish/config.h b/src/DdsFileType/Squish/config.h new file mode 100644 index 0000000..44daaa0 --- /dev/null +++ b/src/DdsFileType/Squish/config.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_CONFIG_H +#define SQUISH_CONFIG_H + +// Set to 1 when using OpenMP +#ifndef SQUISH_USE_OPENMP +#define SQUISH_USE_OPENMP 0 +#endif + +// Set to 1 when building squish to use Altivec instructions. +#ifndef SQUISH_USE_ALTIVEC +#define SQUISH_USE_ALTIVEC 0 +#endif + +// Set to 1 or 2 when building squish to use SSE or SSE2 instructions. +#ifndef SQUISH_USE_SSE +#define SQUISH_USE_SSE 0 +#endif + +// Internally et SQUISH_USE_SIMD when either Altivec or SSE is available. +#if SQUISH_USE_ALTIVEC && SQUISH_USE_SSE +#error "Cannot enable both Altivec and SSE!" +#endif +#if SQUISH_USE_ALTIVEC || SQUISH_USE_SSE +#define SQUISH_USE_SIMD 1 +#else +#define SQUISH_USE_SIMD 0 +#endif + +#endif // ndef SQUISH_CONFIG_H diff --git a/src/DdsFileType/Squish/maths.cpp b/src/DdsFileType/Squish/maths.cpp new file mode 100644 index 0000000..59818a4 --- /dev/null +++ b/src/DdsFileType/Squish/maths.cpp @@ -0,0 +1,227 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +/*! @file + + The symmetric eigensystem solver algorithm is from + http://www.geometrictools.com/Documentation/EigenSymmetric3x3.pdf +*/ + +#include "maths.h" +#include + +namespace squish { + +Sym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights ) +{ + // compute the centroid + float total = 0.0f; + Vec3 centroid( 0.0f ); + for( int i = 0; i < n; ++i ) + { + total += weights[i]; + centroid += weights[i]*points[i]; + } + centroid /= total; + + // accumulate the covariance matrix + Sym3x3 covariance( 0.0f ); + for( int i = 0; i < n; ++i ) + { + Vec3 a = points[i] - centroid; + Vec3 b = weights[i]*a; + + covariance[0] += a.X()*b.X(); + covariance[1] += a.X()*b.Y(); + covariance[2] += a.X()*b.Z(); + covariance[3] += a.Y()*b.Y(); + covariance[4] += a.Y()*b.Z(); + covariance[5] += a.Z()*b.Z(); + } + + // return it + return covariance; +} + +static Vec3 GetMultiplicity1Evector( Sym3x3 const& matrix, float evalue ) +{ + // compute M + Sym3x3 m; + m[0] = matrix[0] - evalue; + m[1] = matrix[1]; + m[2] = matrix[2]; + m[3] = matrix[3] - evalue; + m[4] = matrix[4]; + m[5] = matrix[5] - evalue; + + // compute U + Sym3x3 u; + u[0] = m[3]*m[5] - m[4]*m[4]; + u[1] = m[2]*m[4] - m[1]*m[5]; + u[2] = m[1]*m[4] - m[2]*m[3]; + u[3] = m[0]*m[5] - m[2]*m[2]; + u[4] = m[1]*m[2] - m[4]*m[0]; + u[5] = m[0]*m[3] - m[1]*m[1]; + + // find the largest component + float mc = std::fabs( u[0] ); + int mi = 0; + for( int i = 1; i < 6; ++i ) + { + float c = std::fabs( u[i] ); + if( c > mc ) + { + mc = c; + mi = i; + } + } + + // pick the column with this component + switch( mi ) + { + case 0: + return Vec3( u[0], u[1], u[2] ); + + case 1: + case 3: + return Vec3( u[1], u[3], u[4] ); + + default: + return Vec3( u[2], u[4], u[5] ); + } +} + +static Vec3 GetMultiplicity2Evector( Sym3x3 const& matrix, float evalue ) +{ + // compute M + Sym3x3 m; + m[0] = matrix[0] - evalue; + m[1] = matrix[1]; + m[2] = matrix[2]; + m[3] = matrix[3] - evalue; + m[4] = matrix[4]; + m[5] = matrix[5] - evalue; + + // find the largest component + float mc = std::fabs( m[0] ); + int mi = 0; + for( int i = 1; i < 6; ++i ) + { + float c = std::fabs( m[i] ); + if( c > mc ) + { + mc = c; + mi = i; + } + } + + // pick the first eigenvector based on this index + switch( mi ) + { + case 0: + case 1: + return Vec3( -m[1], m[0], 0.0f ); + + case 2: + return Vec3( m[2], 0.0f, -m[0] ); + + case 3: + case 4: + return Vec3( 0.0f, -m[4], m[3] ); + + default: + return Vec3( 0.0f, -m[5], m[4] ); + } +} + +Vec3 ComputePrincipleComponent( Sym3x3 const& matrix ) +{ + // compute the cubic coefficients + float c0 = matrix[0]*matrix[3]*matrix[5] + + 2.0f*matrix[1]*matrix[2]*matrix[4] + - matrix[0]*matrix[4]*matrix[4] + - matrix[3]*matrix[2]*matrix[2] + - matrix[5]*matrix[1]*matrix[1]; + float c1 = matrix[0]*matrix[3] + matrix[0]*matrix[5] + matrix[3]*matrix[5] + - matrix[1]*matrix[1] - matrix[2]*matrix[2] - matrix[4]*matrix[4]; + float c2 = matrix[0] + matrix[3] + matrix[5]; + + // compute the quadratic coefficients + float a = c1 - ( 1.0f/3.0f )*c2*c2; + float b = ( -2.0f/27.0f )*c2*c2*c2 + ( 1.0f/3.0f )*c1*c2 - c0; + + // compute the root count check + float Q = 0.25f*b*b + ( 1.0f/27.0f )*a*a*a; + + // test the multiplicity + if( FLT_EPSILON < Q ) + { + // only one root, which implies we have a multiple of the identity + return Vec3( 1.0f ); + } + else if( Q < -FLT_EPSILON ) + { + // three distinct roots + float theta = std::atan2( std::sqrt( -Q ), -0.5f*b ); + float rho = std::sqrt( 0.25f*b*b - Q ); + + float rt = std::pow( rho, 1.0f/3.0f ); + float ct = std::cos( theta/3.0f ); + float st = std::sin( theta/3.0f ); + + float l1 = ( 1.0f/3.0f )*c2 + 2.0f*rt*ct; + float l2 = ( 1.0f/3.0f )*c2 - rt*( ct + ( float )sqrt( 3.0f )*st ); + float l3 = ( 1.0f/3.0f )*c2 - rt*( ct - ( float )sqrt( 3.0f )*st ); + + // pick the larger + if( std::fabs( l2 ) > std::fabs( l1 ) ) + l1 = l2; + if( std::fabs( l3 ) > std::fabs( l1 ) ) + l1 = l3; + + // get the eigenvector + return GetMultiplicity1Evector( matrix, l1 ); + } + else // if( -FLT_EPSILON <= Q && Q <= FLT_EPSILON ) + { + // two roots + float rt; + if( b < 0.0f ) + rt = -std::pow( -0.5f*b, 1.0f/3.0f ); + else + rt = std::pow( 0.5f*b, 1.0f/3.0f ); + + float l1 = ( 1.0f/3.0f )*c2 + rt; // repeated + float l2 = ( 1.0f/3.0f )*c2 - 2.0f*rt; + + // get the eigenvector + if( std::fabs( l1 ) > std::fabs( l2 ) ) + return GetMultiplicity2Evector( matrix, l1 ); + else + return GetMultiplicity1Evector( matrix, l2 ); + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/maths.h b/src/DdsFileType/Squish/maths.h new file mode 100644 index 0000000..769ae46 --- /dev/null +++ b/src/DdsFileType/Squish/maths.h @@ -0,0 +1,233 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_MATHS_H +#define SQUISH_MATHS_H + +#include +#include +#include "config.h" + +namespace squish { + +class Vec3 +{ +public: + typedef Vec3 const& Arg; + + Vec3() + { + } + + explicit Vec3( float s ) + { + m_x = s; + m_y = s; + m_z = s; + } + + Vec3( float x, float y, float z ) + { + m_x = x; + m_y = y; + m_z = z; + } + + float X() const { return m_x; } + float Y() const { return m_y; } + float Z() const { return m_z; } + + Vec3 operator-() const + { + return Vec3( -m_x, -m_y, -m_z ); + } + + Vec3& operator+=( Arg v ) + { + m_x += v.m_x; + m_y += v.m_y; + m_z += v.m_z; + return *this; + } + + Vec3& operator-=( Arg v ) + { + m_x -= v.m_x; + m_y -= v.m_y; + m_z -= v.m_z; + return *this; + } + + Vec3& operator*=( Arg v ) + { + m_x *= v.m_x; + m_y *= v.m_y; + m_z *= v.m_z; + return *this; + } + + Vec3& operator*=( float s ) + { + m_x *= s; + m_y *= s; + m_z *= s; + return *this; + } + + Vec3& operator/=( Arg v ) + { + m_x /= v.m_x; + m_y /= v.m_y; + m_z /= v.m_z; + return *this; + } + + Vec3& operator/=( float s ) + { + float t = 1.0f/s; + m_x *= t; + m_y *= t; + m_z *= t; + return *this; + } + + friend Vec3 operator+( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy += right; + } + + friend Vec3 operator-( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy -= right; + } + + friend Vec3 operator*( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy *= right; + } + + friend Vec3 operator*( Arg left, float right ) + { + Vec3 copy( left ); + return copy *= right; + } + + friend Vec3 operator*( float left, Arg right ) + { + Vec3 copy( right ); + return copy *= left; + } + + friend Vec3 operator/( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy /= right; + } + + friend Vec3 operator/( Arg left, float right ) + { + Vec3 copy( left ); + return copy /= right; + } + + friend float Dot( Arg left, Arg right ) + { + return left.m_x*right.m_x + left.m_y*right.m_y + left.m_z*right.m_z; + } + + friend Vec3 Min( Arg left, Arg right ) + { + return Vec3( + std::min( left.m_x, right.m_x ), + std::min( left.m_y, right.m_y ), + std::min( left.m_z, right.m_z ) + ); + } + + friend Vec3 Max( Arg left, Arg right ) + { + return Vec3( + std::max( left.m_x, right.m_x ), + std::max( left.m_y, right.m_y ), + std::max( left.m_z, right.m_z ) + ); + } + + friend Vec3 Truncate( Arg v ) + { + return Vec3( + v.m_x > 0.0f ? std::floor( v.m_x ) : std::ceil( v.m_x ), + v.m_y > 0.0f ? std::floor( v.m_y ) : std::ceil( v.m_y ), + v.m_z > 0.0f ? std::floor( v.m_z ) : std::ceil( v.m_z ) + ); + } + +private: + float m_x; + float m_y; + float m_z; +}; + +inline float LengthSquared( Vec3::Arg v ) +{ + return Dot( v, v ); +} + +class Sym3x3 +{ +public: + Sym3x3() + { + } + + Sym3x3( float s ) + { + for( int i = 0; i < 6; ++i ) + m_x[i] = s; + } + + float operator[]( int index ) const + { + return m_x[index]; + } + + float& operator[]( int index ) + { + return m_x[index]; + } + +private: + float m_x[6]; +}; + +Sym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights ); +Vec3 ComputePrincipleComponent( Sym3x3 const& matrix ); + +} // namespace squish + +#endif // ndef SQUISH_MATHS_H diff --git a/src/DdsFileType/Squish/rangefit.cpp b/src/DdsFileType/Squish/rangefit.cpp new file mode 100644 index 0000000..5a66436 --- /dev/null +++ b/src/DdsFileType/Squish/rangefit.cpp @@ -0,0 +1,202 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "rangefit.h" +#include "colourset.h" +#include "colourblock.h" +#include + +namespace squish { + +RangeFit::RangeFit( ColourSet const* colours, int flags ) + : ColourFit( colours, flags ) +{ + // initialise the metric + bool perceptual = ( ( m_flags & kColourMetricPerceptual ) != 0 ); + if( perceptual ) + m_metric = Vec3( 0.2126f, 0.7152f, 0.0722f ); + else + m_metric = Vec3( 1.0f ); + + // initialise the best error + m_besterror = FLT_MAX; + + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + float const* weights = m_colours->GetWeights(); + + // get the covariance matrix + Sym3x3 covariance = ComputeWeightedCovariance( count, values, weights ); + + // compute the principle component + Vec3 principle = ComputePrincipleComponent( covariance ); + + // get the min and max range as the codebook endpoints + Vec3 start( 0.0f ); + Vec3 end( 0.0f ); + if( count > 0 ) + { + float min, max; + + // compute the range + start = end = values[0]; + min = max = Dot( values[0], principle ); + for( int i = 1; i < count; ++i ) + { + float val = Dot( values[i], principle ); + if( val < min ) + { + start = values[i]; + min = val; + } + else if( val > max ) + { + end = values[i]; + max = val; + } + } + } + + // clamp the output to [0, 1] + Vec3 const one( 1.0f ); + Vec3 const zero( 0.0f ); + start = Min( one, Max( zero, start ) ); + end = Min( one, Max( zero, end ) ); + + // clamp to the grid and save + Vec3 const grid( 31.0f, 63.0f, 31.0f ); + Vec3 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f ); + Vec3 const half( 0.5f ); + m_start = Truncate( grid*start + half )*gridrcp; + m_end = Truncate( grid*end + half )*gridrcp; +} + +void RangeFit::Compress3( void* block ) +{ + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // create a codebook + Vec3 codes[3]; + codes[0] = m_start; + codes[1] = m_end; + codes[2] = 0.5f*m_start + 0.5f*m_end; + + // match each point to the closest code + u8 closest[16]; + float error = 0.0f; + for( int i = 0; i < count; ++i ) + { + // find the closest code + float dist = FLT_MAX; + int idx = 0; + for( int j = 0; j < 3; ++j ) + { + float d = LengthSquared( m_metric*( values[i] - codes[j] ) ); + if( d < dist ) + { + dist = d; + idx = j; + } + } + + // save the index + closest[i] = ( u8 )idx; + + // accumulate the error + error += dist; + } + + // save this scheme if it wins + if( error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( closest, indices ); + + // save the block + WriteColourBlock3( m_start, m_end, indices, block ); + + // save the error + m_besterror = error; + } +} + +void RangeFit::Compress4( void* block ) +{ + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // create a codebook + Vec3 codes[4]; + codes[0] = m_start; + codes[1] = m_end; + codes[2] = ( 2.0f/3.0f )*m_start + ( 1.0f/3.0f )*m_end; + codes[3] = ( 1.0f/3.0f )*m_start + ( 2.0f/3.0f )*m_end; + + // match each point to the closest code + u8 closest[16]; + float error = 0.0f; + for( int i = 0; i < count; ++i ) + { + // find the closest code + float dist = FLT_MAX; + int idx = 0; + for( int j = 0; j < 4; ++j ) + { + float d = LengthSquared( m_metric*( values[i] - codes[j] ) ); + if( d < dist ) + { + dist = d; + idx = j; + } + } + + // save the index + closest[i] = ( u8 )idx; + + // accumulate the error + error += dist; + } + + // save this scheme if it wins + if( error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( closest, indices ); + + // save the block + WriteColourBlock4( m_start, m_end, indices, block ); + + // save the error + m_besterror = error; + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/rangefit.h b/src/DdsFileType/Squish/rangefit.h new file mode 100644 index 0000000..7952019 --- /dev/null +++ b/src/DdsFileType/Squish/rangefit.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_RANGEFIT_H +#define SQUISH_RANGEFIT_H + +#include +#include "colourfit.h" +#include "maths.h" + +namespace squish { + +class ColourSet; + +class RangeFit : public ColourFit +{ +public: + RangeFit( ColourSet const* colours, int flags ); + +private: + virtual void Compress3( void* block ); + virtual void Compress4( void* block ); + + Vec3 m_metric; + Vec3 m_start; + Vec3 m_end; + float m_besterror; +}; + +} // squish + +#endif // ndef SQUISH_RANGEFIT_H diff --git a/src/DdsFileType/Squish/resource.h b/src/DdsFileType/Squish/resource.h new file mode 100644 index 0000000..6a9c2d4 --- /dev/null +++ b/src/DdsFileType/Squish/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Squish.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/DdsFileType/Squish/simd.h b/src/DdsFileType/Squish/simd.h new file mode 100644 index 0000000..22bd10a --- /dev/null +++ b/src/DdsFileType/Squish/simd.h @@ -0,0 +1,40 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_H +#define SQUISH_SIMD_H + +#include "maths.h" + +#if SQUISH_USE_ALTIVEC +#include "simd_ve.h" +#elif SQUISH_USE_SSE +#include "simd_sse.h" +#else +#include "simd_float.h" +#endif + + +#endif // ndef SQUISH_SIMD_H diff --git a/src/DdsFileType/Squish/simd_float.h b/src/DdsFileType/Squish/simd_float.h new file mode 100644 index 0000000..e6351b8 --- /dev/null +++ b/src/DdsFileType/Squish/simd_float.h @@ -0,0 +1,183 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_FLOAT_H +#define SQUISH_SIMD_FLOAT_H + +#include + +namespace squish { + +#define VEC4_CONST( X ) Vec4( X ) + +class Vec4 +{ +public: + typedef Vec4 const& Arg; + + Vec4() {} + + explicit Vec4( float s ) + : m_x( s ), + m_y( s ), + m_z( s ), + m_w( s ) + { + } + + Vec4( float x, float y, float z, float w ) + : m_x( x ), + m_y( y ), + m_z( z ), + m_w( w ) + { + } + + Vec3 GetVec3() const + { + return Vec3( m_x, m_y, m_z ); + } + + Vec4 SplatX() const { return Vec4( m_x ); } + Vec4 SplatY() const { return Vec4( m_y ); } + Vec4 SplatZ() const { return Vec4( m_z ); } + Vec4 SplatW() const { return Vec4( m_w ); } + + Vec4& operator+=( Arg v ) + { + m_x += v.m_x; + m_y += v.m_y; + m_z += v.m_z; + m_w += v.m_w; + return *this; + } + + Vec4& operator-=( Arg v ) + { + m_x -= v.m_x; + m_y -= v.m_y; + m_z -= v.m_z; + m_w -= v.m_w; + return *this; + } + + Vec4& operator*=( Arg v ) + { + m_x *= v.m_x; + m_y *= v.m_y; + m_z *= v.m_z; + m_w *= v.m_w; + return *this; + } + + friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right ) + { + Vec4 copy( left ); + return copy += right; + } + + friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right ) + { + Vec4 copy( left ); + return copy -= right; + } + + friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right ) + { + Vec4 copy( left ); + return copy *= right; + } + + //! Returns a*b + c + friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return a*b + c; + } + + //! Returns -( a*b - c ) + friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return c - a*b; + } + + friend Vec4 Reciprocal( Vec4::Arg v ) + { + return Vec4( + 1.0f/v.m_x, + 1.0f/v.m_y, + 1.0f/v.m_z, + 1.0f/v.m_w + ); + } + + friend Vec4 Min( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( + std::min( left.m_x, right.m_x ), + std::min( left.m_y, right.m_y ), + std::min( left.m_z, right.m_z ), + std::min( left.m_w, right.m_w ) + ); + } + + friend Vec4 Max( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( + std::max( left.m_x, right.m_x ), + std::max( left.m_y, right.m_y ), + std::max( left.m_z, right.m_z ), + std::max( left.m_w, right.m_w ) + ); + } + + friend Vec4 Truncate( Vec4::Arg v ) + { + return Vec4( + v.m_x > 0.0f ? std::floor( v.m_x ) : std::ceil( v.m_x ), + v.m_y > 0.0f ? std::floor( v.m_y ) : std::ceil( v.m_y ), + v.m_z > 0.0f ? std::floor( v.m_z ) : std::ceil( v.m_z ), + v.m_w > 0.0f ? std::floor( v.m_w ) : std::ceil( v.m_w ) + ); + } + + friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) + { + return left.m_x < right.m_x + || left.m_y < right.m_y + || left.m_z < right.m_z + || left.m_w < right.m_w; + } + +private: + float m_x; + float m_y; + float m_z; + float m_w; +}; + +} // namespace squish + +#endif // ndef SQUISH_SIMD_FLOAT_H + diff --git a/src/DdsFileType/Squish/simd_sse.h b/src/DdsFileType/Squish/simd_sse.h new file mode 100644 index 0000000..e584f2a --- /dev/null +++ b/src/DdsFileType/Squish/simd_sse.h @@ -0,0 +1,180 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_SSE_H +#define SQUISH_SIMD_SSE_H + +#include +#if ( SQUISH_USE_SSE > 1 ) +#include +#endif + +#define SQUISH_SSE_SPLAT( a ) \ + ( ( a ) | ( ( a ) << 2 ) | ( ( a ) << 4 ) | ( ( a ) << 6 ) ) + +#define SQUISH_SSE_SHUF( x, y, z, w ) \ + ( ( x ) | ( ( y ) << 2 ) | ( ( z ) << 4 ) | ( ( w ) << 6 ) ) + +namespace squish { + +#define VEC4_CONST( X ) Vec4( X ) + +class Vec4 +{ +public: + typedef Vec4 const& Arg; + + Vec4() {} + + explicit Vec4( __m128 v ) : m_v( v ) {} + + Vec4( Vec4 const& arg ) : m_v( arg.m_v ) {} + + Vec4& operator=( Vec4 const& arg ) + { + m_v = arg.m_v; + return *this; + } + + explicit Vec4( float s ) : m_v( _mm_set1_ps( s ) ) {} + + Vec4( float x, float y, float z, float w ) : m_v( _mm_setr_ps( x, y, z, w ) ) {} + + Vec3 GetVec3() const + { +#ifdef __GNUC__ + __attribute__ ((__aligned__ (16))) float c[4]; +#else + __declspec(align(16)) float c[4]; +#endif + _mm_store_ps( c, m_v ); + return Vec3( c[0], c[1], c[2] ); + } + + Vec4 SplatX() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 0 ) ) ); } + Vec4 SplatY() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 1 ) ) ); } + Vec4 SplatZ() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 2 ) ) ); } + Vec4 SplatW() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 3 ) ) ); } + + Vec4& operator+=( Arg v ) + { + m_v = _mm_add_ps( m_v, v.m_v ); + return *this; + } + + Vec4& operator-=( Arg v ) + { + m_v = _mm_sub_ps( m_v, v.m_v ); + return *this; + } + + Vec4& operator*=( Arg v ) + { + m_v = _mm_mul_ps( m_v, v.m_v ); + return *this; + } + + friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_add_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_sub_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_mul_ps( left.m_v, right.m_v ) ); + } + + //! Returns a*b + c + friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( _mm_add_ps( _mm_mul_ps( a.m_v, b.m_v ), c.m_v ) ); + } + + //! Returns -( a*b - c ) + friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( _mm_sub_ps( c.m_v, _mm_mul_ps( a.m_v, b.m_v ) ) ); + } + + friend Vec4 Reciprocal( Vec4::Arg v ) + { + // get the reciprocal estimate + __m128 estimate = _mm_rcp_ps( v.m_v ); + + // one round of Newton-Rhaphson refinement + __m128 diff = _mm_sub_ps( _mm_set1_ps( 1.0f ), _mm_mul_ps( estimate, v.m_v ) ); + return Vec4( _mm_add_ps( _mm_mul_ps( diff, estimate ), estimate ) ); + } + + friend Vec4 Min( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_min_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 Max( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_max_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 Truncate( Vec4::Arg v ) + { +#if ( SQUISH_USE_SSE == 1 ) + // convert to ints + __m128 input = v.m_v; + __m64 lo = _mm_cvttps_pi32( input ); + __m64 hi = _mm_cvttps_pi32( _mm_movehl_ps( input, input ) ); + + // convert to floats + __m128 part = _mm_movelh_ps( input, _mm_cvtpi32_ps( input, hi ) ); + __m128 truncated = _mm_cvtpi32_ps( part, lo ); + + // clear out the MMX multimedia state to allow FP calls later + _mm_empty(); + return Vec4( truncated ); +#else + // use SSE2 instructions + return Vec4( _mm_cvtepi32_ps( _mm_cvttps_epi32( v.m_v ) ) ); +#endif + } + + friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) + { + __m128 bits = _mm_cmplt_ps( left.m_v, right.m_v ); + int value = _mm_movemask_ps( bits ); + return value != 0; + } + +private: + __m128 m_v; +}; + +} // namespace squish + +#endif // ndef SQUISH_SIMD_SSE_H diff --git a/src/DdsFileType/Squish/simd_ve.h b/src/DdsFileType/Squish/simd_ve.h new file mode 100644 index 0000000..9a33955 --- /dev/null +++ b/src/DdsFileType/Squish/simd_ve.h @@ -0,0 +1,166 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_VE_H +#define SQUISH_SIMD_VE_H + +#include +#undef bool + +namespace squish { + +#define VEC4_CONST( X ) Vec4( ( vector float )( X ) ) + +class Vec4 +{ +public: + typedef Vec4 Arg; + + Vec4() {} + + explicit Vec4( vector float v ) : m_v( v ) {} + + Vec4( Vec4 const& arg ) : m_v( arg.m_v ) {} + + Vec4& operator=( Vec4 const& arg ) + { + m_v = arg.m_v; + return *this; + } + + explicit Vec4( float s ) + { + union { vector float v; float c[4]; } u; + u.c[0] = s; + u.c[1] = s; + u.c[2] = s; + u.c[3] = s; + m_v = u.v; + } + + Vec4( float x, float y, float z, float w ) + { + union { vector float v; float c[4]; } u; + u.c[0] = x; + u.c[1] = y; + u.c[2] = z; + u.c[3] = w; + m_v = u.v; + } + + Vec3 GetVec3() const + { + union { vector float v; float c[4]; } u; + u.v = m_v; + return Vec3( u.c[0], u.c[1], u.c[2] ); + } + + Vec4 SplatX() const { return Vec4( vec_splat( m_v, 0 ) ); } + Vec4 SplatY() const { return Vec4( vec_splat( m_v, 1 ) ); } + Vec4 SplatZ() const { return Vec4( vec_splat( m_v, 2 ) ); } + Vec4 SplatW() const { return Vec4( vec_splat( m_v, 3 ) ); } + + Vec4& operator+=( Arg v ) + { + m_v = vec_add( m_v, v.m_v ); + return *this; + } + + Vec4& operator-=( Arg v ) + { + m_v = vec_sub( m_v, v.m_v ); + return *this; + } + + Vec4& operator*=( Arg v ) + { + m_v = vec_madd( m_v, v.m_v, ( vector float )( -0.0f ) ); + return *this; + } + + friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_add( left.m_v, right.m_v ) ); + } + + friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_sub( left.m_v, right.m_v ) ); + } + + friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_madd( left.m_v, right.m_v, ( vector float )( -0.0f ) ) ); + } + + //! Returns a*b + c + friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( vec_madd( a.m_v, b.m_v, c.m_v ) ); + } + + //! Returns -( a*b - c ) + friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( vec_nmsub( a.m_v, b.m_v, c.m_v ) ); + } + + friend Vec4 Reciprocal( Vec4::Arg v ) + { + // get the reciprocal estimate + vector float estimate = vec_re( v.m_v ); + + // one round of Newton-Rhaphson refinement + vector float diff = vec_nmsub( estimate, v.m_v, ( vector float )( 1.0f ) ); + return Vec4( vec_madd( diff, estimate, estimate ) ); + } + + friend Vec4 Min( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_min( left.m_v, right.m_v ) ); + } + + friend Vec4 Max( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_max( left.m_v, right.m_v ) ); + } + + friend Vec4 Truncate( Vec4::Arg v ) + { + return Vec4( vec_trunc( v.m_v ) ); + } + + friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) + { + return vec_any_lt( left.m_v, right.m_v ) != 0; + } + +private: + vector float m_v; +}; + +} // namespace squish + +#endif // ndef SQUISH_SIMD_VE_H diff --git a/src/DdsFileType/Squish/singlecolourfit.cpp b/src/DdsFileType/Squish/singlecolourfit.cpp new file mode 100644 index 0000000..e8a0117 --- /dev/null +++ b/src/DdsFileType/Squish/singlecolourfit.cpp @@ -0,0 +1,172 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "singlecolourfit.h" +#include "colourset.h" +#include "colourblock.h" + +namespace squish { + +struct SourceBlock +{ + u8 start; + u8 end; + u8 error; +}; + +struct SingleColourLookup +{ + SourceBlock sources[2]; +}; + +#include "singlecolourlookup.inl" + +static int FloatToInt( float a, int limit ) +{ + // use ANSI round-to-zero behaviour to get round-to-nearest + int i = ( int )( a + 0.5f ); + + // clamp to the limit + if( i < 0 ) + i = 0; + else if( i > limit ) + i = limit; + + // done + return i; +} + +SingleColourFit::SingleColourFit( ColourSet const* colours, int flags ) + : ColourFit( colours, flags ) +{ + // grab the single colour + Vec3 const* values = m_colours->GetPoints(); + m_colour[0] = ( u8 )FloatToInt( 255.0f*values->X(), 255 ); + m_colour[1] = ( u8 )FloatToInt( 255.0f*values->Y(), 255 ); + m_colour[2] = ( u8 )FloatToInt( 255.0f*values->Z(), 255 ); + + // initialise the best error + m_besterror = INT_MAX; +} + +void SingleColourFit::Compress3( void* block ) +{ + // build the table of lookups + SingleColourLookup const* const lookups[] = + { + lookup_5_3, + lookup_6_3, + lookup_5_3 + }; + + // find the best end-points and index + ComputeEndPoints( lookups ); + + // build the block if we win + if( m_error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( &m_index, indices ); + + // save the block + WriteColourBlock3( m_start, m_end, indices, block ); + + // save the error + m_besterror = m_error; + } +} + +void SingleColourFit::Compress4( void* block ) +{ + // build the table of lookups + SingleColourLookup const* const lookups[] = + { + lookup_5_4, + lookup_6_4, + lookup_5_4 + }; + + // find the best end-points and index + ComputeEndPoints( lookups ); + + // build the block if we win + if( m_error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( &m_index, indices ); + + // save the block + WriteColourBlock4( m_start, m_end, indices, block ); + + // save the error + m_besterror = m_error; + } +} + +void SingleColourFit::ComputeEndPoints( SingleColourLookup const* const* lookups ) +{ + // check each index combination (endpoint or intermediate) + m_error = INT_MAX; + for( int index = 0; index < 2; ++index ) + { + // check the error for this codebook index + SourceBlock const* sources[3]; + int error = 0; + for( int channel = 0; channel < 3; ++channel ) + { + // grab the lookup table and index for this channel + SingleColourLookup const* lookup = lookups[channel]; + int target = m_colour[channel]; + + // store a pointer to the source for this channel + sources[channel] = lookup[target].sources + index; + + // accumulate the error + int diff = sources[channel]->error; + error += diff*diff; + } + + // keep it if the error is lower + if( error < m_error ) + { + m_start = Vec3( + ( float )sources[0]->start/31.0f, + ( float )sources[1]->start/63.0f, + ( float )sources[2]->start/31.0f + ); + m_end = Vec3( + ( float )sources[0]->end/31.0f, + ( float )sources[1]->end/63.0f, + ( float )sources[2]->end/31.0f + ); + m_index = ( u8 )( 2*index ); + m_error = error; + } + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/singlecolourfit.h b/src/DdsFileType/Squish/singlecolourfit.h new file mode 100644 index 0000000..0388fda --- /dev/null +++ b/src/DdsFileType/Squish/singlecolourfit.h @@ -0,0 +1,58 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SINGLECOLOURFIT_H +#define SQUISH_SINGLECOLOURFIT_H + +#include +#include "colourfit.h" + +namespace squish { + +class ColourSet; +struct SingleColourLookup; + +class SingleColourFit : public ColourFit +{ +public: + SingleColourFit( ColourSet const* colours, int flags ); + +private: + virtual void Compress3( void* block ); + virtual void Compress4( void* block ); + + void ComputeEndPoints( SingleColourLookup const* const* lookups ); + + u8 m_colour[3]; + Vec3 m_start; + Vec3 m_end; + u8 m_index; + int m_error; + int m_besterror; +}; + +} // namespace squish + +#endif // ndef SQUISH_SINGLECOLOURFIT_H diff --git a/src/DdsFileType/Squish/singlecolourlookup.inl b/src/DdsFileType/Squish/singlecolourlookup.inl new file mode 100644 index 0000000..f1c95a1 --- /dev/null +++ b/src/DdsFileType/Squish/singlecolourlookup.inl @@ -0,0 +1,1040 @@ + +static SingleColourLookup const lookup_5_3[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 0, 1 } } }, + { { { 0, 0, 2 }, { 0, 0, 2 } } }, + { { { 0, 0, 3 }, { 0, 1, 1 } } }, + { { { 0, 0, 4 }, { 0, 1, 0 } } }, + { { { 1, 0, 3 }, { 0, 1, 1 } } }, + { { { 1, 0, 2 }, { 0, 1, 2 } } }, + { { { 1, 0, 1 }, { 0, 2, 1 } } }, + { { { 1, 0, 0 }, { 0, 2, 0 } } }, + { { { 1, 0, 1 }, { 0, 2, 1 } } }, + { { { 1, 0, 2 }, { 0, 2, 2 } } }, + { { { 1, 0, 3 }, { 0, 3, 1 } } }, + { { { 1, 0, 4 }, { 0, 3, 0 } } }, + { { { 2, 0, 3 }, { 0, 3, 1 } } }, + { { { 2, 0, 2 }, { 0, 3, 2 } } }, + { { { 2, 0, 1 }, { 0, 4, 1 } } }, + { { { 2, 0, 0 }, { 0, 4, 0 } } }, + { { { 2, 0, 1 }, { 0, 4, 1 } } }, + { { { 2, 0, 2 }, { 0, 4, 2 } } }, + { { { 2, 0, 3 }, { 0, 5, 1 } } }, + { { { 2, 0, 4 }, { 0, 5, 0 } } }, + { { { 3, 0, 3 }, { 0, 5, 1 } } }, + { { { 3, 0, 2 }, { 0, 5, 2 } } }, + { { { 3, 0, 1 }, { 0, 6, 1 } } }, + { { { 3, 0, 0 }, { 0, 6, 0 } } }, + { { { 3, 0, 1 }, { 0, 6, 1 } } }, + { { { 3, 0, 2 }, { 0, 6, 2 } } }, + { { { 3, 0, 3 }, { 0, 7, 1 } } }, + { { { 3, 0, 4 }, { 0, 7, 0 } } }, + { { { 4, 0, 4 }, { 0, 7, 1 } } }, + { { { 4, 0, 3 }, { 0, 7, 2 } } }, + { { { 4, 0, 2 }, { 1, 7, 1 } } }, + { { { 4, 0, 1 }, { 1, 7, 0 } } }, + { { { 4, 0, 0 }, { 0, 8, 0 } } }, + { { { 4, 0, 1 }, { 0, 8, 1 } } }, + { { { 4, 0, 2 }, { 2, 7, 1 } } }, + { { { 4, 0, 3 }, { 2, 7, 0 } } }, + { { { 4, 0, 4 }, { 0, 9, 0 } } }, + { { { 5, 0, 3 }, { 0, 9, 1 } } }, + { { { 5, 0, 2 }, { 3, 7, 1 } } }, + { { { 5, 0, 1 }, { 3, 7, 0 } } }, + { { { 5, 0, 0 }, { 0, 10, 0 } } }, + { { { 5, 0, 1 }, { 0, 10, 1 } } }, + { { { 5, 0, 2 }, { 0, 10, 2 } } }, + { { { 5, 0, 3 }, { 0, 11, 1 } } }, + { { { 5, 0, 4 }, { 0, 11, 0 } } }, + { { { 6, 0, 3 }, { 0, 11, 1 } } }, + { { { 6, 0, 2 }, { 0, 11, 2 } } }, + { { { 6, 0, 1 }, { 0, 12, 1 } } }, + { { { 6, 0, 0 }, { 0, 12, 0 } } }, + { { { 6, 0, 1 }, { 0, 12, 1 } } }, + { { { 6, 0, 2 }, { 0, 12, 2 } } }, + { { { 6, 0, 3 }, { 0, 13, 1 } } }, + { { { 6, 0, 4 }, { 0, 13, 0 } } }, + { { { 7, 0, 3 }, { 0, 13, 1 } } }, + { { { 7, 0, 2 }, { 0, 13, 2 } } }, + { { { 7, 0, 1 }, { 0, 14, 1 } } }, + { { { 7, 0, 0 }, { 0, 14, 0 } } }, + { { { 7, 0, 1 }, { 0, 14, 1 } } }, + { { { 7, 0, 2 }, { 0, 14, 2 } } }, + { { { 7, 0, 3 }, { 0, 15, 1 } } }, + { { { 7, 0, 4 }, { 0, 15, 0 } } }, + { { { 8, 0, 4 }, { 0, 15, 1 } } }, + { { { 8, 0, 3 }, { 0, 15, 2 } } }, + { { { 8, 0, 2 }, { 1, 15, 1 } } }, + { { { 8, 0, 1 }, { 1, 15, 0 } } }, + { { { 8, 0, 0 }, { 0, 16, 0 } } }, + { { { 8, 0, 1 }, { 0, 16, 1 } } }, + { { { 8, 0, 2 }, { 2, 15, 1 } } }, + { { { 8, 0, 3 }, { 2, 15, 0 } } }, + { { { 8, 0, 4 }, { 0, 17, 0 } } }, + { { { 9, 0, 3 }, { 0, 17, 1 } } }, + { { { 9, 0, 2 }, { 3, 15, 1 } } }, + { { { 9, 0, 1 }, { 3, 15, 0 } } }, + { { { 9, 0, 0 }, { 0, 18, 0 } } }, + { { { 9, 0, 1 }, { 0, 18, 1 } } }, + { { { 9, 0, 2 }, { 0, 18, 2 } } }, + { { { 9, 0, 3 }, { 0, 19, 1 } } }, + { { { 9, 0, 4 }, { 0, 19, 0 } } }, + { { { 10, 0, 3 }, { 0, 19, 1 } } }, + { { { 10, 0, 2 }, { 0, 19, 2 } } }, + { { { 10, 0, 1 }, { 0, 20, 1 } } }, + { { { 10, 0, 0 }, { 0, 20, 0 } } }, + { { { 10, 0, 1 }, { 0, 20, 1 } } }, + { { { 10, 0, 2 }, { 0, 20, 2 } } }, + { { { 10, 0, 3 }, { 0, 21, 1 } } }, + { { { 10, 0, 4 }, { 0, 21, 0 } } }, + { { { 11, 0, 3 }, { 0, 21, 1 } } }, + { { { 11, 0, 2 }, { 0, 21, 2 } } }, + { { { 11, 0, 1 }, { 0, 22, 1 } } }, + { { { 11, 0, 0 }, { 0, 22, 0 } } }, + { { { 11, 0, 1 }, { 0, 22, 1 } } }, + { { { 11, 0, 2 }, { 0, 22, 2 } } }, + { { { 11, 0, 3 }, { 0, 23, 1 } } }, + { { { 11, 0, 4 }, { 0, 23, 0 } } }, + { { { 12, 0, 4 }, { 0, 23, 1 } } }, + { { { 12, 0, 3 }, { 0, 23, 2 } } }, + { { { 12, 0, 2 }, { 1, 23, 1 } } }, + { { { 12, 0, 1 }, { 1, 23, 0 } } }, + { { { 12, 0, 0 }, { 0, 24, 0 } } }, + { { { 12, 0, 1 }, { 0, 24, 1 } } }, + { { { 12, 0, 2 }, { 2, 23, 1 } } }, + { { { 12, 0, 3 }, { 2, 23, 0 } } }, + { { { 12, 0, 4 }, { 0, 25, 0 } } }, + { { { 13, 0, 3 }, { 0, 25, 1 } } }, + { { { 13, 0, 2 }, { 3, 23, 1 } } }, + { { { 13, 0, 1 }, { 3, 23, 0 } } }, + { { { 13, 0, 0 }, { 0, 26, 0 } } }, + { { { 13, 0, 1 }, { 0, 26, 1 } } }, + { { { 13, 0, 2 }, { 0, 26, 2 } } }, + { { { 13, 0, 3 }, { 0, 27, 1 } } }, + { { { 13, 0, 4 }, { 0, 27, 0 } } }, + { { { 14, 0, 3 }, { 0, 27, 1 } } }, + { { { 14, 0, 2 }, { 0, 27, 2 } } }, + { { { 14, 0, 1 }, { 0, 28, 1 } } }, + { { { 14, 0, 0 }, { 0, 28, 0 } } }, + { { { 14, 0, 1 }, { 0, 28, 1 } } }, + { { { 14, 0, 2 }, { 0, 28, 2 } } }, + { { { 14, 0, 3 }, { 0, 29, 1 } } }, + { { { 14, 0, 4 }, { 0, 29, 0 } } }, + { { { 15, 0, 3 }, { 0, 29, 1 } } }, + { { { 15, 0, 2 }, { 0, 29, 2 } } }, + { { { 15, 0, 1 }, { 0, 30, 1 } } }, + { { { 15, 0, 0 }, { 0, 30, 0 } } }, + { { { 15, 0, 1 }, { 0, 30, 1 } } }, + { { { 15, 0, 2 }, { 0, 30, 2 } } }, + { { { 15, 0, 3 }, { 0, 31, 1 } } }, + { { { 15, 0, 4 }, { 0, 31, 0 } } }, + { { { 16, 0, 4 }, { 0, 31, 1 } } }, + { { { 16, 0, 3 }, { 0, 31, 2 } } }, + { { { 16, 0, 2 }, { 1, 31, 1 } } }, + { { { 16, 0, 1 }, { 1, 31, 0 } } }, + { { { 16, 0, 0 }, { 4, 28, 0 } } }, + { { { 16, 0, 1 }, { 4, 28, 1 } } }, + { { { 16, 0, 2 }, { 2, 31, 1 } } }, + { { { 16, 0, 3 }, { 2, 31, 0 } } }, + { { { 16, 0, 4 }, { 4, 29, 0 } } }, + { { { 17, 0, 3 }, { 4, 29, 1 } } }, + { { { 17, 0, 2 }, { 3, 31, 1 } } }, + { { { 17, 0, 1 }, { 3, 31, 0 } } }, + { { { 17, 0, 0 }, { 4, 30, 0 } } }, + { { { 17, 0, 1 }, { 4, 30, 1 } } }, + { { { 17, 0, 2 }, { 4, 30, 2 } } }, + { { { 17, 0, 3 }, { 4, 31, 1 } } }, + { { { 17, 0, 4 }, { 4, 31, 0 } } }, + { { { 18, 0, 3 }, { 4, 31, 1 } } }, + { { { 18, 0, 2 }, { 4, 31, 2 } } }, + { { { 18, 0, 1 }, { 5, 31, 1 } } }, + { { { 18, 0, 0 }, { 5, 31, 0 } } }, + { { { 18, 0, 1 }, { 5, 31, 1 } } }, + { { { 18, 0, 2 }, { 5, 31, 2 } } }, + { { { 18, 0, 3 }, { 6, 31, 1 } } }, + { { { 18, 0, 4 }, { 6, 31, 0 } } }, + { { { 19, 0, 3 }, { 6, 31, 1 } } }, + { { { 19, 0, 2 }, { 6, 31, 2 } } }, + { { { 19, 0, 1 }, { 7, 31, 1 } } }, + { { { 19, 0, 0 }, { 7, 31, 0 } } }, + { { { 19, 0, 1 }, { 7, 31, 1 } } }, + { { { 19, 0, 2 }, { 7, 31, 2 } } }, + { { { 19, 0, 3 }, { 8, 31, 1 } } }, + { { { 19, 0, 4 }, { 8, 31, 0 } } }, + { { { 20, 0, 4 }, { 8, 31, 1 } } }, + { { { 20, 0, 3 }, { 8, 31, 2 } } }, + { { { 20, 0, 2 }, { 9, 31, 1 } } }, + { { { 20, 0, 1 }, { 9, 31, 0 } } }, + { { { 20, 0, 0 }, { 12, 28, 0 } } }, + { { { 20, 0, 1 }, { 12, 28, 1 } } }, + { { { 20, 0, 2 }, { 10, 31, 1 } } }, + { { { 20, 0, 3 }, { 10, 31, 0 } } }, + { { { 20, 0, 4 }, { 12, 29, 0 } } }, + { { { 21, 0, 3 }, { 12, 29, 1 } } }, + { { { 21, 0, 2 }, { 11, 31, 1 } } }, + { { { 21, 0, 1 }, { 11, 31, 0 } } }, + { { { 21, 0, 0 }, { 12, 30, 0 } } }, + { { { 21, 0, 1 }, { 12, 30, 1 } } }, + { { { 21, 0, 2 }, { 12, 30, 2 } } }, + { { { 21, 0, 3 }, { 12, 31, 1 } } }, + { { { 21, 0, 4 }, { 12, 31, 0 } } }, + { { { 22, 0, 3 }, { 12, 31, 1 } } }, + { { { 22, 0, 2 }, { 12, 31, 2 } } }, + { { { 22, 0, 1 }, { 13, 31, 1 } } }, + { { { 22, 0, 0 }, { 13, 31, 0 } } }, + { { { 22, 0, 1 }, { 13, 31, 1 } } }, + { { { 22, 0, 2 }, { 13, 31, 2 } } }, + { { { 22, 0, 3 }, { 14, 31, 1 } } }, + { { { 22, 0, 4 }, { 14, 31, 0 } } }, + { { { 23, 0, 3 }, { 14, 31, 1 } } }, + { { { 23, 0, 2 }, { 14, 31, 2 } } }, + { { { 23, 0, 1 }, { 15, 31, 1 } } }, + { { { 23, 0, 0 }, { 15, 31, 0 } } }, + { { { 23, 0, 1 }, { 15, 31, 1 } } }, + { { { 23, 0, 2 }, { 15, 31, 2 } } }, + { { { 23, 0, 3 }, { 16, 31, 1 } } }, + { { { 23, 0, 4 }, { 16, 31, 0 } } }, + { { { 24, 0, 4 }, { 16, 31, 1 } } }, + { { { 24, 0, 3 }, { 16, 31, 2 } } }, + { { { 24, 0, 2 }, { 17, 31, 1 } } }, + { { { 24, 0, 1 }, { 17, 31, 0 } } }, + { { { 24, 0, 0 }, { 20, 28, 0 } } }, + { { { 24, 0, 1 }, { 20, 28, 1 } } }, + { { { 24, 0, 2 }, { 18, 31, 1 } } }, + { { { 24, 0, 3 }, { 18, 31, 0 } } }, + { { { 24, 0, 4 }, { 20, 29, 0 } } }, + { { { 25, 0, 3 }, { 20, 29, 1 } } }, + { { { 25, 0, 2 }, { 19, 31, 1 } } }, + { { { 25, 0, 1 }, { 19, 31, 0 } } }, + { { { 25, 0, 0 }, { 20, 30, 0 } } }, + { { { 25, 0, 1 }, { 20, 30, 1 } } }, + { { { 25, 0, 2 }, { 20, 30, 2 } } }, + { { { 25, 0, 3 }, { 20, 31, 1 } } }, + { { { 25, 0, 4 }, { 20, 31, 0 } } }, + { { { 26, 0, 3 }, { 20, 31, 1 } } }, + { { { 26, 0, 2 }, { 20, 31, 2 } } }, + { { { 26, 0, 1 }, { 21, 31, 1 } } }, + { { { 26, 0, 0 }, { 21, 31, 0 } } }, + { { { 26, 0, 1 }, { 21, 31, 1 } } }, + { { { 26, 0, 2 }, { 21, 31, 2 } } }, + { { { 26, 0, 3 }, { 22, 31, 1 } } }, + { { { 26, 0, 4 }, { 22, 31, 0 } } }, + { { { 27, 0, 3 }, { 22, 31, 1 } } }, + { { { 27, 0, 2 }, { 22, 31, 2 } } }, + { { { 27, 0, 1 }, { 23, 31, 1 } } }, + { { { 27, 0, 0 }, { 23, 31, 0 } } }, + { { { 27, 0, 1 }, { 23, 31, 1 } } }, + { { { 27, 0, 2 }, { 23, 31, 2 } } }, + { { { 27, 0, 3 }, { 24, 31, 1 } } }, + { { { 27, 0, 4 }, { 24, 31, 0 } } }, + { { { 28, 0, 4 }, { 24, 31, 1 } } }, + { { { 28, 0, 3 }, { 24, 31, 2 } } }, + { { { 28, 0, 2 }, { 25, 31, 1 } } }, + { { { 28, 0, 1 }, { 25, 31, 0 } } }, + { { { 28, 0, 0 }, { 28, 28, 0 } } }, + { { { 28, 0, 1 }, { 28, 28, 1 } } }, + { { { 28, 0, 2 }, { 26, 31, 1 } } }, + { { { 28, 0, 3 }, { 26, 31, 0 } } }, + { { { 28, 0, 4 }, { 28, 29, 0 } } }, + { { { 29, 0, 3 }, { 28, 29, 1 } } }, + { { { 29, 0, 2 }, { 27, 31, 1 } } }, + { { { 29, 0, 1 }, { 27, 31, 0 } } }, + { { { 29, 0, 0 }, { 28, 30, 0 } } }, + { { { 29, 0, 1 }, { 28, 30, 1 } } }, + { { { 29, 0, 2 }, { 28, 30, 2 } } }, + { { { 29, 0, 3 }, { 28, 31, 1 } } }, + { { { 29, 0, 4 }, { 28, 31, 0 } } }, + { { { 30, 0, 3 }, { 28, 31, 1 } } }, + { { { 30, 0, 2 }, { 28, 31, 2 } } }, + { { { 30, 0, 1 }, { 29, 31, 1 } } }, + { { { 30, 0, 0 }, { 29, 31, 0 } } }, + { { { 30, 0, 1 }, { 29, 31, 1 } } }, + { { { 30, 0, 2 }, { 29, 31, 2 } } }, + { { { 30, 0, 3 }, { 30, 31, 1 } } }, + { { { 30, 0, 4 }, { 30, 31, 0 } } }, + { { { 31, 0, 3 }, { 30, 31, 1 } } }, + { { { 31, 0, 2 }, { 30, 31, 2 } } }, + { { { 31, 0, 1 }, { 31, 31, 1 } } }, + { { { 31, 0, 0 }, { 31, 31, 0 } } } +}; + +static SingleColourLookup const lookup_6_3[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 1, 1 } } }, + { { { 0, 0, 2 }, { 0, 1, 0 } } }, + { { { 1, 0, 1 }, { 0, 2, 1 } } }, + { { { 1, 0, 0 }, { 0, 2, 0 } } }, + { { { 1, 0, 1 }, { 0, 3, 1 } } }, + { { { 1, 0, 2 }, { 0, 3, 0 } } }, + { { { 2, 0, 1 }, { 0, 4, 1 } } }, + { { { 2, 0, 0 }, { 0, 4, 0 } } }, + { { { 2, 0, 1 }, { 0, 5, 1 } } }, + { { { 2, 0, 2 }, { 0, 5, 0 } } }, + { { { 3, 0, 1 }, { 0, 6, 1 } } }, + { { { 3, 0, 0 }, { 0, 6, 0 } } }, + { { { 3, 0, 1 }, { 0, 7, 1 } } }, + { { { 3, 0, 2 }, { 0, 7, 0 } } }, + { { { 4, 0, 1 }, { 0, 8, 1 } } }, + { { { 4, 0, 0 }, { 0, 8, 0 } } }, + { { { 4, 0, 1 }, { 0, 9, 1 } } }, + { { { 4, 0, 2 }, { 0, 9, 0 } } }, + { { { 5, 0, 1 }, { 0, 10, 1 } } }, + { { { 5, 0, 0 }, { 0, 10, 0 } } }, + { { { 5, 0, 1 }, { 0, 11, 1 } } }, + { { { 5, 0, 2 }, { 0, 11, 0 } } }, + { { { 6, 0, 1 }, { 0, 12, 1 } } }, + { { { 6, 0, 0 }, { 0, 12, 0 } } }, + { { { 6, 0, 1 }, { 0, 13, 1 } } }, + { { { 6, 0, 2 }, { 0, 13, 0 } } }, + { { { 7, 0, 1 }, { 0, 14, 1 } } }, + { { { 7, 0, 0 }, { 0, 14, 0 } } }, + { { { 7, 0, 1 }, { 0, 15, 1 } } }, + { { { 7, 0, 2 }, { 0, 15, 0 } } }, + { { { 8, 0, 1 }, { 0, 16, 1 } } }, + { { { 8, 0, 0 }, { 0, 16, 0 } } }, + { { { 8, 0, 1 }, { 0, 17, 1 } } }, + { { { 8, 0, 2 }, { 0, 17, 0 } } }, + { { { 9, 0, 1 }, { 0, 18, 1 } } }, + { { { 9, 0, 0 }, { 0, 18, 0 } } }, + { { { 9, 0, 1 }, { 0, 19, 1 } } }, + { { { 9, 0, 2 }, { 0, 19, 0 } } }, + { { { 10, 0, 1 }, { 0, 20, 1 } } }, + { { { 10, 0, 0 }, { 0, 20, 0 } } }, + { { { 10, 0, 1 }, { 0, 21, 1 } } }, + { { { 10, 0, 2 }, { 0, 21, 0 } } }, + { { { 11, 0, 1 }, { 0, 22, 1 } } }, + { { { 11, 0, 0 }, { 0, 22, 0 } } }, + { { { 11, 0, 1 }, { 0, 23, 1 } } }, + { { { 11, 0, 2 }, { 0, 23, 0 } } }, + { { { 12, 0, 1 }, { 0, 24, 1 } } }, + { { { 12, 0, 0 }, { 0, 24, 0 } } }, + { { { 12, 0, 1 }, { 0, 25, 1 } } }, + { { { 12, 0, 2 }, { 0, 25, 0 } } }, + { { { 13, 0, 1 }, { 0, 26, 1 } } }, + { { { 13, 0, 0 }, { 0, 26, 0 } } }, + { { { 13, 0, 1 }, { 0, 27, 1 } } }, + { { { 13, 0, 2 }, { 0, 27, 0 } } }, + { { { 14, 0, 1 }, { 0, 28, 1 } } }, + { { { 14, 0, 0 }, { 0, 28, 0 } } }, + { { { 14, 0, 1 }, { 0, 29, 1 } } }, + { { { 14, 0, 2 }, { 0, 29, 0 } } }, + { { { 15, 0, 1 }, { 0, 30, 1 } } }, + { { { 15, 0, 0 }, { 0, 30, 0 } } }, + { { { 15, 0, 1 }, { 0, 31, 1 } } }, + { { { 15, 0, 2 }, { 0, 31, 0 } } }, + { { { 16, 0, 2 }, { 1, 31, 1 } } }, + { { { 16, 0, 1 }, { 1, 31, 0 } } }, + { { { 16, 0, 0 }, { 0, 32, 0 } } }, + { { { 16, 0, 1 }, { 2, 31, 0 } } }, + { { { 16, 0, 2 }, { 0, 33, 0 } } }, + { { { 17, 0, 1 }, { 3, 31, 0 } } }, + { { { 17, 0, 0 }, { 0, 34, 0 } } }, + { { { 17, 0, 1 }, { 4, 31, 0 } } }, + { { { 17, 0, 2 }, { 0, 35, 0 } } }, + { { { 18, 0, 1 }, { 5, 31, 0 } } }, + { { { 18, 0, 0 }, { 0, 36, 0 } } }, + { { { 18, 0, 1 }, { 6, 31, 0 } } }, + { { { 18, 0, 2 }, { 0, 37, 0 } } }, + { { { 19, 0, 1 }, { 7, 31, 0 } } }, + { { { 19, 0, 0 }, { 0, 38, 0 } } }, + { { { 19, 0, 1 }, { 8, 31, 0 } } }, + { { { 19, 0, 2 }, { 0, 39, 0 } } }, + { { { 20, 0, 1 }, { 9, 31, 0 } } }, + { { { 20, 0, 0 }, { 0, 40, 0 } } }, + { { { 20, 0, 1 }, { 10, 31, 0 } } }, + { { { 20, 0, 2 }, { 0, 41, 0 } } }, + { { { 21, 0, 1 }, { 11, 31, 0 } } }, + { { { 21, 0, 0 }, { 0, 42, 0 } } }, + { { { 21, 0, 1 }, { 12, 31, 0 } } }, + { { { 21, 0, 2 }, { 0, 43, 0 } } }, + { { { 22, 0, 1 }, { 13, 31, 0 } } }, + { { { 22, 0, 0 }, { 0, 44, 0 } } }, + { { { 22, 0, 1 }, { 14, 31, 0 } } }, + { { { 22, 0, 2 }, { 0, 45, 0 } } }, + { { { 23, 0, 1 }, { 15, 31, 0 } } }, + { { { 23, 0, 0 }, { 0, 46, 0 } } }, + { { { 23, 0, 1 }, { 0, 47, 1 } } }, + { { { 23, 0, 2 }, { 0, 47, 0 } } }, + { { { 24, 0, 1 }, { 0, 48, 1 } } }, + { { { 24, 0, 0 }, { 0, 48, 0 } } }, + { { { 24, 0, 1 }, { 0, 49, 1 } } }, + { { { 24, 0, 2 }, { 0, 49, 0 } } }, + { { { 25, 0, 1 }, { 0, 50, 1 } } }, + { { { 25, 0, 0 }, { 0, 50, 0 } } }, + { { { 25, 0, 1 }, { 0, 51, 1 } } }, + { { { 25, 0, 2 }, { 0, 51, 0 } } }, + { { { 26, 0, 1 }, { 0, 52, 1 } } }, + { { { 26, 0, 0 }, { 0, 52, 0 } } }, + { { { 26, 0, 1 }, { 0, 53, 1 } } }, + { { { 26, 0, 2 }, { 0, 53, 0 } } }, + { { { 27, 0, 1 }, { 0, 54, 1 } } }, + { { { 27, 0, 0 }, { 0, 54, 0 } } }, + { { { 27, 0, 1 }, { 0, 55, 1 } } }, + { { { 27, 0, 2 }, { 0, 55, 0 } } }, + { { { 28, 0, 1 }, { 0, 56, 1 } } }, + { { { 28, 0, 0 }, { 0, 56, 0 } } }, + { { { 28, 0, 1 }, { 0, 57, 1 } } }, + { { { 28, 0, 2 }, { 0, 57, 0 } } }, + { { { 29, 0, 1 }, { 0, 58, 1 } } }, + { { { 29, 0, 0 }, { 0, 58, 0 } } }, + { { { 29, 0, 1 }, { 0, 59, 1 } } }, + { { { 29, 0, 2 }, { 0, 59, 0 } } }, + { { { 30, 0, 1 }, { 0, 60, 1 } } }, + { { { 30, 0, 0 }, { 0, 60, 0 } } }, + { { { 30, 0, 1 }, { 0, 61, 1 } } }, + { { { 30, 0, 2 }, { 0, 61, 0 } } }, + { { { 31, 0, 1 }, { 0, 62, 1 } } }, + { { { 31, 0, 0 }, { 0, 62, 0 } } }, + { { { 31, 0, 1 }, { 0, 63, 1 } } }, + { { { 31, 0, 2 }, { 0, 63, 0 } } }, + { { { 32, 0, 2 }, { 1, 63, 1 } } }, + { { { 32, 0, 1 }, { 1, 63, 0 } } }, + { { { 32, 0, 0 }, { 16, 48, 0 } } }, + { { { 32, 0, 1 }, { 2, 63, 0 } } }, + { { { 32, 0, 2 }, { 16, 49, 0 } } }, + { { { 33, 0, 1 }, { 3, 63, 0 } } }, + { { { 33, 0, 0 }, { 16, 50, 0 } } }, + { { { 33, 0, 1 }, { 4, 63, 0 } } }, + { { { 33, 0, 2 }, { 16, 51, 0 } } }, + { { { 34, 0, 1 }, { 5, 63, 0 } } }, + { { { 34, 0, 0 }, { 16, 52, 0 } } }, + { { { 34, 0, 1 }, { 6, 63, 0 } } }, + { { { 34, 0, 2 }, { 16, 53, 0 } } }, + { { { 35, 0, 1 }, { 7, 63, 0 } } }, + { { { 35, 0, 0 }, { 16, 54, 0 } } }, + { { { 35, 0, 1 }, { 8, 63, 0 } } }, + { { { 35, 0, 2 }, { 16, 55, 0 } } }, + { { { 36, 0, 1 }, { 9, 63, 0 } } }, + { { { 36, 0, 0 }, { 16, 56, 0 } } }, + { { { 36, 0, 1 }, { 10, 63, 0 } } }, + { { { 36, 0, 2 }, { 16, 57, 0 } } }, + { { { 37, 0, 1 }, { 11, 63, 0 } } }, + { { { 37, 0, 0 }, { 16, 58, 0 } } }, + { { { 37, 0, 1 }, { 12, 63, 0 } } }, + { { { 37, 0, 2 }, { 16, 59, 0 } } }, + { { { 38, 0, 1 }, { 13, 63, 0 } } }, + { { { 38, 0, 0 }, { 16, 60, 0 } } }, + { { { 38, 0, 1 }, { 14, 63, 0 } } }, + { { { 38, 0, 2 }, { 16, 61, 0 } } }, + { { { 39, 0, 1 }, { 15, 63, 0 } } }, + { { { 39, 0, 0 }, { 16, 62, 0 } } }, + { { { 39, 0, 1 }, { 16, 63, 1 } } }, + { { { 39, 0, 2 }, { 16, 63, 0 } } }, + { { { 40, 0, 1 }, { 17, 63, 1 } } }, + { { { 40, 0, 0 }, { 17, 63, 0 } } }, + { { { 40, 0, 1 }, { 18, 63, 1 } } }, + { { { 40, 0, 2 }, { 18, 63, 0 } } }, + { { { 41, 0, 1 }, { 19, 63, 1 } } }, + { { { 41, 0, 0 }, { 19, 63, 0 } } }, + { { { 41, 0, 1 }, { 20, 63, 1 } } }, + { { { 41, 0, 2 }, { 20, 63, 0 } } }, + { { { 42, 0, 1 }, { 21, 63, 1 } } }, + { { { 42, 0, 0 }, { 21, 63, 0 } } }, + { { { 42, 0, 1 }, { 22, 63, 1 } } }, + { { { 42, 0, 2 }, { 22, 63, 0 } } }, + { { { 43, 0, 1 }, { 23, 63, 1 } } }, + { { { 43, 0, 0 }, { 23, 63, 0 } } }, + { { { 43, 0, 1 }, { 24, 63, 1 } } }, + { { { 43, 0, 2 }, { 24, 63, 0 } } }, + { { { 44, 0, 1 }, { 25, 63, 1 } } }, + { { { 44, 0, 0 }, { 25, 63, 0 } } }, + { { { 44, 0, 1 }, { 26, 63, 1 } } }, + { { { 44, 0, 2 }, { 26, 63, 0 } } }, + { { { 45, 0, 1 }, { 27, 63, 1 } } }, + { { { 45, 0, 0 }, { 27, 63, 0 } } }, + { { { 45, 0, 1 }, { 28, 63, 1 } } }, + { { { 45, 0, 2 }, { 28, 63, 0 } } }, + { { { 46, 0, 1 }, { 29, 63, 1 } } }, + { { { 46, 0, 0 }, { 29, 63, 0 } } }, + { { { 46, 0, 1 }, { 30, 63, 1 } } }, + { { { 46, 0, 2 }, { 30, 63, 0 } } }, + { { { 47, 0, 1 }, { 31, 63, 1 } } }, + { { { 47, 0, 0 }, { 31, 63, 0 } } }, + { { { 47, 0, 1 }, { 32, 63, 1 } } }, + { { { 47, 0, 2 }, { 32, 63, 0 } } }, + { { { 48, 0, 2 }, { 33, 63, 1 } } }, + { { { 48, 0, 1 }, { 33, 63, 0 } } }, + { { { 48, 0, 0 }, { 48, 48, 0 } } }, + { { { 48, 0, 1 }, { 34, 63, 0 } } }, + { { { 48, 0, 2 }, { 48, 49, 0 } } }, + { { { 49, 0, 1 }, { 35, 63, 0 } } }, + { { { 49, 0, 0 }, { 48, 50, 0 } } }, + { { { 49, 0, 1 }, { 36, 63, 0 } } }, + { { { 49, 0, 2 }, { 48, 51, 0 } } }, + { { { 50, 0, 1 }, { 37, 63, 0 } } }, + { { { 50, 0, 0 }, { 48, 52, 0 } } }, + { { { 50, 0, 1 }, { 38, 63, 0 } } }, + { { { 50, 0, 2 }, { 48, 53, 0 } } }, + { { { 51, 0, 1 }, { 39, 63, 0 } } }, + { { { 51, 0, 0 }, { 48, 54, 0 } } }, + { { { 51, 0, 1 }, { 40, 63, 0 } } }, + { { { 51, 0, 2 }, { 48, 55, 0 } } }, + { { { 52, 0, 1 }, { 41, 63, 0 } } }, + { { { 52, 0, 0 }, { 48, 56, 0 } } }, + { { { 52, 0, 1 }, { 42, 63, 0 } } }, + { { { 52, 0, 2 }, { 48, 57, 0 } } }, + { { { 53, 0, 1 }, { 43, 63, 0 } } }, + { { { 53, 0, 0 }, { 48, 58, 0 } } }, + { { { 53, 0, 1 }, { 44, 63, 0 } } }, + { { { 53, 0, 2 }, { 48, 59, 0 } } }, + { { { 54, 0, 1 }, { 45, 63, 0 } } }, + { { { 54, 0, 0 }, { 48, 60, 0 } } }, + { { { 54, 0, 1 }, { 46, 63, 0 } } }, + { { { 54, 0, 2 }, { 48, 61, 0 } } }, + { { { 55, 0, 1 }, { 47, 63, 0 } } }, + { { { 55, 0, 0 }, { 48, 62, 0 } } }, + { { { 55, 0, 1 }, { 48, 63, 1 } } }, + { { { 55, 0, 2 }, { 48, 63, 0 } } }, + { { { 56, 0, 1 }, { 49, 63, 1 } } }, + { { { 56, 0, 0 }, { 49, 63, 0 } } }, + { { { 56, 0, 1 }, { 50, 63, 1 } } }, + { { { 56, 0, 2 }, { 50, 63, 0 } } }, + { { { 57, 0, 1 }, { 51, 63, 1 } } }, + { { { 57, 0, 0 }, { 51, 63, 0 } } }, + { { { 57, 0, 1 }, { 52, 63, 1 } } }, + { { { 57, 0, 2 }, { 52, 63, 0 } } }, + { { { 58, 0, 1 }, { 53, 63, 1 } } }, + { { { 58, 0, 0 }, { 53, 63, 0 } } }, + { { { 58, 0, 1 }, { 54, 63, 1 } } }, + { { { 58, 0, 2 }, { 54, 63, 0 } } }, + { { { 59, 0, 1 }, { 55, 63, 1 } } }, + { { { 59, 0, 0 }, { 55, 63, 0 } } }, + { { { 59, 0, 1 }, { 56, 63, 1 } } }, + { { { 59, 0, 2 }, { 56, 63, 0 } } }, + { { { 60, 0, 1 }, { 57, 63, 1 } } }, + { { { 60, 0, 0 }, { 57, 63, 0 } } }, + { { { 60, 0, 1 }, { 58, 63, 1 } } }, + { { { 60, 0, 2 }, { 58, 63, 0 } } }, + { { { 61, 0, 1 }, { 59, 63, 1 } } }, + { { { 61, 0, 0 }, { 59, 63, 0 } } }, + { { { 61, 0, 1 }, { 60, 63, 1 } } }, + { { { 61, 0, 2 }, { 60, 63, 0 } } }, + { { { 62, 0, 1 }, { 61, 63, 1 } } }, + { { { 62, 0, 0 }, { 61, 63, 0 } } }, + { { { 62, 0, 1 }, { 62, 63, 1 } } }, + { { { 62, 0, 2 }, { 62, 63, 0 } } }, + { { { 63, 0, 1 }, { 63, 63, 1 } } }, + { { { 63, 0, 0 }, { 63, 63, 0 } } } +}; + +static SingleColourLookup const lookup_5_4[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 1, 1 } } }, + { { { 0, 0, 2 }, { 0, 1, 0 } } }, + { { { 0, 0, 3 }, { 0, 1, 1 } } }, + { { { 0, 0, 4 }, { 0, 2, 1 } } }, + { { { 1, 0, 3 }, { 0, 2, 0 } } }, + { { { 1, 0, 2 }, { 0, 2, 1 } } }, + { { { 1, 0, 1 }, { 0, 3, 1 } } }, + { { { 1, 0, 0 }, { 0, 3, 0 } } }, + { { { 1, 0, 1 }, { 1, 2, 1 } } }, + { { { 1, 0, 2 }, { 1, 2, 0 } } }, + { { { 1, 0, 3 }, { 0, 4, 0 } } }, + { { { 1, 0, 4 }, { 0, 5, 1 } } }, + { { { 2, 0, 3 }, { 0, 5, 0 } } }, + { { { 2, 0, 2 }, { 0, 5, 1 } } }, + { { { 2, 0, 1 }, { 0, 6, 1 } } }, + { { { 2, 0, 0 }, { 0, 6, 0 } } }, + { { { 2, 0, 1 }, { 2, 3, 1 } } }, + { { { 2, 0, 2 }, { 2, 3, 0 } } }, + { { { 2, 0, 3 }, { 0, 7, 0 } } }, + { { { 2, 0, 4 }, { 1, 6, 1 } } }, + { { { 3, 0, 3 }, { 1, 6, 0 } } }, + { { { 3, 0, 2 }, { 0, 8, 0 } } }, + { { { 3, 0, 1 }, { 0, 9, 1 } } }, + { { { 3, 0, 0 }, { 0, 9, 0 } } }, + { { { 3, 0, 1 }, { 0, 9, 1 } } }, + { { { 3, 0, 2 }, { 0, 10, 1 } } }, + { { { 3, 0, 3 }, { 0, 10, 0 } } }, + { { { 3, 0, 4 }, { 2, 7, 1 } } }, + { { { 4, 0, 4 }, { 2, 7, 0 } } }, + { { { 4, 0, 3 }, { 0, 11, 0 } } }, + { { { 4, 0, 2 }, { 1, 10, 1 } } }, + { { { 4, 0, 1 }, { 1, 10, 0 } } }, + { { { 4, 0, 0 }, { 0, 12, 0 } } }, + { { { 4, 0, 1 }, { 0, 13, 1 } } }, + { { { 4, 0, 2 }, { 0, 13, 0 } } }, + { { { 4, 0, 3 }, { 0, 13, 1 } } }, + { { { 4, 0, 4 }, { 0, 14, 1 } } }, + { { { 5, 0, 3 }, { 0, 14, 0 } } }, + { { { 5, 0, 2 }, { 2, 11, 1 } } }, + { { { 5, 0, 1 }, { 2, 11, 0 } } }, + { { { 5, 0, 0 }, { 0, 15, 0 } } }, + { { { 5, 0, 1 }, { 1, 14, 1 } } }, + { { { 5, 0, 2 }, { 1, 14, 0 } } }, + { { { 5, 0, 3 }, { 0, 16, 0 } } }, + { { { 5, 0, 4 }, { 0, 17, 1 } } }, + { { { 6, 0, 3 }, { 0, 17, 0 } } }, + { { { 6, 0, 2 }, { 0, 17, 1 } } }, + { { { 6, 0, 1 }, { 0, 18, 1 } } }, + { { { 6, 0, 0 }, { 0, 18, 0 } } }, + { { { 6, 0, 1 }, { 2, 15, 1 } } }, + { { { 6, 0, 2 }, { 2, 15, 0 } } }, + { { { 6, 0, 3 }, { 0, 19, 0 } } }, + { { { 6, 0, 4 }, { 1, 18, 1 } } }, + { { { 7, 0, 3 }, { 1, 18, 0 } } }, + { { { 7, 0, 2 }, { 0, 20, 0 } } }, + { { { 7, 0, 1 }, { 0, 21, 1 } } }, + { { { 7, 0, 0 }, { 0, 21, 0 } } }, + { { { 7, 0, 1 }, { 0, 21, 1 } } }, + { { { 7, 0, 2 }, { 0, 22, 1 } } }, + { { { 7, 0, 3 }, { 0, 22, 0 } } }, + { { { 7, 0, 4 }, { 2, 19, 1 } } }, + { { { 8, 0, 4 }, { 2, 19, 0 } } }, + { { { 8, 0, 3 }, { 0, 23, 0 } } }, + { { { 8, 0, 2 }, { 1, 22, 1 } } }, + { { { 8, 0, 1 }, { 1, 22, 0 } } }, + { { { 8, 0, 0 }, { 0, 24, 0 } } }, + { { { 8, 0, 1 }, { 0, 25, 1 } } }, + { { { 8, 0, 2 }, { 0, 25, 0 } } }, + { { { 8, 0, 3 }, { 0, 25, 1 } } }, + { { { 8, 0, 4 }, { 0, 26, 1 } } }, + { { { 9, 0, 3 }, { 0, 26, 0 } } }, + { { { 9, 0, 2 }, { 2, 23, 1 } } }, + { { { 9, 0, 1 }, { 2, 23, 0 } } }, + { { { 9, 0, 0 }, { 0, 27, 0 } } }, + { { { 9, 0, 1 }, { 1, 26, 1 } } }, + { { { 9, 0, 2 }, { 1, 26, 0 } } }, + { { { 9, 0, 3 }, { 0, 28, 0 } } }, + { { { 9, 0, 4 }, { 0, 29, 1 } } }, + { { { 10, 0, 3 }, { 0, 29, 0 } } }, + { { { 10, 0, 2 }, { 0, 29, 1 } } }, + { { { 10, 0, 1 }, { 0, 30, 1 } } }, + { { { 10, 0, 0 }, { 0, 30, 0 } } }, + { { { 10, 0, 1 }, { 2, 27, 1 } } }, + { { { 10, 0, 2 }, { 2, 27, 0 } } }, + { { { 10, 0, 3 }, { 0, 31, 0 } } }, + { { { 10, 0, 4 }, { 1, 30, 1 } } }, + { { { 11, 0, 3 }, { 1, 30, 0 } } }, + { { { 11, 0, 2 }, { 4, 24, 0 } } }, + { { { 11, 0, 1 }, { 1, 31, 1 } } }, + { { { 11, 0, 0 }, { 1, 31, 0 } } }, + { { { 11, 0, 1 }, { 1, 31, 1 } } }, + { { { 11, 0, 2 }, { 2, 30, 1 } } }, + { { { 11, 0, 3 }, { 2, 30, 0 } } }, + { { { 11, 0, 4 }, { 2, 31, 1 } } }, + { { { 12, 0, 4 }, { 2, 31, 0 } } }, + { { { 12, 0, 3 }, { 4, 27, 0 } } }, + { { { 12, 0, 2 }, { 3, 30, 1 } } }, + { { { 12, 0, 1 }, { 3, 30, 0 } } }, + { { { 12, 0, 0 }, { 4, 28, 0 } } }, + { { { 12, 0, 1 }, { 3, 31, 1 } } }, + { { { 12, 0, 2 }, { 3, 31, 0 } } }, + { { { 12, 0, 3 }, { 3, 31, 1 } } }, + { { { 12, 0, 4 }, { 4, 30, 1 } } }, + { { { 13, 0, 3 }, { 4, 30, 0 } } }, + { { { 13, 0, 2 }, { 6, 27, 1 } } }, + { { { 13, 0, 1 }, { 6, 27, 0 } } }, + { { { 13, 0, 0 }, { 4, 31, 0 } } }, + { { { 13, 0, 1 }, { 5, 30, 1 } } }, + { { { 13, 0, 2 }, { 5, 30, 0 } } }, + { { { 13, 0, 3 }, { 8, 24, 0 } } }, + { { { 13, 0, 4 }, { 5, 31, 1 } } }, + { { { 14, 0, 3 }, { 5, 31, 0 } } }, + { { { 14, 0, 2 }, { 5, 31, 1 } } }, + { { { 14, 0, 1 }, { 6, 30, 1 } } }, + { { { 14, 0, 0 }, { 6, 30, 0 } } }, + { { { 14, 0, 1 }, { 6, 31, 1 } } }, + { { { 14, 0, 2 }, { 6, 31, 0 } } }, + { { { 14, 0, 3 }, { 8, 27, 0 } } }, + { { { 14, 0, 4 }, { 7, 30, 1 } } }, + { { { 15, 0, 3 }, { 7, 30, 0 } } }, + { { { 15, 0, 2 }, { 8, 28, 0 } } }, + { { { 15, 0, 1 }, { 7, 31, 1 } } }, + { { { 15, 0, 0 }, { 7, 31, 0 } } }, + { { { 15, 0, 1 }, { 7, 31, 1 } } }, + { { { 15, 0, 2 }, { 8, 30, 1 } } }, + { { { 15, 0, 3 }, { 8, 30, 0 } } }, + { { { 15, 0, 4 }, { 10, 27, 1 } } }, + { { { 16, 0, 4 }, { 10, 27, 0 } } }, + { { { 16, 0, 3 }, { 8, 31, 0 } } }, + { { { 16, 0, 2 }, { 9, 30, 1 } } }, + { { { 16, 0, 1 }, { 9, 30, 0 } } }, + { { { 16, 0, 0 }, { 12, 24, 0 } } }, + { { { 16, 0, 1 }, { 9, 31, 1 } } }, + { { { 16, 0, 2 }, { 9, 31, 0 } } }, + { { { 16, 0, 3 }, { 9, 31, 1 } } }, + { { { 16, 0, 4 }, { 10, 30, 1 } } }, + { { { 17, 0, 3 }, { 10, 30, 0 } } }, + { { { 17, 0, 2 }, { 10, 31, 1 } } }, + { { { 17, 0, 1 }, { 10, 31, 0 } } }, + { { { 17, 0, 0 }, { 12, 27, 0 } } }, + { { { 17, 0, 1 }, { 11, 30, 1 } } }, + { { { 17, 0, 2 }, { 11, 30, 0 } } }, + { { { 17, 0, 3 }, { 12, 28, 0 } } }, + { { { 17, 0, 4 }, { 11, 31, 1 } } }, + { { { 18, 0, 3 }, { 11, 31, 0 } } }, + { { { 18, 0, 2 }, { 11, 31, 1 } } }, + { { { 18, 0, 1 }, { 12, 30, 1 } } }, + { { { 18, 0, 0 }, { 12, 30, 0 } } }, + { { { 18, 0, 1 }, { 14, 27, 1 } } }, + { { { 18, 0, 2 }, { 14, 27, 0 } } }, + { { { 18, 0, 3 }, { 12, 31, 0 } } }, + { { { 18, 0, 4 }, { 13, 30, 1 } } }, + { { { 19, 0, 3 }, { 13, 30, 0 } } }, + { { { 19, 0, 2 }, { 16, 24, 0 } } }, + { { { 19, 0, 1 }, { 13, 31, 1 } } }, + { { { 19, 0, 0 }, { 13, 31, 0 } } }, + { { { 19, 0, 1 }, { 13, 31, 1 } } }, + { { { 19, 0, 2 }, { 14, 30, 1 } } }, + { { { 19, 0, 3 }, { 14, 30, 0 } } }, + { { { 19, 0, 4 }, { 14, 31, 1 } } }, + { { { 20, 0, 4 }, { 14, 31, 0 } } }, + { { { 20, 0, 3 }, { 16, 27, 0 } } }, + { { { 20, 0, 2 }, { 15, 30, 1 } } }, + { { { 20, 0, 1 }, { 15, 30, 0 } } }, + { { { 20, 0, 0 }, { 16, 28, 0 } } }, + { { { 20, 0, 1 }, { 15, 31, 1 } } }, + { { { 20, 0, 2 }, { 15, 31, 0 } } }, + { { { 20, 0, 3 }, { 15, 31, 1 } } }, + { { { 20, 0, 4 }, { 16, 30, 1 } } }, + { { { 21, 0, 3 }, { 16, 30, 0 } } }, + { { { 21, 0, 2 }, { 18, 27, 1 } } }, + { { { 21, 0, 1 }, { 18, 27, 0 } } }, + { { { 21, 0, 0 }, { 16, 31, 0 } } }, + { { { 21, 0, 1 }, { 17, 30, 1 } } }, + { { { 21, 0, 2 }, { 17, 30, 0 } } }, + { { { 21, 0, 3 }, { 20, 24, 0 } } }, + { { { 21, 0, 4 }, { 17, 31, 1 } } }, + { { { 22, 0, 3 }, { 17, 31, 0 } } }, + { { { 22, 0, 2 }, { 17, 31, 1 } } }, + { { { 22, 0, 1 }, { 18, 30, 1 } } }, + { { { 22, 0, 0 }, { 18, 30, 0 } } }, + { { { 22, 0, 1 }, { 18, 31, 1 } } }, + { { { 22, 0, 2 }, { 18, 31, 0 } } }, + { { { 22, 0, 3 }, { 20, 27, 0 } } }, + { { { 22, 0, 4 }, { 19, 30, 1 } } }, + { { { 23, 0, 3 }, { 19, 30, 0 } } }, + { { { 23, 0, 2 }, { 20, 28, 0 } } }, + { { { 23, 0, 1 }, { 19, 31, 1 } } }, + { { { 23, 0, 0 }, { 19, 31, 0 } } }, + { { { 23, 0, 1 }, { 19, 31, 1 } } }, + { { { 23, 0, 2 }, { 20, 30, 1 } } }, + { { { 23, 0, 3 }, { 20, 30, 0 } } }, + { { { 23, 0, 4 }, { 22, 27, 1 } } }, + { { { 24, 0, 4 }, { 22, 27, 0 } } }, + { { { 24, 0, 3 }, { 20, 31, 0 } } }, + { { { 24, 0, 2 }, { 21, 30, 1 } } }, + { { { 24, 0, 1 }, { 21, 30, 0 } } }, + { { { 24, 0, 0 }, { 24, 24, 0 } } }, + { { { 24, 0, 1 }, { 21, 31, 1 } } }, + { { { 24, 0, 2 }, { 21, 31, 0 } } }, + { { { 24, 0, 3 }, { 21, 31, 1 } } }, + { { { 24, 0, 4 }, { 22, 30, 1 } } }, + { { { 25, 0, 3 }, { 22, 30, 0 } } }, + { { { 25, 0, 2 }, { 22, 31, 1 } } }, + { { { 25, 0, 1 }, { 22, 31, 0 } } }, + { { { 25, 0, 0 }, { 24, 27, 0 } } }, + { { { 25, 0, 1 }, { 23, 30, 1 } } }, + { { { 25, 0, 2 }, { 23, 30, 0 } } }, + { { { 25, 0, 3 }, { 24, 28, 0 } } }, + { { { 25, 0, 4 }, { 23, 31, 1 } } }, + { { { 26, 0, 3 }, { 23, 31, 0 } } }, + { { { 26, 0, 2 }, { 23, 31, 1 } } }, + { { { 26, 0, 1 }, { 24, 30, 1 } } }, + { { { 26, 0, 0 }, { 24, 30, 0 } } }, + { { { 26, 0, 1 }, { 26, 27, 1 } } }, + { { { 26, 0, 2 }, { 26, 27, 0 } } }, + { { { 26, 0, 3 }, { 24, 31, 0 } } }, + { { { 26, 0, 4 }, { 25, 30, 1 } } }, + { { { 27, 0, 3 }, { 25, 30, 0 } } }, + { { { 27, 0, 2 }, { 28, 24, 0 } } }, + { { { 27, 0, 1 }, { 25, 31, 1 } } }, + { { { 27, 0, 0 }, { 25, 31, 0 } } }, + { { { 27, 0, 1 }, { 25, 31, 1 } } }, + { { { 27, 0, 2 }, { 26, 30, 1 } } }, + { { { 27, 0, 3 }, { 26, 30, 0 } } }, + { { { 27, 0, 4 }, { 26, 31, 1 } } }, + { { { 28, 0, 4 }, { 26, 31, 0 } } }, + { { { 28, 0, 3 }, { 28, 27, 0 } } }, + { { { 28, 0, 2 }, { 27, 30, 1 } } }, + { { { 28, 0, 1 }, { 27, 30, 0 } } }, + { { { 28, 0, 0 }, { 28, 28, 0 } } }, + { { { 28, 0, 1 }, { 27, 31, 1 } } }, + { { { 28, 0, 2 }, { 27, 31, 0 } } }, + { { { 28, 0, 3 }, { 27, 31, 1 } } }, + { { { 28, 0, 4 }, { 28, 30, 1 } } }, + { { { 29, 0, 3 }, { 28, 30, 0 } } }, + { { { 29, 0, 2 }, { 30, 27, 1 } } }, + { { { 29, 0, 1 }, { 30, 27, 0 } } }, + { { { 29, 0, 0 }, { 28, 31, 0 } } }, + { { { 29, 0, 1 }, { 29, 30, 1 } } }, + { { { 29, 0, 2 }, { 29, 30, 0 } } }, + { { { 29, 0, 3 }, { 29, 30, 1 } } }, + { { { 29, 0, 4 }, { 29, 31, 1 } } }, + { { { 30, 0, 3 }, { 29, 31, 0 } } }, + { { { 30, 0, 2 }, { 29, 31, 1 } } }, + { { { 30, 0, 1 }, { 30, 30, 1 } } }, + { { { 30, 0, 0 }, { 30, 30, 0 } } }, + { { { 30, 0, 1 }, { 30, 31, 1 } } }, + { { { 30, 0, 2 }, { 30, 31, 0 } } }, + { { { 30, 0, 3 }, { 30, 31, 1 } } }, + { { { 30, 0, 4 }, { 31, 30, 1 } } }, + { { { 31, 0, 3 }, { 31, 30, 0 } } }, + { { { 31, 0, 2 }, { 31, 30, 1 } } }, + { { { 31, 0, 1 }, { 31, 31, 1 } } }, + { { { 31, 0, 0 }, { 31, 31, 0 } } } +}; + +static SingleColourLookup const lookup_6_4[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 1, 0 } } }, + { { { 0, 0, 2 }, { 0, 2, 0 } } }, + { { { 1, 0, 1 }, { 0, 3, 1 } } }, + { { { 1, 0, 0 }, { 0, 3, 0 } } }, + { { { 1, 0, 1 }, { 0, 4, 0 } } }, + { { { 1, 0, 2 }, { 0, 5, 0 } } }, + { { { 2, 0, 1 }, { 0, 6, 1 } } }, + { { { 2, 0, 0 }, { 0, 6, 0 } } }, + { { { 2, 0, 1 }, { 0, 7, 0 } } }, + { { { 2, 0, 2 }, { 0, 8, 0 } } }, + { { { 3, 0, 1 }, { 0, 9, 1 } } }, + { { { 3, 0, 0 }, { 0, 9, 0 } } }, + { { { 3, 0, 1 }, { 0, 10, 0 } } }, + { { { 3, 0, 2 }, { 0, 11, 0 } } }, + { { { 4, 0, 1 }, { 0, 12, 1 } } }, + { { { 4, 0, 0 }, { 0, 12, 0 } } }, + { { { 4, 0, 1 }, { 0, 13, 0 } } }, + { { { 4, 0, 2 }, { 0, 14, 0 } } }, + { { { 5, 0, 1 }, { 0, 15, 1 } } }, + { { { 5, 0, 0 }, { 0, 15, 0 } } }, + { { { 5, 0, 1 }, { 0, 16, 0 } } }, + { { { 5, 0, 2 }, { 1, 15, 0 } } }, + { { { 6, 0, 1 }, { 0, 17, 0 } } }, + { { { 6, 0, 0 }, { 0, 18, 0 } } }, + { { { 6, 0, 1 }, { 0, 19, 0 } } }, + { { { 6, 0, 2 }, { 3, 14, 0 } } }, + { { { 7, 0, 1 }, { 0, 20, 0 } } }, + { { { 7, 0, 0 }, { 0, 21, 0 } } }, + { { { 7, 0, 1 }, { 0, 22, 0 } } }, + { { { 7, 0, 2 }, { 4, 15, 0 } } }, + { { { 8, 0, 1 }, { 0, 23, 0 } } }, + { { { 8, 0, 0 }, { 0, 24, 0 } } }, + { { { 8, 0, 1 }, { 0, 25, 0 } } }, + { { { 8, 0, 2 }, { 6, 14, 0 } } }, + { { { 9, 0, 1 }, { 0, 26, 0 } } }, + { { { 9, 0, 0 }, { 0, 27, 0 } } }, + { { { 9, 0, 1 }, { 0, 28, 0 } } }, + { { { 9, 0, 2 }, { 7, 15, 0 } } }, + { { { 10, 0, 1 }, { 0, 29, 0 } } }, + { { { 10, 0, 0 }, { 0, 30, 0 } } }, + { { { 10, 0, 1 }, { 0, 31, 0 } } }, + { { { 10, 0, 2 }, { 9, 14, 0 } } }, + { { { 11, 0, 1 }, { 0, 32, 0 } } }, + { { { 11, 0, 0 }, { 0, 33, 0 } } }, + { { { 11, 0, 1 }, { 2, 30, 0 } } }, + { { { 11, 0, 2 }, { 0, 34, 0 } } }, + { { { 12, 0, 1 }, { 0, 35, 0 } } }, + { { { 12, 0, 0 }, { 0, 36, 0 } } }, + { { { 12, 0, 1 }, { 3, 31, 0 } } }, + { { { 12, 0, 2 }, { 0, 37, 0 } } }, + { { { 13, 0, 1 }, { 0, 38, 0 } } }, + { { { 13, 0, 0 }, { 0, 39, 0 } } }, + { { { 13, 0, 1 }, { 5, 30, 0 } } }, + { { { 13, 0, 2 }, { 0, 40, 0 } } }, + { { { 14, 0, 1 }, { 0, 41, 0 } } }, + { { { 14, 0, 0 }, { 0, 42, 0 } } }, + { { { 14, 0, 1 }, { 6, 31, 0 } } }, + { { { 14, 0, 2 }, { 0, 43, 0 } } }, + { { { 15, 0, 1 }, { 0, 44, 0 } } }, + { { { 15, 0, 0 }, { 0, 45, 0 } } }, + { { { 15, 0, 1 }, { 8, 30, 0 } } }, + { { { 15, 0, 2 }, { 0, 46, 0 } } }, + { { { 16, 0, 2 }, { 0, 47, 0 } } }, + { { { 16, 0, 1 }, { 1, 46, 0 } } }, + { { { 16, 0, 0 }, { 0, 48, 0 } } }, + { { { 16, 0, 1 }, { 0, 49, 0 } } }, + { { { 16, 0, 2 }, { 0, 50, 0 } } }, + { { { 17, 0, 1 }, { 2, 47, 0 } } }, + { { { 17, 0, 0 }, { 0, 51, 0 } } }, + { { { 17, 0, 1 }, { 0, 52, 0 } } }, + { { { 17, 0, 2 }, { 0, 53, 0 } } }, + { { { 18, 0, 1 }, { 4, 46, 0 } } }, + { { { 18, 0, 0 }, { 0, 54, 0 } } }, + { { { 18, 0, 1 }, { 0, 55, 0 } } }, + { { { 18, 0, 2 }, { 0, 56, 0 } } }, + { { { 19, 0, 1 }, { 5, 47, 0 } } }, + { { { 19, 0, 0 }, { 0, 57, 0 } } }, + { { { 19, 0, 1 }, { 0, 58, 0 } } }, + { { { 19, 0, 2 }, { 0, 59, 0 } } }, + { { { 20, 0, 1 }, { 7, 46, 0 } } }, + { { { 20, 0, 0 }, { 0, 60, 0 } } }, + { { { 20, 0, 1 }, { 0, 61, 0 } } }, + { { { 20, 0, 2 }, { 0, 62, 0 } } }, + { { { 21, 0, 1 }, { 8, 47, 0 } } }, + { { { 21, 0, 0 }, { 0, 63, 0 } } }, + { { { 21, 0, 1 }, { 1, 62, 0 } } }, + { { { 21, 0, 2 }, { 1, 63, 0 } } }, + { { { 22, 0, 1 }, { 10, 46, 0 } } }, + { { { 22, 0, 0 }, { 2, 62, 0 } } }, + { { { 22, 0, 1 }, { 2, 63, 0 } } }, + { { { 22, 0, 2 }, { 3, 62, 0 } } }, + { { { 23, 0, 1 }, { 11, 47, 0 } } }, + { { { 23, 0, 0 }, { 3, 63, 0 } } }, + { { { 23, 0, 1 }, { 4, 62, 0 } } }, + { { { 23, 0, 2 }, { 4, 63, 0 } } }, + { { { 24, 0, 1 }, { 13, 46, 0 } } }, + { { { 24, 0, 0 }, { 5, 62, 0 } } }, + { { { 24, 0, 1 }, { 5, 63, 0 } } }, + { { { 24, 0, 2 }, { 6, 62, 0 } } }, + { { { 25, 0, 1 }, { 14, 47, 0 } } }, + { { { 25, 0, 0 }, { 6, 63, 0 } } }, + { { { 25, 0, 1 }, { 7, 62, 0 } } }, + { { { 25, 0, 2 }, { 7, 63, 0 } } }, + { { { 26, 0, 1 }, { 16, 45, 0 } } }, + { { { 26, 0, 0 }, { 8, 62, 0 } } }, + { { { 26, 0, 1 }, { 8, 63, 0 } } }, + { { { 26, 0, 2 }, { 9, 62, 0 } } }, + { { { 27, 0, 1 }, { 16, 48, 0 } } }, + { { { 27, 0, 0 }, { 9, 63, 0 } } }, + { { { 27, 0, 1 }, { 10, 62, 0 } } }, + { { { 27, 0, 2 }, { 10, 63, 0 } } }, + { { { 28, 0, 1 }, { 16, 51, 0 } } }, + { { { 28, 0, 0 }, { 11, 62, 0 } } }, + { { { 28, 0, 1 }, { 11, 63, 0 } } }, + { { { 28, 0, 2 }, { 12, 62, 0 } } }, + { { { 29, 0, 1 }, { 16, 54, 0 } } }, + { { { 29, 0, 0 }, { 12, 63, 0 } } }, + { { { 29, 0, 1 }, { 13, 62, 0 } } }, + { { { 29, 0, 2 }, { 13, 63, 0 } } }, + { { { 30, 0, 1 }, { 16, 57, 0 } } }, + { { { 30, 0, 0 }, { 14, 62, 0 } } }, + { { { 30, 0, 1 }, { 14, 63, 0 } } }, + { { { 30, 0, 2 }, { 15, 62, 0 } } }, + { { { 31, 0, 1 }, { 16, 60, 0 } } }, + { { { 31, 0, 0 }, { 15, 63, 0 } } }, + { { { 31, 0, 1 }, { 24, 46, 0 } } }, + { { { 31, 0, 2 }, { 16, 62, 0 } } }, + { { { 32, 0, 2 }, { 16, 63, 0 } } }, + { { { 32, 0, 1 }, { 17, 62, 0 } } }, + { { { 32, 0, 0 }, { 25, 47, 0 } } }, + { { { 32, 0, 1 }, { 17, 63, 0 } } }, + { { { 32, 0, 2 }, { 18, 62, 0 } } }, + { { { 33, 0, 1 }, { 18, 63, 0 } } }, + { { { 33, 0, 0 }, { 27, 46, 0 } } }, + { { { 33, 0, 1 }, { 19, 62, 0 } } }, + { { { 33, 0, 2 }, { 19, 63, 0 } } }, + { { { 34, 0, 1 }, { 20, 62, 0 } } }, + { { { 34, 0, 0 }, { 28, 47, 0 } } }, + { { { 34, 0, 1 }, { 20, 63, 0 } } }, + { { { 34, 0, 2 }, { 21, 62, 0 } } }, + { { { 35, 0, 1 }, { 21, 63, 0 } } }, + { { { 35, 0, 0 }, { 30, 46, 0 } } }, + { { { 35, 0, 1 }, { 22, 62, 0 } } }, + { { { 35, 0, 2 }, { 22, 63, 0 } } }, + { { { 36, 0, 1 }, { 23, 62, 0 } } }, + { { { 36, 0, 0 }, { 31, 47, 0 } } }, + { { { 36, 0, 1 }, { 23, 63, 0 } } }, + { { { 36, 0, 2 }, { 24, 62, 0 } } }, + { { { 37, 0, 1 }, { 24, 63, 0 } } }, + { { { 37, 0, 0 }, { 32, 47, 0 } } }, + { { { 37, 0, 1 }, { 25, 62, 0 } } }, + { { { 37, 0, 2 }, { 25, 63, 0 } } }, + { { { 38, 0, 1 }, { 26, 62, 0 } } }, + { { { 38, 0, 0 }, { 32, 50, 0 } } }, + { { { 38, 0, 1 }, { 26, 63, 0 } } }, + { { { 38, 0, 2 }, { 27, 62, 0 } } }, + { { { 39, 0, 1 }, { 27, 63, 0 } } }, + { { { 39, 0, 0 }, { 32, 53, 0 } } }, + { { { 39, 0, 1 }, { 28, 62, 0 } } }, + { { { 39, 0, 2 }, { 28, 63, 0 } } }, + { { { 40, 0, 1 }, { 29, 62, 0 } } }, + { { { 40, 0, 0 }, { 32, 56, 0 } } }, + { { { 40, 0, 1 }, { 29, 63, 0 } } }, + { { { 40, 0, 2 }, { 30, 62, 0 } } }, + { { { 41, 0, 1 }, { 30, 63, 0 } } }, + { { { 41, 0, 0 }, { 32, 59, 0 } } }, + { { { 41, 0, 1 }, { 31, 62, 0 } } }, + { { { 41, 0, 2 }, { 31, 63, 0 } } }, + { { { 42, 0, 1 }, { 32, 61, 0 } } }, + { { { 42, 0, 0 }, { 32, 62, 0 } } }, + { { { 42, 0, 1 }, { 32, 63, 0 } } }, + { { { 42, 0, 2 }, { 41, 46, 0 } } }, + { { { 43, 0, 1 }, { 33, 62, 0 } } }, + { { { 43, 0, 0 }, { 33, 63, 0 } } }, + { { { 43, 0, 1 }, { 34, 62, 0 } } }, + { { { 43, 0, 2 }, { 42, 47, 0 } } }, + { { { 44, 0, 1 }, { 34, 63, 0 } } }, + { { { 44, 0, 0 }, { 35, 62, 0 } } }, + { { { 44, 0, 1 }, { 35, 63, 0 } } }, + { { { 44, 0, 2 }, { 44, 46, 0 } } }, + { { { 45, 0, 1 }, { 36, 62, 0 } } }, + { { { 45, 0, 0 }, { 36, 63, 0 } } }, + { { { 45, 0, 1 }, { 37, 62, 0 } } }, + { { { 45, 0, 2 }, { 45, 47, 0 } } }, + { { { 46, 0, 1 }, { 37, 63, 0 } } }, + { { { 46, 0, 0 }, { 38, 62, 0 } } }, + { { { 46, 0, 1 }, { 38, 63, 0 } } }, + { { { 46, 0, 2 }, { 47, 46, 0 } } }, + { { { 47, 0, 1 }, { 39, 62, 0 } } }, + { { { 47, 0, 0 }, { 39, 63, 0 } } }, + { { { 47, 0, 1 }, { 40, 62, 0 } } }, + { { { 47, 0, 2 }, { 48, 46, 0 } } }, + { { { 48, 0, 2 }, { 40, 63, 0 } } }, + { { { 48, 0, 1 }, { 41, 62, 0 } } }, + { { { 48, 0, 0 }, { 41, 63, 0 } } }, + { { { 48, 0, 1 }, { 48, 49, 0 } } }, + { { { 48, 0, 2 }, { 42, 62, 0 } } }, + { { { 49, 0, 1 }, { 42, 63, 0 } } }, + { { { 49, 0, 0 }, { 43, 62, 0 } } }, + { { { 49, 0, 1 }, { 48, 52, 0 } } }, + { { { 49, 0, 2 }, { 43, 63, 0 } } }, + { { { 50, 0, 1 }, { 44, 62, 0 } } }, + { { { 50, 0, 0 }, { 44, 63, 0 } } }, + { { { 50, 0, 1 }, { 48, 55, 0 } } }, + { { { 50, 0, 2 }, { 45, 62, 0 } } }, + { { { 51, 0, 1 }, { 45, 63, 0 } } }, + { { { 51, 0, 0 }, { 46, 62, 0 } } }, + { { { 51, 0, 1 }, { 48, 58, 0 } } }, + { { { 51, 0, 2 }, { 46, 63, 0 } } }, + { { { 52, 0, 1 }, { 47, 62, 0 } } }, + { { { 52, 0, 0 }, { 47, 63, 0 } } }, + { { { 52, 0, 1 }, { 48, 61, 0 } } }, + { { { 52, 0, 2 }, { 48, 62, 0 } } }, + { { { 53, 0, 1 }, { 56, 47, 0 } } }, + { { { 53, 0, 0 }, { 48, 63, 0 } } }, + { { { 53, 0, 1 }, { 49, 62, 0 } } }, + { { { 53, 0, 2 }, { 49, 63, 0 } } }, + { { { 54, 0, 1 }, { 58, 46, 0 } } }, + { { { 54, 0, 0 }, { 50, 62, 0 } } }, + { { { 54, 0, 1 }, { 50, 63, 0 } } }, + { { { 54, 0, 2 }, { 51, 62, 0 } } }, + { { { 55, 0, 1 }, { 59, 47, 0 } } }, + { { { 55, 0, 0 }, { 51, 63, 0 } } }, + { { { 55, 0, 1 }, { 52, 62, 0 } } }, + { { { 55, 0, 2 }, { 52, 63, 0 } } }, + { { { 56, 0, 1 }, { 61, 46, 0 } } }, + { { { 56, 0, 0 }, { 53, 62, 0 } } }, + { { { 56, 0, 1 }, { 53, 63, 0 } } }, + { { { 56, 0, 2 }, { 54, 62, 0 } } }, + { { { 57, 0, 1 }, { 62, 47, 0 } } }, + { { { 57, 0, 0 }, { 54, 63, 0 } } }, + { { { 57, 0, 1 }, { 55, 62, 0 } } }, + { { { 57, 0, 2 }, { 55, 63, 0 } } }, + { { { 58, 0, 1 }, { 56, 62, 1 } } }, + { { { 58, 0, 0 }, { 56, 62, 0 } } }, + { { { 58, 0, 1 }, { 56, 63, 0 } } }, + { { { 58, 0, 2 }, { 57, 62, 0 } } }, + { { { 59, 0, 1 }, { 57, 63, 1 } } }, + { { { 59, 0, 0 }, { 57, 63, 0 } } }, + { { { 59, 0, 1 }, { 58, 62, 0 } } }, + { { { 59, 0, 2 }, { 58, 63, 0 } } }, + { { { 60, 0, 1 }, { 59, 62, 1 } } }, + { { { 60, 0, 0 }, { 59, 62, 0 } } }, + { { { 60, 0, 1 }, { 59, 63, 0 } } }, + { { { 60, 0, 2 }, { 60, 62, 0 } } }, + { { { 61, 0, 1 }, { 60, 63, 1 } } }, + { { { 61, 0, 0 }, { 60, 63, 0 } } }, + { { { 61, 0, 1 }, { 61, 62, 0 } } }, + { { { 61, 0, 2 }, { 61, 63, 0 } } }, + { { { 62, 0, 1 }, { 62, 62, 1 } } }, + { { { 62, 0, 0 }, { 62, 62, 0 } } }, + { { { 62, 0, 1 }, { 62, 63, 0 } } }, + { { { 62, 0, 2 }, { 63, 62, 0 } } }, + { { { 63, 0, 1 }, { 63, 63, 1 } } }, + { { { 63, 0, 0 }, { 63, 63, 0 } } } +}; diff --git a/src/DdsFileType/Squish/squish-Info.plist b/src/DdsFileType/Squish/squish-Info.plist new file mode 100644 index 0000000..5cb05e0 --- /dev/null +++ b/src/DdsFileType/Squish/squish-Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.sjbrown.squish + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/src/DdsFileType/Squish/squish.cpp b/src/DdsFileType/Squish/squish.cpp new file mode 100644 index 0000000..cc0482b --- /dev/null +++ b/src/DdsFileType/Squish/squish.cpp @@ -0,0 +1,293 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include + +#ifdef SQUISH_USE_OPENMP +#include +#endif + +#include "colourset.h" +#include "maths.h" +#include "rangefit.h" +#include "clusterfit.h" +#include "colourblock.h" +#include "alpha.h" +#include "singlecolourfit.h" + +namespace squish { + +static int FixFlags( int flags ) +{ + // grab the flag bits + int method = flags & ( kDxt1 | kDxt3 | kDxt5 ); + int fit = flags & ( kColourIterativeClusterFit | kColourClusterFit | kColourRangeFit ); + int metric = flags & ( kColourMetricPerceptual | kColourMetricUniform ); + int extra = flags & kWeightColourByAlpha; + + // set defaults + if( method != kDxt3 && method != kDxt5 ) + method = kDxt1; + if( ( fit != kColourRangeFit ) && ( fit != kColourIterativeClusterFit ) ) + fit = kColourClusterFit; + if( metric != kColourMetricUniform ) + metric = kColourMetricPerceptual; + + // done + return method | fit | metric | extra; +} + +void Compress( u8 const* rgba, void* block, int flags ) +{ + // compress with full mask + CompressMasked( rgba, 0xffff, block, flags ); +} + +void CompressMasked( u8 const* rgba, int mask, void* block, int flags ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // get the block locations + void* colourBlock = block; + void* alphaBock = block; + if( ( flags & ( kDxt3 | kDxt5 ) ) != 0 ) + colourBlock = reinterpret_cast< u8* >( block ) + 8; + + // create the minimal point set + ColourSet colours( rgba, mask, flags ); + + // check the compression type and compress colour + if( colours.GetCount() == 1 ) + { + // always do a single colour fit + SingleColourFit fit( &colours, flags ); + fit.Compress( colourBlock ); + } + else if( ( flags & kColourRangeFit ) != 0 || colours.GetCount() == 0 ) + { + // do a range fit + RangeFit fit( &colours, flags ); + fit.Compress( colourBlock ); + } + else + { + // default to a cluster fit (could be iterative or not) + ClusterFit fit( &colours, flags ); + fit.Compress( colourBlock ); + } + + // compress alpha separately if necessary + if( ( flags & kDxt3 ) != 0 ) + CompressAlphaDxt3( rgba, mask, alphaBock ); + else if( ( flags & kDxt5 ) != 0 ) + CompressAlphaDxt5( rgba, mask, alphaBock ); +} + +void Decompress( u8* rgba, void const* block, int flags ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // get the block locations + void const* colourBlock = block; + void const* alphaBock = block; + if( ( flags & ( kDxt3 | kDxt5 ) ) != 0 ) + colourBlock = reinterpret_cast< u8 const* >( block ) + 8; + + // decompress colour + DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 ); + + // decompress alpha separately if necessary + if( ( flags & kDxt3 ) != 0 ) + DecompressAlphaDxt3( rgba, alphaBock ); + else if( ( flags & kDxt5 ) != 0 ) + DecompressAlphaDxt5( rgba, alphaBock ); +} + +int GetStorageRequirements( int width, int height, int flags ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // compute the storage requirements + int blockcount = ( ( width + 3 )/4 ) * ( ( height + 3 )/4 ); + int blocksize = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16; + return blockcount*blocksize; +} + +void CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, ProgressFn progressFn ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // initialise the block output + u8* targetBlock = reinterpret_cast< u8* >( blocks ); + int bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16; + + int progress = 0; + + if (progressFn != NULL) + { + progressFn(0, height); + } + + // loop over blocks +#ifdef SQUISH_USE_OPENMP + #pragma omp parallel for shared(progress) +#endif + for( int y = 0; y < height; y += 4 ) + { + for( int x = 0; x < width; x += 4 ) + { + // build the 4x4 block of pixels + u8 sourceRgba[16*4]; + u8* targetPixel = sourceRgba; + int mask = 0; + for( int py = 0; py < 4; ++py ) + { + for( int px = 0; px < 4; ++px ) + { + // get the source pixel in the image + int sx = x + px; + int sy = y + py; + + // enable if we're in the image + if( sx < width && sy < height ) + { + // copy the rgba value + u8 const* sourcePixel = rgba + 4*( width*sy + sx ); + for( int i = 0; i < 4; ++i ) + *targetPixel++ = *sourcePixel++; + + // enable this pixel + mask |= ( 1 << ( 4*py + px ) ); + } + else + { + // skip this pixel as its outside the image + targetPixel += 4; + } + } + } + + // compress it into the output + int blockNum = (((width + 3) / 4) * (y / 4)) + (x / 4); + u8* outputBlock = targetBlock + (bytesPerBlock * blockNum); + CompressMasked( sourceRgba, mask, outputBlock, flags ); + } + +#ifdef SQUISH_USE_OPENMP + #pragma omp atomic +#endif + progress += 4; + + if (progressFn != NULL) + { + progressFn(progress, height); + } + } + + if (progressFn != NULL) + { + progressFn(height, height); + } +} + +void DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags, ProgressFn progressFn ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // initialise the block input + u8 const* source = reinterpret_cast< u8 const* >( blocks ); + int bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16; + + int progress = 0; + + if (progressFn != NULL) + { + progressFn(height, height); + } + + // loop over blocks +#ifdef SQUISH_USE_OPENMP + #pragma omp parallel for shared(progress) +#endif + for( int y = 0; y < height; y += 4 ) + { + for( int x = 0; x < width; x += 4 ) + { + // decompress the block + int blockNum = (((width + 3) / 4) * (y / 4)) + (x / 4); + u8 const* sourceBlock = source + (bytesPerBlock * blockNum); + + u8 targetRgba[4*16]; + Decompress( targetRgba, sourceBlock, flags ); + + // write the decompressed pixels to the correct image locations + u8 const* sourcePixel = targetRgba; + for( int py = 0; py < 4; ++py ) + { + for( int px = 0; px < 4; ++px ) + { + // get the target location + int sx = x + px; + int sy = y + py; + if( sx < width && sy < height ) + { + u8* targetPixel = rgba + 4*( width*sy + sx ); + + // copy the rgba value + for( int i = 0; i < 4; ++i ) + *targetPixel++ = *sourcePixel++; + } + else + { + // skip this pixel as its outside the image + sourcePixel += 4; + } + } + } + +#ifdef SQUISH_USE_OPENMP + #pragma omp atomic +#endif + progress += 4; + + if (progressFn != NULL) + { + progressFn(progress, height); + } + } + } + + if (progressFn != NULL) + { + progressFn(height, height); + } +} + +} // namespace squish diff --git a/src/DdsFileType/Squish/squish.h b/src/DdsFileType/Squish/squish.h new file mode 100644 index 0000000..f9d1aec --- /dev/null +++ b/src/DdsFileType/Squish/squish.h @@ -0,0 +1,254 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_H +#define SQUISH_H + +#ifdef _MSC_VER +#pragma strict_gs_check(on) +#endif + +//! All squish API functions live in this namespace. +namespace squish { + +// Function pointer for reporting progress +typedef void (__stdcall *ProgressFn)(int workDone, int workTotal); + +// ----------------------------------------------------------------------------- + +//! Typedef a quantity that is a single unsigned byte. +typedef unsigned char u8; + +// ----------------------------------------------------------------------------- + +enum +{ + //! Use DXT1 compression. + kDxt1 = ( 1 << 0 ), + + //! Use DXT3 compression. + kDxt3 = ( 1 << 1 ), + + //! Use DXT5 compression. + kDxt5 = ( 1 << 2 ), + + //! Use a very slow but very high quality colour compressor. + kColourIterativeClusterFit = ( 1 << 8 ), + + //! Use a slow but high quality colour compressor (the default). + kColourClusterFit = ( 1 << 3 ), + + //! Use a fast but low quality colour compressor. + kColourRangeFit = ( 1 << 4 ), + + //! Use a perceptual metric for colour error (the default). + kColourMetricPerceptual = ( 1 << 5 ), + + //! Use a uniform metric for colour error. + kColourMetricUniform = ( 1 << 6 ), + + //! Weight the colour by alpha during cluster fit (disabled by default). + kWeightColourByAlpha = ( 1 << 7 ) +}; + +// ----------------------------------------------------------------------------- + +/*! @brief Compresses a 4x4 block of pixels. + + @param rgba The rgba values of the 16 source pixels. + @param block Storage for the compressed DXT block. + @param flags Compression flags. + + The source pixels should be presented as a contiguous array of 16 rgba + values, with each component as 1 byte each. In memory this should be: + + { r1, g1, b1, a1, .... , r16, g16, b16, a16 } + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. When using DXT1 + compression, 8 bytes of storage are required for the compressed DXT block. + DXT3 and DXT5 compression require 16 bytes of storage per block. + + The flags parameter can also specify a preferred colour compressor and + colour error metric to use when fitting the RGB components of the data. + Possible colour compressors are: kColourClusterFit (the default), + kColourRangeFit or kColourIterativeClusterFit. Possible colour error metrics + are: kColourMetricPerceptual (the default) or kColourMetricUniform. If no + flags are specified in any particular category then the default will be + used. Unknown flags are ignored. + + When using kColourClusterFit, an additional flag can be specified to + weight the colour of each pixel by its alpha value. For images that are + rendered using alpha blending, this can significantly increase the + perceived quality. +*/ +void Compress( u8 const* rgba, void* block, int flags ); + +// ----------------------------------------------------------------------------- + +/*! @brief Compresses a 4x4 block of pixels. + + @param rgba The rgba values of the 16 source pixels. + @param mask The valid pixel mask. + @param block Storage for the compressed DXT block. + @param flags Compression flags. + + The source pixels should be presented as a contiguous array of 16 rgba + values, with each component as 1 byte each. In memory this should be: + + { r1, g1, b1, a1, .... , r16, g16, b16, a16 } + + The mask parameter enables only certain pixels within the block. The lowest + bit enables the first pixel and so on up to the 16th bit. Bits beyond the + 16th bit are ignored. Pixels that are not enabled are allowed to take + arbitrary colours in the output block. An example of how this can be used + is in the CompressImage function to disable pixels outside the bounds of + the image when the width or height is not divisible by 4. + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. When using DXT1 + compression, 8 bytes of storage are required for the compressed DXT block. + DXT3 and DXT5 compression require 16 bytes of storage per block. + + The flags parameter can also specify a preferred colour compressor and + colour error metric to use when fitting the RGB components of the data. + Possible colour compressors are: kColourClusterFit (the default), + kColourRangeFit or kColourIterativeClusterFit. Possible colour error metrics + are: kColourMetricPerceptual (the default) or kColourMetricUniform. If no + flags are specified in any particular category then the default will be + used. Unknown flags are ignored. + + When using kColourClusterFit, an additional flag can be specified to + weight the colour of each pixel by its alpha value. For images that are + rendered using alpha blending, this can significantly increase the + perceived quality. +*/ +void CompressMasked( u8 const* rgba, int mask, void* block, int flags ); + +// ----------------------------------------------------------------------------- + +/*! @brief Decompresses a 4x4 block of pixels. + + @param rgba Storage for the 16 decompressed pixels. + @param block The compressed DXT block. + @param flags Compression flags. + + The decompressed pixels will be written as a contiguous array of 16 rgba + values, with each component as 1 byte each. In memory this is: + + { r1, g1, b1, a1, .... , r16, g16, b16, a16 } + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. All other flags + are ignored. +*/ +void Decompress( u8* rgba, void const* block, int flags ); + +// ----------------------------------------------------------------------------- + +/*! @brief Computes the amount of compressed storage required. + + @param width The width of the image. + @param height The height of the image. + @param flags Compression flags. + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. All other flags + are ignored. + + Most DXT images will be a multiple of 4 in each dimension, but this + function supports arbitrary size images by allowing the outer blocks to + be only partially used. +*/ +int GetStorageRequirements( int width, int height, int flags ); + +// ----------------------------------------------------------------------------- + +/*! @brief Compresses an image in memory. + + @param rgba The pixels of the source. + @param width The width of the source image. + @param height The height of the source image. + @param blocks Storage for the compressed output. + @param flags Compression flags. + + The source pixels should be presented as a contiguous array of width*height + rgba values, with each component as 1 byte each. In memory this should be: + + { r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. When using DXT1 + compression, 8 bytes of storage are required for each compressed DXT block. + DXT3 and DXT5 compression require 16 bytes of storage per block. + + The flags parameter can also specify a preferred colour compressor and + colour error metric to use when fitting the RGB components of the data. + Possible colour compressors are: kColourClusterFit (the default), + kColourRangeFit or kColourIterativeClusterFit. Possible colour error metrics + are: kColourMetricPerceptual (the default) or kColourMetricUniform. If no + flags are specified in any particular category then the default will be + used. Unknown flags are ignored. + + When using kColourClusterFit, an additional flag can be specified to + weight the colour of each pixel by its alpha value. For images that are + rendered using alpha blending, this can significantly increase the + perceived quality. + + Internally this function calls squish::Compress for each block. To see how + much memory is required in the compressed image, use + squish::GetStorageRequirements. +*/ +void CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, ProgressFn progressFn ); + +// ----------------------------------------------------------------------------- + +/*! @brief Decompresses an image in memory. + + @param rgba Storage for the decompressed pixels. + @param width The width of the source image. + @param height The height of the source image. + @param blocks The compressed DXT blocks. + @param flags Compression flags. + + The decompressed pixels will be written as a contiguous array of width*height + 16 rgba values, with each component as 1 byte each. In memory this is: + + { r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. All other flags + are ignored. + + Internally this function calls squish::Decompress for each block. +*/ +void DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags, ProgressFn progressFn ); + +// ----------------------------------------------------------------------------- + +} // namespace squish + +#endif // ndef SQUISH_H + diff --git a/src/DdsFileType/Squish/squishinterface.cpp b/src/DdsFileType/Squish/squishinterface.cpp new file mode 100644 index 0000000..7adfde3 --- /dev/null +++ b/src/DdsFileType/Squish/squishinterface.cpp @@ -0,0 +1,47 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "squish.h" +#include "squishinterface.h" + +extern "C" +{ + void SquishInitialize( void ) + { + // This function doesn't do anything. It exists solely to ensure that the Squish DLL is loaded + // and mapped into memory at an earlier point in time. That way the error checking can be + // a bit simpler for something like the DDS Paint.NET plugin. + } + + void SquishCompressImage( char* rgba, int width, int height, void* blocks, int flags, squish::ProgressFn progressFn ) + { + squish::CompressImage( ( const squish::u8* )rgba, width, height, blocks, flags, progressFn ); + } + + void SquishDecompressImage( char* rgba, int width, int height, void* blocks, int flags, squish::ProgressFn progressFn ) + { + squish::DecompressImage( ( squish::u8* ) rgba, width, height, ( void const* )blocks, flags, progressFn ); + } +}; \ No newline at end of file diff --git a/src/DdsFileType/Squish/squishinterface.h b/src/DdsFileType/Squish/squishinterface.h new file mode 100644 index 0000000..6aabcb7 --- /dev/null +++ b/src/DdsFileType/Squish/squishinterface.h @@ -0,0 +1,38 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_INTERFACE_H +#define SQUISH_INTERFACE_H + +#include + +extern "C" +{ + __declspec( dllexport ) void SquishInitialize( void ); + __declspec( dllexport ) void SquishCompressImage( char*, int width, int height, void* blocks, int flags, squish::ProgressFn progressFn ); + __declspec( dllexport ) void SquishDecompressImage( char* rgba, int width, int height, void* blocks, int flags, squish::ProgressFn progressFn ); +} + +#endif //SQUISH_INTERFACE_H \ No newline at end of file diff --git a/src/DdsFileType/Squish/texture_compression_s3tc.txt b/src/DdsFileType/Squish/texture_compression_s3tc.txt new file mode 100644 index 0000000..f229cf3 --- /dev/null +++ b/src/DdsFileType/Squish/texture_compression_s3tc.txt @@ -0,0 +1,508 @@ +Name + + EXT_texture_compression_s3tc + +Name Strings + + GL_EXT_texture_compression_s3tc + +Contact + + Pat Brown, NVIDIA Corporation (pbrown 'at' nvidia.com) + +Status + + FINAL + +Version + + 1.1, 16 November 2001 (containing only clarifications relative to + version 1.0, dated 7 July 2000) + +Number + + 198 + +Dependencies + + OpenGL 1.1 is required. + + GL_ARB_texture_compression is required. + + This extension is written against the OpenGL 1.2.1 Specification. + +Overview + + This extension provides additional texture compression functionality + specific to S3's S3TC format (called DXTC in Microsoft's DirectX API), + subject to all the requirements and limitations described by the extension + GL_ARB_texture_compression. + + This extension supports DXT1, DXT3, and DXT5 texture compression formats. + For the DXT1 image format, this specification supports an RGB-only mode + and a special RGBA mode with single-bit "transparent" alpha. + +IP Status + + Contact S3 Incorporated (http://www.s3.com) regarding any intellectual + property issues associated with implementing this extension. + + WARNING: Vendors able to support S3TC texture compression in Direct3D + drivers do not necessarily have the right to use the same functionality in + OpenGL. + +Issues + + (1) Should DXT2 and DXT4 (premultiplied alpha) formats be supported? + + RESOLVED: No -- insufficient interest. Supporting DXT2 and DXT4 + would require some rework to the TexEnv definition (maybe add a new + base internal format RGBA_PREMULTIPLIED_ALPHA) for these formats. + Note that the EXT_texture_env_combine extension (which extends normal + TexEnv modes) can be used to support textures with premultipled alpha. + + (2) Should generic "RGB_S3TC_EXT" and "RGBA_S3TC_EXT" enums be supported + or should we use only the DXT enums? + + RESOLVED: No. A generic RGBA_S3TC_EXT is problematic because DXT3 + and DXT5 are both nominally RGBA (and DXT1 with the 1-bit alpha is + also) yet one format must be chosen up front. + + (3) Should TexSubImage support all block-aligned edits or just the minimal + functionality required by the ARB_texture_compression extension? + + RESOLVED: Allow all valid block-aligned edits. + + (4) A pre-compressed image with a DXT1 format can be used as either an + RGB_S3TC_DXT1 or an RGBA_S3TC_DXT1 image. If the image has + transparent texels, how are they treated in each format? + + RESOLVED: The renderer has to make sure that an RGB_S3TC_DXT1 format + is decoded as RGB (where alpha is effectively one for all texels), + while RGBA_S3TC_DXT1 is decoded as RGBA (where alpha is zero for all + texels with "transparent" encodings). Otherwise, the formats are + identical. + + (5) Is the encoding of the RGB components for DXT1 formats correct in this + spec? MSDN documentation does not specify an RGB color for the + "transparent" encoding. Is it really black? + + RESOLVED: Yes. The specification for the DXT1 format initially + required black, but later changed that requirement to a + recommendation. All vendors involved in the definition of this + specification support black. In addition, specifying black has a + useful behavior. + + When blending multiple texels (GL_LINEAR filtering), mixing opaque and + transparent samples is problematic. Defining a black color on + transparent texels achieves a sensible result that works like a + texture with premultiplied alpha. For example, if three opaque white + and one transparent sample is being averaged, the result would be a + 75% intensity gray (with an alpha of 75%). This is the same result on + the color channels as would be obtained using a white color, 75% + alpha, and a SRC_ALPHA blend factor. + + (6) Is the encoding of the RGB components for DXT3 and DXT5 formats + correct in this spec? MSDN documentation suggests that the RGB blocks + for DXT3 and DXT5 are decoded as described by the DXT1 format. + + RESOLVED: Yes -- this appears to be a bug in the MSDN documentation. + The specification for the DXT2-DXT5 formats require decoding using the + opaque block encoding, regardless of the relative values of "color0" + and "color1". + +New Procedures and Functions + + None. + +New Tokens + + Accepted by the parameter of TexImage2D, CopyTexImage2D, + and CompressedTexImage2DARB and the parameter of + CompressedTexSubImage2DARB: + + COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 + COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + +Additions to Chapter 2 of the OpenGL 1.2.1 Specification (OpenGL Operation) + + None. + +Additions to Chapter 3 of the OpenGL 1.2.1 Specification (Rasterization) + + Add to Table 3.16.1: Specific Compressed Internal Formats + + Compressed Internal Format Base Internal Format + ========================== ==================== + COMPRESSED_RGB_S3TC_DXT1_EXT RGB + COMPRESSED_RGBA_S3TC_DXT1_EXT RGBA + COMPRESSED_RGBA_S3TC_DXT3_EXT RGBA + COMPRESSED_RGBA_S3TC_DXT5_EXT RGBA + + + Modify Section 3.8.2, Alternate Image Specification + + (add to end of TexSubImage discussion, p.123 -- after edit from the + ARB_texture_compression spec) + + If the internal format of the texture image being modified is + COMPRESSED_RGB_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT3_EXT, or COMPRESSED_RGBA_S3TC_DXT5_EXT, the + texture is stored using one of the several S3TC compressed texture image + formats. Such images are easily edited along 4x4 texel boundaries, so the + limitations on TexSubImage2D or CopyTexSubImage2D parameters are relaxed. + TexSubImage2D and CopyTexSubImage2D will result in an INVALID_OPERATION + error only if one of the following conditions occurs: + + * is not a multiple of four or equal to TEXTURE_WIDTH, + unless and are both zero. + * is not a multiple of four or equal to TEXTURE_HEIGHT, + unless and are both zero. + * or is not a multiple of four. + + The contents of any 4x4 block of texels of an S3TC compressed texture + image that does not intersect the area being modified are preserved during + valid TexSubImage2D and CopyTexSubImage2D calls. + + + Add to Section 3.8.2, Alternate Image Specification (adding to the end of + the CompressedTexImage section introduced by the ARB_texture_compression + spec) + + If is COMPRESSED_RGB_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT3_EXT, or + COMPRESSED_RGBA_S3TC_DXT5_EXT, the compressed texture is stored using one + of several S3TC compressed texture image formats. The S3TC texture + compression algorithm supports only 2D images without borders. + CompressedTexImage1DARB and CompressedTexImage3DARB produce an + INVALID_ENUM error if is an S3TC format. + CompressedTexImage2DARB will produce an INVALID_OPERATION error if + is non-zero. + + + Add to Section 3.8.2, Alternate Image Specification (adding to the end of + the CompressedTexSubImage section introduced by the + ARB_texture_compression spec) + + If the internal format of the texture image being modified is + COMPRESSED_RGB_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT3_EXT, or COMPRESSED_RGBA_S3TC_DXT5_EXT, the + texture is stored using one of the several S3TC compressed texture image + formats. Since the S3TC texture compression algorithm supports only 2D + images, CompressedTexSubImage1DARB and CompressedTexSubImage3DARB produce + an INVALID_ENUM error if is an S3TC format. Since S3TC images + are easily edited along 4x4 texel boundaries, the limitations on + CompressedTexSubImage2D are relaxed. CompressedTexSubImage2D will result + in an INVALID_OPERATION error only if one of the following conditions + occurs: + + * is not a multiple of four or equal to TEXTURE_WIDTH. + * is not a multiple of four or equal to TEXTURE_HEIGHT. + * or is not a multiple of four. + + The contents of any 4x4 block of texels of an S3TC compressed texture + image that does not intersect the area being modified are preserved during + valid TexSubImage2D and CopyTexSubImage2D calls. + +Additions to Chapter 4 of the OpenGL 1.2.1 Specification (Per-Fragment +Operations and the Frame Buffer) + + None. + +Additions to Chapter 5 of the OpenGL 1.2.1 Specification (Special Functions) + + None. + +Additions to Chapter 6 of the OpenGL 1.2.1 Specification (State and +State Requests) + + None. + +Additions to Appendix A of the OpenGL 1.2.1 Specification (Invariance) + + None. + +Additions to the AGL/GLX/WGL Specifications + + None. + +GLX Protocol + + None. + +Errors + + INVALID_ENUM is generated by CompressedTexImage1DARB or + CompressedTexImage3DARB if is + COMPRESSED_RGB_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT3_EXT, or COMPRESSED_RGBA_S3TC_DXT5_EXT. + + INVALID_OPERATION is generated by CompressedTexImage2DARB if + is COMPRESSED_RGB_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT3_EXT, or + COMPRESSED_RGBA_S3TC_DXT5_EXT and is not equal to zero. + + INVALID_ENUM is generated by CompressedTexSubImage1DARB or + CompressedTexSubImage3DARB if is COMPRESSED_RGB_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT3_EXT, or + COMPRESSED_RGBA_S3TC_DXT5_EXT. + + INVALID_OPERATION is generated by TexSubImage2D CopyTexSubImage2D, or + CompressedTexSubImage2D if TEXTURE_INTERNAL_FORMAT is + COMPRESSED_RGB_S3TC_DXT1_EXT, COMPRESSED_RGBA_S3TC_DXT1_EXT, + COMPRESSED_RGBA_S3TC_DXT3_EXT, or COMPRESSED_RGBA_S3TC_DXT5_EXT and any of + the following apply: is not a multiple of four or equal to + TEXTURE_WIDTH; is not a multiple of four or equal to + TEXTURE_HEIGHT; or is not a multiple of four. + + + The following restrictions from the ARB_texture_compression specification + do not apply to S3TC texture formats, since subimage modification is + straightforward as long as the subimage is properly aligned. + + DELETE: INVALID_OPERATION is generated by TexSubImage1D, TexSubImage2D, + DELETE: TexSubImage3D, CopyTexSubImage1D, CopyTexSubImage2D, or + DELETE: CopyTexSubImage3D if the internal format of the texture image is + DELETE: compressed and , , or does not equal + DELETE: -b, where b is value of TEXTURE_BORDER. + + DELETE: INVALID_VALUE is generated by CompressedTexSubImage1DARB, + DELETE: CompressedTexSubImage2DARB, or CompressedTexSubImage3DARB if the + DELETE: entire texture image is not being edited: if , + DELETE: , or is greater than -b, + is + DELETE: less than w+b, + is less than h+b, or + DELETE: + is less than d+b, where b is the value of + DELETE: TEXTURE_BORDER, w is the value of TEXTURE_WIDTH, h is the value of + DELETE: TEXTURE_HEIGHT, and d is the value of TEXTURE_DEPTH. + + See also errors in the GL_ARB_texture_compression specification. + +New State + + In the "Textures" state table, increment the TEXTURE_INTERNAL_FORMAT + subscript for Z by 4 in the "Type" row. + +New Implementation Dependent State + + None + +Appendix + + S3TC Compressed Texture Image Formats + + Compressed texture images stored using the S3TC compressed image formats + are represented as a collection of 4x4 texel blocks, where each block + contains 64 or 128 bits of texel data. The image is encoded as a normal + 2D raster image in which each 4x4 block is treated as a single pixel. If + an S3TC image has a width or height less than four, the data corresponding + to texels outside the image are irrelevant and undefined. + + When an S3TC image with a width of , height of , and block size of + (8 or 16 bytes) is decoded, the corresponding image size (in + bytes) is: + + ceil(/4) * ceil(/4) * blocksize. + + When decoding an S3TC image, the block containing the texel at offset + (, ) begins at an offset (in bytes) relative to the base of the + image of: + + blocksize * (ceil(/4) * floor(/4) + floor(/4)). + + The data corresponding to a specific texel (, ) are extracted from a + 4x4 texel block using a relative (x,y) value of + + ( modulo 4, modulo 4). + + There are four distinct S3TC image formats: + + COMPRESSED_RGB_S3TC_DXT1_EXT: Each 4x4 block of texels consists of 64 + bits of RGB image data. + + Each RGB image data block is encoded as a sequence of 8 bytes, called (in + order of increasing address): + + c0_lo, c0_hi, c1_lo, c1_hi, bits_0, bits_1, bits_2, bits_3 + + The 8 bytes of the block are decoded into three quantities: + + color0 = c0_lo + c0_hi * 256 + color1 = c1_lo + c1_hi * 256 + bits = bits_0 + 256 * (bits_1 + 256 * (bits_2 + 256 * bits_3)) + + color0 and color1 are 16-bit unsigned integers that are unpacked to + RGB colors RGB0 and RGB1 as though they were 16-bit packed pixels with + a of RGB and a type of UNSIGNED_SHORT_5_6_5. + + bits is a 32-bit unsigned integer, from which a two-bit control code + is extracted for a texel at location (x,y) in the block using: + + code(x,y) = bits[2*(4*y+x)+1..2*(4*y+x)+0] + + where bit 31 is the most significant and bit 0 is the least + significant bit. + + The RGB color for a texel at location (x,y) in the block is given by: + + RGB0, if color0 > color1 and code(x,y) == 0 + RGB1, if color0 > color1 and code(x,y) == 1 + (2*RGB0+RGB1)/3, if color0 > color1 and code(x,y) == 2 + (RGB0+2*RGB1)/3, if color0 > color1 and code(x,y) == 3 + + RGB0, if color0 <= color1 and code(x,y) == 0 + RGB1, if color0 <= color1 and code(x,y) == 1 + (RGB0+RGB1)/2, if color0 <= color1 and code(x,y) == 2 + BLACK, if color0 <= color1 and code(x,y) == 3 + + Arithmetic operations are done per component, and BLACK refers to an + RGB color where red, green, and blue are all zero. + + Since this image has an RGB format, there is no alpha component and the + image is considered fully opaque. + + + COMPRESSED_RGBA_S3TC_DXT1_EXT: Each 4x4 block of texels consists of 64 + bits of RGB image data and minimal alpha information. The RGB components + of a texel are extracted in the same way as COMPRESSED_RGB_S3TC_DXT1_EXT. + + The alpha component for a texel at location (x,y) in the block is + given by: + + 0.0, if color0 <= color1 and code(x,y) == 3 + 1.0, otherwise + + IMPORTANT: When encoding an RGBA image into a format using 1-bit + alpha, any texels with an alpha component less than 0.5 end up with an + alpha of 0.0 and any texels with an alpha component greater than or + equal to 0.5 end up with an alpha of 1.0. When encoding an RGBA image + into the COMPRESSED_RGBA_S3TC_DXT1_EXT format, the resulting red, + green, and blue components of any texels with a final alpha of 0.0 + will automatically be zero (black). If this behavior is not desired + by an application, it should not use COMPRESSED_RGBA_S3TC_DXT1_EXT. + This format will never be used when a generic compressed internal + format (Table 3.16.2) is specified, although the nearly identical + format COMPRESSED_RGB_S3TC_DXT1_EXT (above) may be. + + + COMPRESSED_RGBA_S3TC_DXT3_EXT: Each 4x4 block of texels consists of 64 + bits of uncompressed alpha image data followed by 64 bits of RGB image + data. + + Each RGB image data block is encoded according to the + COMPRESSED_RGB_S3TC_DXT1_EXT format, with the exception that the two code + bits always use the non-transparent encodings. In other words, they are + treated as though color0 > color1, regardless of the actual values of + color0 and color1. + + Each alpha image data block is encoded as a sequence of 8 bytes, called + (in order of increasing address): + + a0, a1, a2, a3, a4, a5, a6, a7 + + The 8 bytes of the block are decoded into one 64-bit integer: + + alpha = a0 + 256 * (a1 + 256 * (a2 + 256 * (a3 + 256 * (a4 + + 256 * (a5 + 256 * (a6 + 256 * a7)))))) + + alpha is a 64-bit unsigned integer, from which a four-bit alpha value + is extracted for a texel at location (x,y) in the block using: + + alpha(x,y) = bits[4*(4*y+x)+3..4*(4*y+x)+0] + + where bit 63 is the most significant and bit 0 is the least + significant bit. + + The alpha component for a texel at location (x,y) in the block is + given by alpha(x,y) / 15. + + + COMPRESSED_RGBA_S3TC_DXT5_EXT: Each 4x4 block of texels consists of 64 + bits of compressed alpha image data followed by 64 bits of RGB image data. + + Each RGB image data block is encoded according to the + COMPRESSED_RGB_S3TC_DXT1_EXT format, with the exception that the two code + bits always use the non-transparent encodings. In other words, they are + treated as though color0 > color1, regardless of the actual values of + color0 and color1. + + Each alpha image data block is encoded as a sequence of 8 bytes, called + (in order of increasing address): + + alpha0, alpha1, bits_0, bits_1, bits_2, bits_3, bits_4, bits_5 + + The alpha0 and alpha1 are 8-bit unsigned bytes converted to alpha + components by multiplying by 1/255. + + The 6 "bits" bytes of the block are decoded into one 48-bit integer: + + bits = bits_0 + 256 * (bits_1 + 256 * (bits_2 + 256 * (bits_3 + + 256 * (bits_4 + 256 * bits_5)))) + + bits is a 48-bit unsigned integer, from which a three-bit control code + is extracted for a texel at location (x,y) in the block using: + + code(x,y) = bits[3*(4*y+x)+1..3*(4*y+x)+0] + + where bit 47 is the most significant and bit 0 is the least + significant bit. + + The alpha component for a texel at location (x,y) in the block is + given by: + + alpha0, code(x,y) == 0 + alpha1, code(x,y) == 1 + + (6*alpha0 + 1*alpha1)/7, alpha0 > alpha1 and code(x,y) == 2 + (5*alpha0 + 2*alpha1)/7, alpha0 > alpha1 and code(x,y) == 3 + (4*alpha0 + 3*alpha1)/7, alpha0 > alpha1 and code(x,y) == 4 + (3*alpha0 + 4*alpha1)/7, alpha0 > alpha1 and code(x,y) == 5 + (2*alpha0 + 5*alpha1)/7, alpha0 > alpha1 and code(x,y) == 6 + (1*alpha0 + 6*alpha1)/7, alpha0 > alpha1 and code(x,y) == 7 + + (4*alpha0 + 1*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 2 + (3*alpha0 + 2*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 3 + (2*alpha0 + 3*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 4 + (1*alpha0 + 4*alpha1)/5, alpha0 <= alpha1 and code(x,y) == 5 + 0.0, alpha0 <= alpha1 and code(x,y) == 6 + 1.0, alpha0 <= alpha1 and code(x,y) == 7 + + +Revision History + + 1.1, 11/16/01 pbrown: Updated contact info, clarified where texels + fall within a single block. + + 1.0, 07/07/00 prbrown1: Published final version agreed to by working + group members. + + 0.9, 06/24/00 prbrown1: Documented that block-aligned TexSubImage calls + do not modify existing texels outside the + modified blocks. Added caveat to allow for a + (0,0)-anchored TexSubImage operation of + arbitrary size. + + 0.7, 04/11/00 prbrown1: Added issues on DXT1, DXT3, and DXT5 encodings + where the MSDN documentation doesn't match what + is really done. Added enum values from the + extension registry. + + 0.4, 03/28/00 prbrown1: Updated to reflect final version of the + ARB_texture_compression extension. Allowed + block-aligned TexSubImage calls. + + 0.3, 03/07/00 prbrown1: Resolved issues pertaining to the format of RGB + blocks in the DXT3 and DXT5 formats (they don't + ever use the "transparent" encoding). Fixed + decoding of DXT1 blocks. Pointed out issue of + "transparent" texels in DXT1 encodings having + different behaviors for RGB and RGBA internal + formats. + + 0.2, 02/23/00 prbrown1: Minor revisions; added several issues. + + 0.11, 02/17/00 prbrown1: Slight modification to error semantics + (INVALID_ENUM instead of INVALID_OPERATION). + + 0.1, 02/15/00 prbrown1: Initial revision. diff --git a/src/DocumentClickAction.cs b/src/DocumentClickAction.cs new file mode 100644 index 0000000..864f041 --- /dev/null +++ b/src/DocumentClickAction.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum DocumentClickAction + { + Select, + Close + } +} diff --git a/src/DocumentStrip.cs b/src/DocumentStrip.cs new file mode 100644 index 0000000..f45dfd2 --- /dev/null +++ b/src/DocumentStrip.cs @@ -0,0 +1,626 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class DocumentStrip + : ImageStrip, + IDocumentList + { + private object thumbsLock = new object(); + + private List documents = new List(); + private List documentButtons = new List(); + private Dictionary dw2button = new Dictionary(); + private Dictionary thumbs = new Dictionary(); + private ThumbnailManager thumbnailManager; + private DocumentWorkspace selectedDocument = null; + private bool ensureSelectedIsVisible = true; + private int suspendThumbnailUpdates = 0; + + public void SuspendThumbnailUpdates() + { + ++this.suspendThumbnailUpdates; + } + + public void ResumeThumbnailUpdates() + { + --this.suspendThumbnailUpdates; + } + + public void SyncThumbnails() + { + if (!this.thumbnailManager.IsDisposed) + { + using (new WaitCursorChanger(this)) + { + this.thumbnailManager.DrainQueue(); + } + + Refresh(); + } + } + + public int ThumbnailUpdateLatency + { + get + { + return this.thumbnailManager.UpdateLatency; + } + + set + { + this.thumbnailManager.UpdateLatency = value; + } + } + + public bool EnsureSelectedIsVisible + { + get + { + return this.ensureSelectedIsVisible; + } + + set + { + if (this.ensureSelectedIsVisible != value) + { + this.ensureSelectedIsVisible = value; + PerformLayout(); + } + } + } + + public DocumentWorkspace[] DocumentList + { + get + { + return this.documents.ToArray(); + } + } + + public Image[] DocumentThumbnails + { + get + { + SyncThumbnails(); + + Image[] thumbnails = new Image[this.documents.Count]; + + for (int i = 0; i < thumbnails.Length; ++i) + { + DocumentWorkspace dw = this.documents[i]; + RenderArgs ra; + + if (!this.thumbs.TryGetValue(dw, out ra)) + { + thumbnails[i] = null; + } + else + { + thumbnails[i] = ra.Bitmap; + } + } + + return thumbnails; + } + } + + public int DocumentCount + { + get + { + return this.documents.Count; + } + } + + public event EventHandler DocumentListChanged; + protected virtual void OnDocumentListChanged() + { + if (DocumentListChanged != null) + { + DocumentListChanged(this, EventArgs.Empty); + } + } + + public DocumentWorkspace SelectedDocument + { + get + { + return this.selectedDocument; + } + + set + { + if (!this.documents.Contains(value)) + { + throw new ArgumentException("DocumentWorkspace isn't being tracked by this instance of DocumentStrip"); + } + + if (this.selectedDocument != value) + { + SelectDocumentWorkspace(value); + OnDocumentClicked(value, DocumentClickAction.Select); + Refresh(); + } + } + } + + public int SelectedDocumentIndex + { + get + { + return this.documents.IndexOf(this.selectedDocument); + } + } + + public override Size GetPreferredSize(Size proposedSize) + { + Size itemSize = ItemSize; + int preferredWidth; + + if (this.ItemCount == 0) + { + preferredWidth = 0; + } + else + { + preferredWidth = itemSize.Width * DocumentCount; + } + + Size preferredSize = new Size(preferredWidth, itemSize.Height); + return preferredSize; + } + + private bool OnDigitHotKeyPressed(Keys keys) + { + // strip off the Alt and Control stuff + keys &= ~Keys.Alt; + keys &= ~Keys.Control; + + if (keys < Keys.D0 || keys > Keys.D9) + { + return false; + } + + int digit = (int)keys - (int)Keys.D0; + int index; + + if (digit == 0) + { + index = 9; + } + else + { + index = digit - 1; + } + + if (index < this.documents.Count) + { + PerformItemClick(index, ItemPart.Image, MouseButtons.Left); + return true; + } + else + { + return false; + } + } + + public DocumentStrip() + { + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Tab, OnNextTabHotKeyPressed); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.PageDown, OnNextTabHotKeyPressed); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Shift | Keys.Tab, OnPreviousTabHotKeyPressed); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.PageUp, OnPreviousTabHotKeyPressed); + + thumbnailManager = new ThumbnailManager(this); // allow for a 1px black border + + InitializeComponent(); + + for (int i = 0; i <= 9; ++i) + { + Keys digit = Utility.LetterOrDigitCharToKeys((char)(i + '0')); + PdnBaseForm.RegisterFormHotKey(Keys.Control | digit, OnDigitHotKeyPressed); + PdnBaseForm.RegisterFormHotKey(Keys.Alt | digit, OnDigitHotKeyPressed); + } + + this.ShowCloseButtons = true; + } + + private bool OnNextTabHotKeyPressed(Keys keys) + { + bool processed = NextTab(); + return processed; + } + + private bool OnPreviousTabHotKeyPressed(Keys keys) + { + bool processed = PreviousTab(); + return processed; + } + + public bool NextTab() + { + bool changed = false; + + if (this.selectedDocument != null) + { + int currentIndex = this.documents.IndexOf(this.selectedDocument); + int newIndex = (currentIndex + 1) % this.documents.Count; + SelectedDocument = this.documents[newIndex]; + changed = true; + } + + return changed; + } + + public bool PreviousTab() + { + bool changed = false; + + if (this.selectedDocument != null) + { + int currentIndex = this.documents.IndexOf(this.selectedDocument); + int newIndex = (currentIndex + (this.documents.Count - 1)) % this.documents.Count; + SelectedDocument = this.documents[newIndex]; + changed = true; + } + + return changed; + } + + private void InitializeComponent() + { + this.Name = "DocumentStrip"; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + while (this.documents.Count > 0) + { + RemoveDocumentWorkspace(this.documents[this.documents.Count - 1]); + } + + if (this.thumbnailManager != null) + { + this.thumbnailManager.Dispose(); + this.thumbnailManager = null; + } + + foreach (DocumentWorkspace dw in this.thumbs.Keys) + { + RenderArgs ra = this.thumbs[dw]; + ra.Dispose(); + } + + this.thumbs.Clear(); + this.thumbs = null; + } + + base.Dispose(disposing); + } + + protected override void OnScrollArrowClicked(ArrowDirection arrowDirection) + { + int sign = 0; + + switch (arrowDirection) + { + case ArrowDirection.Left: + sign = -1; + break; + + case ArrowDirection.Right: + sign = +1; + break; + } + + int delta = ItemSize.Width; + + ScrollOffset += sign * delta; + + base.OnScrollArrowClicked(arrowDirection); + } + + protected override void OnItemClicked(Item item, ItemPart itemPart, MouseButtons mouseButtons) + { + DocumentWorkspace dw = item.Tag as DocumentWorkspace; + + if (dw != null) + { + switch (itemPart) + { + case ItemPart.None: + // do nothing + break; + + case ItemPart.CloseButton: + if (mouseButtons == MouseButtons.Left) + { + OnDocumentClicked(dw, DocumentClickAction.Close); + } + break; + + case ItemPart.Image: + if (mouseButtons == MouseButtons.Left) + { + SelectedDocument = dw; + } + else if (mouseButtons == MouseButtons.Right) + { + // TODO: right click menu + } + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + + base.OnItemClicked(item, itemPart, mouseButtons); + } + + public event EventHandler>> DocumentClicked; + protected virtual void OnDocumentClicked(DocumentWorkspace dw, DocumentClickAction action) + { + if (DocumentClicked != null) + { + DocumentClicked(this, new EventArgs>( + Pair.Create(dw, action))); + } + } + + public void UnlockDocumentWorkspaceDirtyValue(DocumentWorkspace unlockMe) + { + Item docItem = this.dw2button[unlockMe]; + docItem.UnlockDirtyValue(); + } + + public void LockDocumentWorkspaceDirtyValue(DocumentWorkspace lockMe, bool forceDirtyValue) + { + Item docItem = this.dw2button[lockMe]; + docItem.LockDirtyValue(forceDirtyValue); + } + + public void AddDocumentWorkspace(DocumentWorkspace addMe) + { + this.documents.Add(addMe); + + ImageStrip.Item docButton = new ImageStrip.Item(); + docButton.Image = null; + docButton.Tag = addMe; + + AddItem(docButton); + this.documentButtons.Add(docButton); + + addMe.CompositionUpdated += Workspace_CompositionUpdated; + + this.dw2button.Add(addMe, docButton); + + if (addMe.Document != null) + { + QueueThumbnailUpdate(addMe); + docButton.Dirty = addMe.Document.Dirty; + addMe.Document.DirtyChanged += Document_DirtyChanged; + } + + addMe.DocumentChanging += Workspace_DocumentChanging; + addMe.DocumentChanged += Workspace_DocumentChanged; + + OnDocumentListChanged(); + } + + private void Workspace_DocumentChanging(object sender, EventArgs e) + { + if (e.Data != null) + { + e.Data.DirtyChanged -= Document_DirtyChanged; + } + } + + private void Workspace_DocumentChanged(object sender, EventArgs e) + { + DocumentWorkspace dw = (DocumentWorkspace)sender; + ImageStrip.Item docButton = this.dw2button[dw]; + + if (dw.Document != null) + { + docButton.Dirty = dw.Document.Dirty; + dw.Document.DirtyChanged += Document_DirtyChanged; + } + else + { + docButton.Dirty = false; + } + } + + private void Document_DirtyChanged(object sender, EventArgs e) + { + for (int i = 0; i < this.documents.Count; ++i) + { + if (object.ReferenceEquals(sender, this.documents[i].Document)) + { + ImageStrip.Item docButton = this.dw2button[this.documents[i]]; + docButton.Dirty = ((Document)sender).Dirty; + } + } + } + + private void Workspace_CompositionUpdated(object sender, EventArgs e) + { + DocumentWorkspace dw = (DocumentWorkspace)sender; + QueueThumbnailUpdate(dw); + } + + public void RemoveDocumentWorkspace(DocumentWorkspace removeMe) + { + removeMe.CompositionUpdated -= Workspace_CompositionUpdated; + + if (this.selectedDocument == removeMe) + { + this.selectedDocument = null; + } + + removeMe.DocumentChanging -= Workspace_DocumentChanging; + removeMe.DocumentChanged -= Workspace_DocumentChanged; + + if (removeMe.Document != null) + { + removeMe.Document.DirtyChanged -= Document_DirtyChanged; + } + + this.documents.Remove(removeMe); + this.thumbnailManager.RemoveFromQueue(removeMe); + + ImageStrip.Item docButton = this.dw2button[removeMe]; + this.RemoveItem(docButton); + this.dw2button.Remove(removeMe); + this.documentButtons.Remove(docButton); + + if (this.thumbs.ContainsKey(removeMe)) + { + RenderArgs thumbRA = this.thumbs[removeMe]; + Surface surface = thumbRA.Surface; + thumbRA.Dispose(); + this.thumbs.Remove(removeMe); + surface.Dispose(); + } + + OnDocumentListChanged(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout(levent); + + if (this.ensureSelectedIsVisible && + (!Focused && !LeftScrollButton.Focused && !RightScrollButton.Focused)) + { + int index = this.documents.IndexOf(this.selectedDocument); + EnsureItemFullyVisible(index); + } + } + + public void SelectDocumentWorkspace(DocumentWorkspace selectMe) + { + UI.SuspendControlPainting(this); + + this.selectedDocument = selectMe; + + if (this.thumbs.ContainsKey(selectMe)) + { + RenderArgs thumb = this.thumbs[selectMe]; + Bitmap bitmap = thumb.Bitmap; + } + else + { + QueueThumbnailUpdate(selectMe); + } + + foreach (ImageStrip.Item docItem in this.documentButtons) + { + if ((docItem.Tag as DocumentWorkspace) == selectMe) + { + EnsureItemFullyVisible(docItem); + docItem.Checked = true; + } + else + { + docItem.Checked = false; + } + } + + UI.ResumeControlPainting(this); + Invalidate(true); + } + + public void RefreshThumbnail(DocumentWorkspace dw) + { + if (this.documents.Contains(dw)) + { + QueueThumbnailUpdate(dw); + } + } + + public void RefreshAllThumbnails() + { + foreach (DocumentWorkspace dw in this.documents) + { + QueueThumbnailUpdate(dw); + } + } + + private void OnThumbnailUpdated(DocumentWorkspace dw) + { + // We must double check that the DW is still around, because there's a chance + // that the DW was been removed while the thumbnail was being rendered. + if (this.dw2button.ContainsKey(dw)) + { + ImageStrip.Item docButton = this.dw2button[dw]; + RenderArgs docRA = this.thumbs[dw]; + docButton.Image = docRA.Bitmap; + docButton.Update(); + } + } + + public void QueueThumbnailUpdate(DocumentWorkspace dw) + { + if (this.suspendThumbnailUpdates <= 0) + { + this.thumbnailManager.QueueThumbnailUpdate(dw, PreferredImageSize.Width - 2, OnThumbnailRendered); + } + } + + private void OnThumbnailRendered(object sender, EventArgs> e) + { + RenderArgs ra = null; + DocumentWorkspace dw = (DocumentWorkspace)e.Data.First; + + Size desiredSize = new Size(e.Data.Second.Width + 2, e.Data.Second.Height + 2); + + if (this.thumbs.ContainsKey(dw)) + { + ra = this.thumbs[dw]; + + if (ra.Size != desiredSize) + { + ra.Dispose(); + ra = null; + + this.thumbs.Remove(dw); + } + } + + if (ra == null) + { + Surface surface = new Surface(desiredSize); + ra = new RenderArgs(surface); + this.thumbs.Add(dw, ra); + } + + ra.Surface.Clear(ColorBgra.Black); + ra.Surface.CopySurface(e.Data.Second, new Point(1, 1)); + e.Data.Second.Dispose(); + + OnThumbnailUpdated(dw); + } + } +} diff --git a/src/DocumentWorkspace.cs b/src/DocumentWorkspace.cs new file mode 100644 index 0000000..6258998 --- /dev/null +++ b/src/DocumentWorkspace.cs @@ -0,0 +1,2944 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Data; +using PaintDotNet.Effects; +using PaintDotNet.HistoryFunctions; +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using PaintDotNet.Tools; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Security; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Builds on DocumentView by adding application-specific elements. + /// + internal class DocumentWorkspace + : DocumentView, + IHistoryWorkspace, + IThumbnailProvider + { + public static readonly DateTime NeverSavedDateTime = DateTime.MinValue; + + private static Type[] tools; // TODO: move to Tool class? + private static ToolInfo[] toolInfos; + + private ZoomBasis zoomBasis; + private AppWorkspace appWorkspace; + private string filePath = null; + private FileType fileType = null; + private SaveConfigToken saveConfigToken = null; + private Selection selection = new Selection(); + private Surface scratchSurface = null; + private SelectionRenderer selectionRenderer; + private Hashtable staticToolData = Hashtable.Synchronized(new Hashtable()); + private Tool activeTool; + private Type previousActiveToolType; + private Type preNullTool = null; + private int nullToolCount = 0; + private int zoomChangesCount = 0; + private HistoryStack history; + private Layer activeLayer; + private System.Windows.Forms.Timer toolPulseTimer; + private DateTime lastSaveTime = NeverSavedDateTime; + private int suspendToolCursorChanges = 0; + private ImageResource statusIcon = null; + private string statusText = null; + + private readonly string contextStatusBarWithAngleFormat = PdnResources.GetString("StatusBar.Context.SelectedArea.Text.WithAngle.Format"); + private readonly string contextStatusBarFormat = PdnResources.GetString("StatusBar.Context.SelectedArea.Text.Format"); + + public void SuspendToolCursorChanges() + { + ++this.suspendToolCursorChanges; + } + + public void ResumeToolCursorChanges() + { + --this.suspendToolCursorChanges; + + if (this.suspendToolCursorChanges <= 0 && this.activeTool != null) + { + Cursor = this.activeTool.Cursor; + } + } + + public ImageResource StatusIcon + { + get + { + return this.statusIcon; + } + } + + public string StatusText + { + get + { + return this.statusText; + } + } + + public void SetStatus(string newStatusText, ImageResource newStatusIcon) + { + this.statusText = newStatusText; + this.statusIcon = newStatusIcon; + OnStatusChanged(); + } + + public event EventHandler StatusChanged; + protected virtual void OnStatusChanged() + { + if (StatusChanged != null) + { + StatusChanged(this, EventArgs.Empty); + } + } + + static DocumentWorkspace() + { + InitializeTools(); + InitializeToolInfos(); + } + + public DateTime LastSaveTime + { + get + { + return this.lastSaveTime; + } + } + + public bool IsZoomChanging + { + get + { + return (this.zoomChangesCount > 0); + } + } + + private void BeginZoomChanges() + { + ++this.zoomChangesCount; + } + + private void EndZoomChanges() + { + --this.zoomChangesCount; + } + + protected override void OnSizeChanged(EventArgs e) + { + PerformLayout(); + base.OnSizeChanged(e); + } + + protected override void OnLayout(LayoutEventArgs e) + { + if (this.zoomBasis == ZoomBasis.FitToWindow) + { + ZoomToWindow(); + + // This bizarre ordering of setting PanelAutoScroll prevents some very weird layout/scroll-without-scrollbars stuff. + PanelAutoScroll = true; + PanelAutoScroll = false; + } + + base.OnLayout(e); + } + + protected override void OnResize(EventArgs e) + { + if (this.zoomBasis == ZoomBasis.FitToWindow) + { + PerformLayout(); + } + + base.OnResize(e); + } + + public DocumentWorkspace() + { + this.activeLayer = null; + this.history = new HistoryStack(this); + + InitializeComponent(); + + // hook the DocumentWorkspace with its selectedPath ... + this.selectionRenderer = new SelectionRenderer(this.RendererList, this.Selection, this); + this.RendererList.Add(this.selectionRenderer, true); + this.selectionRenderer.EnableOutlineAnimation = true; + this.selectionRenderer.EnableSelectionTinting = false; + this.selectionRenderer.EnableSelectionOutline = true; + + this.selection.Changed += new EventHandler(Selection_Changed); + + this.zoomBasis = ZoomBasis.FitToWindow; + } + + protected override void OnUnitsChanged() + { + if (!Selection.IsEmpty) + { + UpdateSelectionInfoInStatusBar(); + } + + base.OnUnitsChanged(); + } + + public void UpdateStatusBarToToolHelpText(Tool tool) + { + if (tool == null) + { + SetStatus(string.Empty, null); + } + else + { + string toolName = tool.Name; + string helpText = tool.HelpText; + + string contextFormat = PdnResources.GetString("StatusBar.Context.Help.Text.Format"); + string contextText = string.Format(contextFormat, toolName, helpText); + + SetStatus(contextText, PdnResources.GetImageResource("Icons.MenuHelpHelpTopicsIcon.png")); + } + } + + public void UpdateStatusBarToToolHelpText() + { + UpdateStatusBarToToolHelpText(this.activeTool); + } + + private void UpdateSelectionInfoInStatusBar() + { + if (Selection.IsEmpty) + { + UpdateStatusBarToToolHelpText(); + } + else + { + string newStatusText; + + int area = 0; + Rectangle bounds; + + using (PdnRegion tempSelection = Selection.CreateRegionRaw()) + { + tempSelection.Intersect(Document.Bounds); + bounds = Utility.GetRegionBounds(tempSelection); + area = tempSelection.GetArea(); + } + + string unitsAbbreviationXY; + string xString; + string yString; + string unitsAbbreviationWH; + string widthString; + string heightString; + + Document.CoordinatesToStrings(Units, bounds.X, bounds.Y, out xString, out yString, out unitsAbbreviationXY); + Document.CoordinatesToStrings(Units, bounds.Width, bounds.Height, out widthString, out heightString, out unitsAbbreviationWH); + + NumberFormatInfo nfi = (NumberFormatInfo)CultureInfo.CurrentCulture.NumberFormat.Clone(); + + string areaString; + if (this.Units == MeasurementUnit.Pixel) + { + nfi.NumberDecimalDigits = 0; + areaString = area.ToString("N", nfi); + } + else + { + nfi.NumberDecimalDigits = 2; + double areaD = Document.PixelAreaToPhysicalArea(area, this.Units); + areaString = areaD.ToString("N", nfi); + } + + string pluralUnits = PdnResources.GetString("MeasurementUnit." + this.Units.ToString() + ".Plural"); + MoveToolBase moveTool = Tool as MoveToolBase; + + if (moveTool != null && moveTool.HostShouldShowAngle) + { + NumberFormatInfo nfi2 = (NumberFormatInfo)nfi.Clone(); + nfi2.NumberDecimalDigits = 2; + float angle = moveTool.HostAngle; + + while (angle > 180.0f) + { + angle -= 360.0f; + } + + while (angle < -180.0f) + { + angle += 360.0f; + } + + newStatusText = string.Format( + contextStatusBarWithAngleFormat, + xString, + unitsAbbreviationXY, + yString, + unitsAbbreviationXY, + widthString, + unitsAbbreviationWH, + heightString, + unitsAbbreviationWH, + areaString, + pluralUnits.ToLower(), + moveTool.HostAngle.ToString("N", nfi2)); + } + else + { + newStatusText = string.Format( + contextStatusBarFormat, + xString, + unitsAbbreviationXY, + yString, + unitsAbbreviationXY, + widthString, + unitsAbbreviationWH, + heightString, + unitsAbbreviationWH, + areaString, + pluralUnits.ToLower()); + } + + SetStatus(newStatusText, PdnResources.GetImageResource("Icons.SelectionIcon.png")); + } + } + + private void Selection_Changed(object sender, EventArgs e) + { + UpdateRulerSelectionTinting(); + UpdateSelectionInfoInStatusBar(); + } + + private void InitializeComponent() + { + this.toolPulseTimer = new System.Windows.Forms.Timer(); + this.toolPulseTimer.Interval = 16; + this.toolPulseTimer.Tick += new EventHandler(this.ToolPulseTimer_Tick); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.activeTool != null) + { + this.activeTool.Dispose(); + this.activeTool = null; + } + } + + base.Dispose(disposing); + } + + public void PerformActionAsync(DocumentWorkspaceAction action) + { + BeginInvoke(new Procedure(PerformAction), new object[] { action }); + } + + public void PerformAction(DocumentWorkspaceAction action) + { + bool nullTool = false; + + if ((action.ActionFlags & ActionFlags.KeepToolActive) != ActionFlags.KeepToolActive) + { + PushNullTool(); + Update(); + nullTool = true; + } + + try + { + using (new WaitCursorChanger(this)) + { + HistoryMemento ha = action.PerformAction(this); + + if (ha != null) + { + History.PushNewMemento(ha); + } + } + } + + finally + { + if (nullTool) + { + PopNullTool(); + } + } + } + + /// + /// Executes a HistoryFunction in the context of this DocumentWorkspace. + /// + /// The HistoryFunction to execute. + /// + /// Depending on the HistoryFunction, the currently active tool may be refreshed. + /// + public HistoryFunctionResult ExecuteFunction(HistoryFunction function) + { + HistoryFunctionResult result; + + bool nullTool = false; + + if ((function.ActionFlags & ActionFlags.KeepToolActive) != ActionFlags.KeepToolActive) + { + PushNullTool(); + Update(); + nullTool = true; + } + + try + { + using (new WaitCursorChanger(this)) + { + HistoryMemento hm = null; + string errorText; + + try + { + bool cancelled = false; + + if ((function.ActionFlags & ActionFlags.ReportsProgress) != ActionFlags.ReportsProgress) + { + hm = function.Execute(this); + } + else + { + ProgressDialog pd = new ProgressDialog(); + bool pdLoaded = false; + bool closeAtLoad = false; + + EventHandler loadCallback = + delegate(object sender, EventArgs e) + { + pdLoaded = true; + + if (closeAtLoad) + { + pd.Close(); + } + }; + + ProgressEventHandler progressCallback = + delegate(object sender, ProgressEventArgs e) + { + if (pdLoaded) + { + double newValue = Utility.Clamp(e.Percent, 0.0, 100.0); + pd.Value = newValue; + } + }; + + EventHandler> finishedCallback = + delegate(object sender, EventArgs e) + { + hm = e.Data; + + if (pdLoaded) + { + // TODO: fix ProgressDialog's very weird interface + pd.ExternalFinish(); + pd.Close(); + } + else + { + closeAtLoad = true; + } + }; + + EventHandler cancelClickCallback = + delegate(object sender, EventArgs e) + { + cancelled = true; + function.RequestCancel(); + //pd.Cancellable = false; + }; + + pd.Text = PdnInfo.GetBareProductName(); + pd.Description = PdnResources.GetString("ExecuteFunction.ProgressDialog.Description.Text"); + pd.Load += loadCallback; + pd.Cancellable = false; //function.Cancellable; + pd.CancelClick += cancelClickCallback; + function.Progress += progressCallback; + function.BeginExecute(this, this, finishedCallback); + pd.ShowDialog(this); + pd.Dispose(); + } + + if (hm == null && !cancelled) + { + result = HistoryFunctionResult.SuccessNoOp; + } + else if (hm == null && cancelled) + { + result = HistoryFunctionResult.Cancelled; + } + else + { + result = HistoryFunctionResult.Success; + } + + errorText = null; + } + + catch (HistoryFunctionNonFatalException hfnfex) + { + if (hfnfex.InnerException is OutOfMemoryException) + { + result = HistoryFunctionResult.OutOfMemory; + } + else + { + result = HistoryFunctionResult.NonFatalError; + } + + if (hfnfex.LocalizedErrorText != null) + { + errorText = hfnfex.LocalizedErrorText; + } + else + { + if (hfnfex.InnerException is OutOfMemoryException) + { + errorText = PdnResources.GetString("ExecuteFunction.GenericOutOfMemory"); + } + else + { + errorText = PdnResources.GetString("ExecuteFunction.GenericError"); + } + } + } + + if (errorText != null) + { + Utility.ErrorBox(this, errorText); + } + + if (hm != null) + { + History.PushNewMemento(hm); + } + } + } + + finally + { + if (nullTool) + { + PopNullTool(); + } + } + + return result; + } + + public override void ZoomIn() + { + this.ZoomBasis = ZoomBasis.ScaleFactor; + base.ZoomIn(); + } + + public override void ZoomIn(double factor) + { + this.ZoomBasis = ZoomBasis.ScaleFactor; + base.ZoomIn(factor); + } + + public override void ZoomOut() + { + this.ZoomBasis = ZoomBasis.ScaleFactor; + base.ZoomOut(); + } + + public override void ZoomOut(double factor) + { + this.ZoomBasis = ZoomBasis.ScaleFactor; + base.ZoomOut(factor); + } + + // TODO: + /// + /// Same as PerformAction(Type) except it lets you rename the HistoryMemento's name. + /// + /// + /// + public void PerformAction(Type actionType, string newName, ImageResource icon) + { + using (new WaitCursorChanger(this)) + { + ConstructorInfo ci = actionType.GetConstructor(new Type[] { typeof(DocumentWorkspace) }); + object actionAsObject = ci.Invoke(new object[] { this }); + DocumentWorkspaceAction action = actionAsObject as DocumentWorkspaceAction; + + if (action != null) + { + bool nullTool = false; + + if ((action.ActionFlags & ActionFlags.KeepToolActive) != ActionFlags.KeepToolActive) + { + PushNullTool(); + Update(); + nullTool = true; + } + + try + { + HistoryMemento ha = action.PerformAction(this); + + if (ha != null) + { + ha.Name = newName; + ha.Image = icon; + History.PushNewMemento(ha); + } + } + + finally + { + if (nullTool) + { + PopNullTool(); + } + } + } + } + } + + public event EventHandler ZoomBasisChanging; + protected virtual void OnZoomBasisChanging() + { + if (ZoomBasisChanging != null) + { + ZoomBasisChanging(this, EventArgs.Empty); + } + } + + public event EventHandler ZoomBasisChanged; + protected virtual void OnZoomBasisChanged() + { + if (ZoomBasisChanged != null) + { + ZoomBasisChanged(this, EventArgs.Empty); + } + } + + public ZoomBasis ZoomBasis + { + get + { + return this.zoomBasis; + } + + set + { + if (this.zoomBasis != value) + { + OnZoomBasisChanging(); + this.zoomBasis = value; + + switch (this.zoomBasis) + { + case ZoomBasis.FitToWindow: + ZoomToWindow(); + + // Enable PanelAutoScroll only long enough to recenter the view + PanelAutoScroll = true; + PanelAutoScroll = false; + + // this would be unset by the scalefactor changes in ZoomToWindow + this.zoomBasis = ZoomBasis.FitToWindow; + break; + + case ZoomBasis.ScaleFactor: + PanelAutoScroll = true; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + OnZoomBasisChanged(); + } + } + } + + public void ZoomToSelection() + { + if (Selection.IsEmpty) + { + ZoomToWindow(); + } + else + { + using (PdnRegion region = Selection.CreateRegion()) + { + ZoomToRectangle(region.GetBoundsInt()); + } + } + } + + public void ZoomToRectangle(Rectangle selectionBounds) + { + PointF selectionCenter = new PointF((selectionBounds.Left + selectionBounds.Right + 1) / 2, + (selectionBounds.Top + selectionBounds.Bottom + 1) / 2); + + PointF cornerPosition; + + ScaleFactor zoom = ScaleFactor.Min(ClientRectangleMin.Width, selectionBounds.Width + 2, + ClientRectangleMin.Height, selectionBounds.Height + 2, + ScaleFactor.MinValue); + + // Zoom out to fit the image + ZoomBasis = ZoomBasis.ScaleFactor; + ScaleFactor = zoom; + + cornerPosition = new PointF(selectionCenter.X - (VisibleDocumentRectangleF.Width / 2), + selectionCenter.Y - (VisibleDocumentRectangleF.Height / 2)); + + DocumentScrollPositionF = cornerPosition; + } + + protected override void HandleMouseWheel(Control sender, MouseEventArgs e) + { + if (Control.ModifierKeys == Keys.Control) + { + double mouseDelta = (double)e.Delta / 120.0f; + Rectangle visibleDocBoundsStart = this.VisibleDocumentBounds; + Point mouseDocPt = this.MouseToDocument(sender, new Point(e.X, e.Y)); + RectangleF visibleDocDocRect1 = this.VisibleDocumentRectangleF; + + PointF mouseNPt = new PointF( + (mouseDocPt.X - visibleDocDocRect1.X) / visibleDocDocRect1.Width, + (mouseDocPt.Y - visibleDocDocRect1.Y) / visibleDocDocRect1.Height); + + const double factor = 1.12; + double mouseFactor = Math.Pow(factor, Math.Abs(mouseDelta)); + + if (e.Delta > 0) + { + this.ZoomIn(mouseFactor); + } + else if (e.Delta < 0) + { + this.ZoomOut(mouseFactor); + } + + RectangleF visibleDocDocRect2 = this.VisibleDocumentRectangleF; + + PointF scrollPt2 = new PointF( + mouseDocPt.X - visibleDocDocRect2.Width * mouseNPt.X, + mouseDocPt.Y - visibleDocDocRect2.Height * mouseNPt.Y); + + this.DocumentScrollPositionF = scrollPt2; + + Rectangle visibleDocBoundsEnd = this.VisibleDocumentBounds; + + if (visibleDocBoundsEnd != visibleDocBoundsStart) + { + // Make sure the screen updates, otherwise it can get a little funky looking + this.Update(); + } + } + + base.HandleMouseWheel(sender, e); + } + + public void SelectClosestVisibleLayer(Layer layer) + { + int oldLayerIndex = this.Document.Layers.IndexOf(layer); + int newLayerIndex = oldLayerIndex; + + // find the closest layer that is still visible + for (int i = 0; i < this.Document.Layers.Count; ++i) + { + int lower = oldLayerIndex - i; + int upper = oldLayerIndex + i; + + if (lower >= 0 && lower < this.Document.Layers.Count && ((Layer)this.Document.Layers[lower]).Visible) + { + newLayerIndex = lower; + break; + } + + if (upper >= 0 && upper < this.Document.Layers.Count && ((Layer)this.Document.Layers[upper]).Visible) + { + newLayerIndex = upper; + break; + } + } + + if (newLayerIndex != oldLayerIndex) + { + this.ActiveLayer = (Layer)Document.Layers[newLayerIndex]; + } + } + + public void UpdateRulerSelectionTinting() + { + if (this.RulersEnabled) + { + Rectangle bounds = this.Selection.GetBounds(); + this.SetHighlightRectangle(bounds); + } + } + + private void LayerRemovingHandler(object sender, IndexEventArgs e) + { + Layer layer = (Layer)this.Document.Layers[e.Index]; + layer.PropertyChanging -= LayerPropertyChangingHandler; + layer.PropertyChanged -= LayerPropertyChangedHandler; + + // pick a new valid layer! + int newLayerIndex; + + if (e.Index == this.Document.Layers.Count - 1) + { + newLayerIndex = e.Index - 1; + } + else + { + newLayerIndex = e.Index + 1; + } + + if (newLayerIndex >= 0 && newLayerIndex < this.Document.Layers.Count) + { + this.ActiveLayer = (Layer)this.Document.Layers[newLayerIndex]; + } + else + { + if (this.Document.Layers.Count == 0) + { + this.ActiveLayer = null; + } + else + { + this.ActiveLayer = (Layer)this.Document.Layers[0]; + } + } + } + + private void LayerRemovedHandler(object sender, IndexEventArgs e) + { + } + + private void LayerInsertedHandler(object sender, IndexEventArgs e) + { + Layer layer = (Layer)this.Document.Layers[e.Index]; + this.ActiveLayer = layer; + layer.PropertyChanging += LayerPropertyChangingHandler; + layer.PropertyChanged += LayerPropertyChangedHandler; + } + + private void LayerPropertyChangingHandler(object sender, PropertyEventArgs e) + { + string nameFormat = PdnResources.GetString("LayerPropertyChanging.HistoryMementoNameFormat"); + string haName = string.Format(nameFormat, e.PropertyName); + + LayerPropertyHistoryMemento lpha = new LayerPropertyHistoryMemento( + haName, + PdnResources.GetImageResource("Icons.MenuLayersLayerPropertiesIcon.png"), + this, + this.Document.Layers.IndexOf(sender)); + + this.History.PushNewMemento(lpha); + } + + private void LayerPropertyChangedHandler(object sender, PropertyEventArgs e) + { + Layer layer = (Layer)sender; + + if (!layer.Visible && + layer == this.ActiveLayer && + this.Document.Layers.Count > 1 && + !History.IsExecutingMemento) + { + SelectClosestVisibleLayer(layer); + } + } + + private void ToolPulseTimer_Tick(object sender, EventArgs e) + { + if (FindForm() == null || FindForm().WindowState == FormWindowState.Minimized) + { + return; + } + + if (this.Tool != null && this.Tool.Active) + { + this.Tool.PerformPulse(); + } + } + + protected override void OnLoad(EventArgs e) + { + if (this.appWorkspace == null) + { + throw new InvalidOperationException("Must set the Workspace property"); + } + + base.OnLoad(e); + } + + public event EventHandler ActiveLayerChanging; + protected void OnLayerChanging() + { + if (ActiveLayerChanging != null) + { + ActiveLayerChanging(this, EventArgs.Empty); + } + } + + public event EventHandler ActiveLayerChanged; + protected void OnLayerChanged() + { + this.Focus(); + + if (ActiveLayerChanged != null) + { + ActiveLayerChanged(this, EventArgs.Empty); + } + } + + public Layer ActiveLayer + { + get + { + return this.activeLayer; + } + + set + { + OnLayerChanging(); + + bool deactivateTool; + + if (this.Tool != null) + { + deactivateTool = this.Tool.DeactivateOnLayerChange; + } + else + { + deactivateTool = false; + } + + if (deactivateTool) + { + PushNullTool(); + this.EnableToolPulse = false; + } + + try + { + // Verify that the layer is in the document (sanity checking) + if (this.Document != null) + { + if (value != null && !this.Document.Layers.Contains(value)) + { + throw new InvalidOperationException("ActiveLayer was changed to a layer that is not contained within the Document"); + } + } + else + { + // Document == null + if (value != null) + { + throw new InvalidOperationException("ActiveLayer was set to non-null while Document was null"); + } + } + + // Finally, set the field. + this.activeLayer = value; + } + + finally + { + if (deactivateTool) + { + PopNullTool(); + this.EnableToolPulse = true; + } + } + + OnLayerChanged(); + } + } + + public int ActiveLayerIndex + { + get + { + return Document.Layers.IndexOf(ActiveLayer); + } + + set + { + this.ActiveLayer = (Layer)Document.Layers[value]; + } + } + + public bool EnableToolPulse + { + get + { + return this.toolPulseTimer.Enabled; + } + + set + { + this.toolPulseTimer.Enabled = value; + } + } + + public HistoryStack History + { + get + { + return this.history; + } + } + + public Tool Tool + { + get + { + return this.activeTool; + } + } + + public Type GetToolType() + { + if (Tool != null) + { + return Tool.GetType(); + } + else + { + return null; + } + } + + public void SetToolFromType(Type toolType) + { + if (toolType == GetToolType()) + { + return; + } + else if (toolType == null) + { + SetTool(null); + } + else + { + Tool newTool = CreateTool(toolType); + SetTool(newTool); + } + } + + public void PushNullTool() + { + if (this.nullToolCount == 0) + { + this.preNullTool = GetToolType(); + this.SetTool(null); + this.nullToolCount = 1; + } + else + { + ++this.nullToolCount; + } + } + + public void PopNullTool() + { + --this.nullToolCount; + + if (this.nullToolCount == 0) + { + this.SetToolFromType(this.preNullTool); + this.preNullTool = null; + } + else if (this.nullToolCount < 0) + { + throw new InvalidOperationException("PopNullTool() call was not matched with PushNullTool()"); + } + } + + public Type PreviousActiveToolType + { + get + { + return this.previousActiveToolType; + } + } + + public void SetTool(Tool copyMe) + { + OnToolChanging(); + + if (this.activeTool != null) + { + this.previousActiveToolType = this.activeTool.GetType(); + this.activeTool.CursorChanged -= ToolCursorChangedHandler; + this.activeTool.PerformDeactivate(); + this.activeTool.Dispose(); + this.activeTool = null; + } + + if (copyMe == null) + { + EnableToolPulse = false; + } + else + { + Tracing.LogFeature("SetTool(" + copyMe.GetType().FullName + ")"); + this.activeTool = CreateTool(copyMe.GetType()); + this.activeTool.PerformActivate(); + this.activeTool.CursorChanged += ToolCursorChangedHandler; + + if (this.suspendToolCursorChanges <= 0) + { + Cursor = this.activeTool.Cursor; + } + + EnableToolPulse = true; + } + + OnToolChanged(); + } + + public Tool CreateTool(Type toolType) + { + return DocumentWorkspace.CreateTool(toolType, this); + } + + private static Tool CreateTool(Type toolType, DocumentWorkspace dc) + { + ConstructorInfo ci = toolType.GetConstructor(new Type[] { typeof(DocumentWorkspace) }); + Tool tool = (Tool)ci.Invoke(new object[] { dc }); + return tool; + } + + private static void InitializeTools() + { + // add all the tools + tools = new Type[] + { + typeof(RectangleSelectTool), + typeof(MoveTool), + typeof(LassoSelectTool), + typeof(MoveSelectionTool), + + typeof(EllipseSelectTool), + typeof(ZoomTool), + + typeof(MagicWandTool), + typeof(PanTool), + + typeof(PaintBucketTool), + typeof(GradientTool), + + typeof(PaintBrushTool), + typeof(EraserTool), + typeof(PencilTool), + typeof(ColorPickerTool), + typeof(CloneStampTool), + typeof(RecolorTool), + typeof(TextTool), + + typeof(LineTool), + typeof(RectangleTool), + typeof(RoundedRectangleTool), + typeof(EllipseTool), + typeof(FreeformShapeTool), + }; + } + + private static void InitializeToolInfos() + { + int i = 0; + toolInfos = new ToolInfo[tools.Length]; + + foreach (Type toolType in tools) + { + using (Tool tool = DocumentWorkspace.CreateTool(toolType, null)) + { + toolInfos[i] = tool.Info; + ++i; + } + } + } + + public static Type[] Tools + { + get + { + return (Type[])tools.Clone(); + } + } + + public static ToolInfo[] ToolInfos + { + get + { + return (ToolInfo[])toolInfos.Clone(); + } + } + + public event EventHandler ToolChanging; + protected void OnToolChanging() + { + if (ToolChanging != null) + { + ToolChanging(this, EventArgs.Empty); + } + } + + public event EventHandler ToolChanged; + protected void OnToolChanged() + { + if (ToolChanged != null) + { + ToolChanged(this, EventArgs.Empty); + } + } + + private void ToolCursorChangedHandler(object sender, EventArgs e) + { + if (this.suspendToolCursorChanges <= 0) + { + Cursor = this.activeTool.Cursor; + } + } + + // Note: static tool data is removed whenever the Document changes + // TODO: shouldn't this be moved to the Tool class somehow? + public object GetStaticToolData(Type toolType) + { + return staticToolData[toolType]; + } + + public void SetStaticToolData(Type toolType, object data) + { + staticToolData[toolType] = data; + } + + public AppWorkspace AppWorkspace + { + get + { + return this.appWorkspace; + } + + set + { + this.appWorkspace = value; + } + } + + public Selection Selection + { + get + { + return this.selection; + } + } + + public bool EnableOutlineAnimation + { + get + { + return this.selectionRenderer.EnableOutlineAnimation; + } + + set + { + this.selectionRenderer.EnableOutlineAnimation = value; + } + } + + public bool EnableSelectionOutline + { + get + { + return this.selectionRenderer.EnableSelectionOutline; + } + + set + { + this.selectionRenderer.EnableSelectionOutline = value; + } + } + + public bool EnableSelectionTinting + { + get + { + return this.selectionRenderer.EnableSelectionTinting; + } + + set + { + this.selectionRenderer.EnableSelectionTinting = value; + } + } + + public void ResetOutlineWhiteOpacity() + { + this.selectionRenderer.ResetOutlineWhiteOpacity(); + } + + public event EventHandler FilePathChanged; + protected virtual void OnFilePathChanged() + { + if (FilePathChanged != null) + { + FilePathChanged(this, EventArgs.Empty); + } + } + + public string FilePath + { + get + { + return this.filePath; + } + } + + public string GetFriendlyName() + { + string friendlyName; + + if (this.filePath != null) + { + friendlyName = Path.GetFileName(this.filePath); + } + else + { + friendlyName = PdnResources.GetString("Untitled.FriendlyName"); + } + + return friendlyName; + } + + public FileType FileType + { + get + { + return this.fileType; + } + } + + public SaveConfigToken SaveConfigToken + { + get + { + if (this.saveConfigToken == null) + { + return null; + } + else + { + return (SaveConfigToken)this.saveConfigToken.Clone(); + } + } + } + + public event EventHandler SaveOptionsChanged; + protected virtual void OnSaveOptionsChanged() + { + if (SaveOptionsChanged != null) + { + SaveOptionsChanged(this, EventArgs.Empty); + } + } + + /// + /// Sets the FileType and SaveConfigToken parameters that are used if the + /// user chooses "Save" from the File menu. These are not used by the + /// DocumentControl class and should be used by whoever actually goes + /// to save the Document instance. + /// + /// + /// + public void SetDocumentSaveOptions(string newFilePath, FileType newFileType, SaveConfigToken newSaveConfigToken) + { + this.filePath = newFilePath; + OnFilePathChanged(); + + this.fileType = newFileType; + + if (newSaveConfigToken == null) + { + this.saveConfigToken = null; + } + else + { + this.saveConfigToken = (SaveConfigToken)newSaveConfigToken.Clone(); + } + + OnSaveOptionsChanged(); + } + + public void GetDocumentSaveOptions(out string filePathResult, out FileType fileTypeResult, out SaveConfigToken saveConfigTokenResult) + { + filePathResult = this.filePath; + fileTypeResult = this.fileType; + + if (this.saveConfigToken == null) + { + saveConfigTokenResult = null; + } + else + { + saveConfigTokenResult = (SaveConfigToken)this.saveConfigToken.Clone(); + } + } + + private bool isScratchSurfaceBorrowed = false; + private string borrowScratchSurfaceReason = string.Empty; + + /// The scratch, stencil, accumulation, whatever buffer. This is used by many parts + /// of Paint.NET as a temporary area for which to store data. + /// This surface is 'owned' by any Tool that is active. If you want to use this you + /// must first deactivate the Tool using PushNullTool() and then reactivate it when + /// you are finished by calling PopNullTool(). + /// Tools should use Tool.ScratchSurface instead of these API's. + + public Surface BorrowScratchSurface(string reason) + { + if (this.isScratchSurfaceBorrowed) + { + throw new InvalidOperationException( + "ScratchSurface already borrowed: '" + + this.borrowScratchSurfaceReason + + "' (trying to borrow for: '" + reason + "')"); + } + + Tracing.Ping("Borrowing scratchSurface: " + reason); + this.isScratchSurfaceBorrowed = true; + this.borrowScratchSurfaceReason = reason; + return this.scratchSurface; + } + + public void ReturnScratchSurface(Surface borrowedScratchSurface) + { + if (!this.isScratchSurfaceBorrowed) + { + throw new InvalidOperationException("ScratchSurface wasn't borrowed"); + } + + if (this.scratchSurface != borrowedScratchSurface) + { + throw new InvalidOperationException("returned ScratchSurface doesn't match the real one"); + } + + Tracing.Ping("Returning scratchSurface: " + this.borrowScratchSurfaceReason); + this.isScratchSurfaceBorrowed = false; + this.borrowScratchSurfaceReason = string.Empty; + } + + /// + /// Updates any pertinent EXIF tags, such as "Creation Software", to be + /// relevant or up-to-date. + /// + /// + private void UpdateExifTags(Document document) + { + // We want it to say "Creation Software: Paint.NET vX.Y" + // I have verified that other image editing software overwrites this tag, + // and does not just add it when it does not exist. + PropertyItem pi = Exif.CreateAscii(ExifTagID.Software, PdnInfo.GetProductName(false)); + document.Metadata.ReplaceExifValues(ExifTagID.Software, new PropertyItem[1] { pi }); + } + + private ZoomBasis savedZb; + private ScaleFactor savedSf; + private int savedAli; + protected override void OnDocumentChanging(Document newDocument) + { + base.OnDocumentChanging(newDocument); + + this.savedZb = this.ZoomBasis; + this.savedSf = ScaleFactor; + + if (this.ActiveLayer != null) + { + this.savedAli = ActiveLayerIndex; + } + else + { + this.savedAli = -1; + } + + if (newDocument != null) + { + UpdateExifTags(newDocument); + } + + if (this.Document != null) + { + foreach (Layer layer in this.Document.Layers) + { + layer.PropertyChanging -= LayerPropertyChangingHandler; + layer.PropertyChanged -= LayerPropertyChangedHandler; + } + + this.Document.Layers.RemovingAt -= LayerRemovingHandler; + this.Document.Layers.RemovedAt -= LayerRemovedHandler; + this.Document.Layers.Inserted -= LayerInsertedHandler; + } + + this.staticToolData.Clear(); + + PushNullTool(); // matching Pop is in OnDocumetChanged() + ActiveLayer = null; + + if (this.scratchSurface != null) + { + if (this.isScratchSurfaceBorrowed) + { + throw new InvalidOperationException("scratchSurface is currently borrowed: " + this.borrowScratchSurfaceReason); + } + + if (newDocument == null || newDocument.Size != this.scratchSurface.Size) + { + this.scratchSurface.Dispose(); + this.scratchSurface = null; + } + } + + if (!Selection.IsEmpty) + { + Selection.Reset(); + } + } + + protected override void OnDocumentChanged() + { + // if the ActiveLayer is not in this new document, then + // we try to set ActiveLayer to the first layer in this + // new document. But if the document contains no layers, + // or is null, we just null the ActiveLayer. + if (this.Document == null) + { + this.ActiveLayer = null; + } + else + { + if (this.activeTool != null) + { + throw new InvalidOperationException("Tool was not deactivated while Document was being changed"); + } + + if (this.scratchSurface != null) + { + if (this.isScratchSurfaceBorrowed) + { + throw new InvalidOperationException("scratchSurface is currently borrowed: " + this.borrowScratchSurfaceReason); + } + + if (Document == null || this.scratchSurface.Size != Document.Size) + { + this.scratchSurface.Dispose(); + this.scratchSurface = null; + } + } + + this.scratchSurface = new Surface(this.Document.Size); + + this.Selection.ClipRectangle = this.Document.Bounds; + + foreach (Layer layer in this.Document.Layers) + { + layer.PropertyChanging += LayerPropertyChangingHandler; + layer.PropertyChanged += LayerPropertyChangedHandler; + } + + this.Document.Layers.RemovingAt += LayerRemovingHandler; + this.Document.Layers.RemovedAt += LayerRemovedHandler; + this.Document.Layers.Inserted += LayerInsertedHandler; + + if (!this.Document.Layers.Contains(this.ActiveLayer)) + { + if (this.Document.Layers.Count > 0) + { + if (savedAli >= 0 && savedAli < this.Document.Layers.Count) + { + this.ActiveLayer = (Layer)this.Document.Layers[savedAli]; + } + else + { + this.ActiveLayer = (Layer)this.Document.Layers[0]; + } + } + else + { + this.ActiveLayer = null; + } + } + + // we invalidate each layer so that the layer previews refresh themselves + foreach (Layer layer in this.Document.Layers) + { + layer.Invalidate(); + } + + bool oldDirty = this.Document.Dirty; + this.Document.Invalidate(); + this.Document.Dirty = oldDirty; + + this.ZoomBasis = this.savedZb; + if (this.savedZb == ZoomBasis.ScaleFactor) + { + ScaleFactor = this.savedSf; + } + } + + PopNullTool(); + AutoScrollPosition = new Point(0, 0); + + base.OnDocumentChanged(); + } + + /// + /// Takes the current Document from this DocumentWorkspace instance and adds it to the MRU list. + /// + /// + public void AddToMruList() + { + using (new PushNullToolMode(this)) + { + string fullFileName = Path.GetFullPath(this.FilePath); + int edgeLength = AppWorkspace.MostRecentFiles.IconSize; + Surface thumb1 = RenderThumbnail(edgeLength, true, true); + + // Put it inside a square bitmap + Surface thumb = new Surface(4 + edgeLength, 4 + edgeLength); + + thumb.Clear(ColorBgra.Transparent); + + Rectangle dstRect = new Rectangle((thumb.Width - thumb1.Width) / 2, + (thumb.Height - thumb1.Height) / 2, thumb1.Width, thumb1.Height); + + thumb.CopySurface(thumb1, dstRect.Location); + + using (RenderArgs ra = new RenderArgs(thumb)) + { + // Draw black border + Rectangle borderRect = new Rectangle(dstRect.Left - 1, dstRect.Top - 1, dstRect.Width + 2, dstRect.Height + 2); + --borderRect.Width; + --borderRect.Height; + ra.Graphics.DrawRectangle(Pens.Black, borderRect); + + Rectangle shadowRect = Rectangle.Inflate(borderRect, 1, 1); + ++shadowRect.Width; + ++shadowRect.Height; + Utility.DrawDropShadow1px(ra.Graphics, shadowRect); + + thumb1.Dispose(); + thumb1 = null; + + MostRecentFile mrf = new MostRecentFile(fullFileName, Utility.FullCloneBitmap(ra.Bitmap)); + + if (AppWorkspace.MostRecentFiles.Contains(fullFileName)) + { + AppWorkspace.MostRecentFiles.Remove(fullFileName); + } + + AppWorkspace.MostRecentFiles.Add(mrf); + AppWorkspace.MostRecentFiles.SaveMruList(); + } + } + } + + /// + /// Shows an OpenFileDialog or SaveFileDialog and populates the InitialDirectory from the global + /// settings repository if possible. + /// + /// The FileDialog to show. + /// + /// The FileDialog should already have its InitialDirectory populated as a suggestion of where to start. + /// + public static DialogResult ShowFileDialog(Control owner, IFileDialog fd) + { + string initialDirectory = Settings.CurrentUser.GetString(SettingNames.LastFileDialogDirectory, fd.InitialDirectory); + + // TODO: spawn this in a background thread, if it doesn't respond within ~500ms?, assume the dir doesn't exist + bool dirExists = false; + + try + { + DirectoryInfo dirInfo = new DirectoryInfo(initialDirectory); + + using (new WaitCursorChanger(owner)) + { + dirExists = dirInfo.Exists; + + if (!dirInfo.Exists) + { + initialDirectory = fd.InitialDirectory; + } + } + } + + catch (Exception) + { + initialDirectory = fd.InitialDirectory; + } + + fd.InitialDirectory = initialDirectory; + + OurFileDialogUICallbacks ouc = new OurFileDialogUICallbacks(); + DialogResult result = fd.ShowDialog(owner, ouc); + + if (result == DialogResult.OK) + { + string fileName; + + if (fd is IFileOpenDialog) + { + string[] fileNames = ((IFileOpenDialog)fd).FileNames; + + if (fileNames.Length > 0) + { + fileName = fileNames[0]; + } + else + { + fileName = null; + } + } + else if (fd is IFileSaveDialog) + { + fileName = ((IFileSaveDialog)fd).FileName; + } + else + { + throw new InvalidOperationException(); + } + + if (fileName != null) + { + string newDir = Path.GetDirectoryName(fileName); + Settings.CurrentUser.SetString(SettingNames.LastFileDialogDirectory, newDir); + } + else + { + throw new FileNotFoundException(); + } + } + + return result; + } + + private sealed class OurFileDialogUICallbacks + : IFileDialogUICallbacks + { + public FileOverwriteAction ShowOverwritePrompt(IWin32Window owner, string pathName) + { + FileOverwriteAction returnVal; + + string title = PdnResources.GetString("SaveAs.OverwriteConfirmation.Title"); + string textFormat = PdnResources.GetString("SaveAs.OverwriteConfirmation.Text.Format"); + string fileName; + + try + { + fileName = Path.GetFileName(pathName); + } + + catch (Exception) + { + fileName = pathName; + } + + string text = string.Format(textFormat, fileName); + + DialogResult result = MessageBox.Show(owner, text, title, MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2); + + switch (result) + { + case DialogResult.Yes: + returnVal = FileOverwriteAction.Overwrite; + break; + + case DialogResult.No: + returnVal = FileOverwriteAction.Cancel; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return returnVal; + } + + public bool ShowError(IWin32Window owner, string filePath, Exception ex) + { + if (ex is PathTooLongException) + { + string title = PdnInfo.GetBareProductName(); + string message = PdnResources.GetString("FileDialog.PathTooLongException.Message"); + + MessageBox.Show(owner, message, title, MessageBoxButtons.OK, MessageBoxIcon.Error); + + return true; + } + else + { + return false; + } + } + + public IFileTransferProgressEvents CreateFileTransferProgressEvents() + { + return new OurProgressEvents(); + } + } + + private sealed class OurProgressEvents + : IFileTransferProgressEvents + { + private TransferProgressDialog progressDialog; + private ICancelable cancelSink; + private int itemCount = 0; + private int itemOrdinal = 0; + private string itemName = string.Empty; + private long totalWork; + private long totalProgress; + private const int maxPBValue = 200; // granularity of progress bar. 100 means 1%, 200 means 0.5%, etc. + private bool cancelRequested = false; + + private ManualResetEvent operationEnded = new ManualResetEvent(false); + + public OurProgressEvents() + { + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "cancelSink")] + public void BeginOperation(IWin32Window owner, EventHandler callWhenUIShown, ICancelable cancelSink) + { + if (this.progressDialog != null) + { + throw new InvalidOperationException("Operation already in progress"); + } + + this.progressDialog = new TransferProgressDialog(); + this.progressDialog.Text = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.TransferProgress.Title"); + this.progressDialog.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileOpenIcon.png").Reference); + this.progressDialog.ItemText = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemText.Initializing"); + this.progressDialog.ProgressBar.Style = ProgressBarStyle.Marquee; + this.progressDialog.ProgressBar.Maximum = maxPBValue; + + this.progressDialog.CancelClicked += + delegate(object sender, EventArgs e) + { + this.cancelRequested = true; + this.cancelSink.RequestCancel(); + UpdateUI(); + }; + + EventHandler progressDialog_Shown = + delegate(object sender, EventArgs e) + { + callWhenUIShown(this, EventArgs.Empty); + }; + + this.cancelSink = cancelSink; + this.itemOrdinal = 0; + this.cancelRequested = false; + this.itemName = string.Empty; + this.itemCount = 0; + this.itemOrdinal = 0; + this.totalProgress = 0; + this.totalWork = 0; + + this.progressDialog.Shown += progressDialog_Shown; + this.progressDialog.ShowDialog(owner); + this.progressDialog.Shown -= progressDialog_Shown; + + this.progressDialog.Dispose(); + this.progressDialog = null; + this.cancelSink = null; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "itemCount")] + public void SetItemCount(int itemCount) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(SetItemCount), new object[] { itemCount }); + } + else + { + this.itemCount = itemCount; + UpdateUI(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "itemOrdinal")] + public void SetItemOrdinal(int itemOrdinal) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(SetItemOrdinal), new object[] { itemOrdinal }); + } + else + { + this.itemOrdinal = itemOrdinal; + this.totalWork = 0; + this.totalProgress = 0; + UpdateUI(); + } + } + + public void SetItemInfo(string itemInfo) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(SetItemInfo), new object[] { itemInfo }); + } + else + { + this.itemName = itemInfo; + UpdateUI(); + } + } + + public void BeginItem() + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(BeginItem), null); + } + else + { + this.progressDialog.ProgressBar.Style = ProgressBarStyle.Continuous; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "totalWork")] + public void SetItemWorkTotal(long totalWork) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(SetItemWorkTotal), new object[] { totalWork }); + } + else + { + this.totalWork = totalWork; + UpdateUI(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "totalProgress")] + public void SetItemWorkProgress(long totalProgress) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(SetItemWorkProgress), new object[] { totalProgress }); + } + else + { + this.totalProgress = totalProgress; + UpdateUI(); + } + } + + public void EndItem(WorkItemResult result) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(EndItem), new object[] { result }); + } + else + { + } + } + + public void EndOperation(OperationResult result) + { + if (this.progressDialog.InvokeRequired) + { + this.progressDialog.BeginInvoke(new Procedure(EndOperation), new object[] { result }); + } + else + { + this.progressDialog.Close(); + } + } + + public WorkItemFailureAction ReportItemFailure(Exception ex) + { + if (this.progressDialog.InvokeRequired) + { + object result = this.progressDialog.Invoke( + new Function(ReportItemFailure), + new object[] { ex }); + + return (WorkItemFailureAction)result; + } + else + { + WorkItemFailureAction result; + result = ShowFileTransferFailedDialog(ex); + return result; + } + } + + private WorkItemFailureAction ShowFileTransferFailedDialog(Exception ex) + { + WorkItemFailureAction result; + Icon formIcon = this.progressDialog.Icon; + + string formTitle = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemFailureDialog.Title"); + + Image taskImage = PdnResources.GetImageResource("Icons.WarningIcon.png").Reference; + + string introTextFormat = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemFailureDialog.IntroText.Format"); + string introText = string.Format(introTextFormat, ex.Message); + + TaskButton retryTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuImageRotate90CWIcon.png").Reference, + PdnResources.GetString("DocumentWorkspace.ShowFileDialog.RetryTB.ActionText"), + PdnResources.GetString("DocumentWorkspace.ShowFileDialog.RetryTB.ExplanationText")); + + TaskButton skipTB = new TaskButton( + PdnResources.GetImageResource("Icons.HistoryFastForwardIcon.png").Reference, + PdnResources.GetString("DocumentWorkspace.ShowFileDialog.SkipTB.ActionText"), + PdnResources.GetString("DocumentWorkspace.ShowFileDialog.SkipTB.ExplanationText")); + + TaskButton cancelTB = new TaskButton( + PdnResources.GetImageResource("Icons.CancelIcon.png").Reference, + PdnResources.GetString("DocumentWorkspace.ShowFileDialog.CancelTB.ActionText"), + PdnResources.GetString("DocumentWorkspace.ShowFileDialog.CancelTB.ExplanationText")); + + List taskButtons = new List(); + taskButtons.Add(retryTB); + + // Only have the Skip button if there is more than 1 item being transferred. + // If only 1 item is begin transferred, Skip and Cancel are essentially synonymous. + if (this.itemCount > 1) + { + taskButtons.Add(skipTB); + } + + taskButtons.Add(cancelTB); + + int width96 = (TaskDialog.DefaultPixelWidth96Dpi * 4) / 3; // 33% wider + + TaskButton clickedTB = TaskDialog.Show( + this.progressDialog, + formIcon, + formTitle, + taskImage, + true, + introText, + taskButtons.ToArray(), + retryTB, + cancelTB, + width96); + + if (clickedTB == retryTB) + { + result = WorkItemFailureAction.RetryItem; + } + else if (clickedTB == skipTB) + { + result = WorkItemFailureAction.SkipItem; + } + else + { + result = WorkItemFailureAction.CancelOperation; + } + + return result; + } + + private void UpdateUI() + { + int itemCount2 = Math.Max(1, this.itemCount); + + double startValue = (double)this.itemOrdinal / (double)itemCount2; + double endValue = (double)(this.itemOrdinal + 1) / (double)itemCount2; + + long totalWork2 = Math.Max(1, this.totalWork); + double lerp = (double)this.totalProgress / (double)totalWork2; + + double newValue = Utility.Lerp(startValue, endValue, lerp); + int newValueInt = (int)Math.Ceiling(maxPBValue * newValue); + + if (this.cancelRequested) + { + this.progressDialog.CancelEnabled = false; + this.progressDialog.ItemText = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ItemText.Canceling"); + this.progressDialog.OperationProgress = string.Empty; + this.progressDialog.ProgressBar.Style = ProgressBarStyle.Marquee; + } + else + { + this.progressDialog.CancelEnabled = true; + this.progressDialog.ItemText = this.itemName; + string progressFormat = PdnResources.GetString("DocumentWorkspace.ShowFileDialog.ProgressText.Format"); + string progressText = string.Format(progressFormat, this.itemOrdinal + 1, this.itemCount); + this.progressDialog.OperationProgress = progressText; + this.progressDialog.ProgressBar.Style = ProgressBarStyle.Continuous; + this.progressDialog.ProgressBar.Value = newValueInt; + } + } + } + + public static DialogResult ChooseFile(Control parent, out string fileName) + { + return ChooseFile(parent, out fileName, null); + } + + public static DialogResult ChooseFile(Control parent, out string fileName, string startingDir) + { + string[] fileNames; + DialogResult result = ChooseFiles(parent, out fileNames, false, startingDir); + + if (result == DialogResult.OK) + { + fileName = fileNames[0]; + } + else + { + fileName = null; + } + + return result; + } + + public static DialogResult ChooseFiles(Control owner, out string[] fileNames, bool multiselect) + { + return ChooseFiles(owner, out fileNames, multiselect, null); + } + + public static DialogResult ChooseFiles(Control owner, out string[] fileNames, bool multiselect, string startingDir) + { + FileTypeCollection fileTypes = FileTypes.GetFileTypes(); + + using (IFileOpenDialog ofd = SystemLayer.CommonDialogs.CreateFileOpenDialog()) + { + if (startingDir != null) + { + ofd.InitialDirectory = startingDir; + } + else + { + ofd.InitialDirectory = GetDefaultSavePath(); + } + + ofd.CheckFileExists = true; + ofd.CheckPathExists = true; + ofd.Multiselect = multiselect; + + ofd.Filter = fileTypes.ToString(true, PdnResources.GetString("FileDialog.Types.AllImages"), false, true); + ofd.FilterIndex = 0; + + DialogResult result = ShowFileDialog(owner, ofd); + + if (result == DialogResult.OK) + { + fileNames = ofd.FileNames; + } + else + { + fileNames = new string[0]; + } + + return result; + } + } + + /// + /// Use this to get a save config token. You should already know the filename and file type. + /// An existing save config token is optional and will be used to pre-populate the config dialog. + /// + /// + /// + /// + /// false if the user cancelled, otherwise true + private bool GetSaveConfigToken( + FileType currentFileType, + SaveConfigToken currentSaveConfigToken, + out SaveConfigToken newSaveConfigToken, + Surface saveScratchSurface) + { + if (currentFileType.SupportsConfiguration) + { + using (SaveConfigDialog scd = new SaveConfigDialog()) + { + scd.ScratchSurface = saveScratchSurface; + + ProgressEventHandler peh = delegate(object sender, ProgressEventArgs e) + { + if (e.Percent < 0 || e.Percent >= 100) + { + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar(); + } + else + { + AppWorkspace.Widgets.StatusBarProgress.SetProgressStatusBar(e.Percent); + } + }; + + //if (currentFileType.SavesWithProgress) + { + scd.Progress += peh; + } + + scd.Document = Document; + scd.FileType = currentFileType; + + SaveConfigToken token = currentFileType.GetLastSaveConfigToken(); + if (currentSaveConfigToken != null && + token.GetType() == currentSaveConfigToken.GetType()) + { + scd.SaveConfigToken = currentSaveConfigToken; + } + + scd.EnableInstanceOpacity = false; + + // show configuration/preview dialog + DialogResult dr = scd.ShowDialog(this); + + //if (currentFileType.SavesWithProgress) + { + scd.Progress -= peh; + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar(); + } + + if (dr == DialogResult.OK) + { + newSaveConfigToken = scd.SaveConfigToken; + return true; + } + else + { + newSaveConfigToken = null; + return false; + } + } + } + else + { + newSaveConfigToken = currentFileType.GetLastSaveConfigToken(); + return true; + } + } + + /// + /// Used to set the file name, file type, and save config token + /// + /// + /// + /// + /// true if the user clicked through and accepted, or false if they cancelled at any point + private bool DoSaveAsDialog( + out string newFileName, + out FileType newFileType, + out SaveConfigToken newSaveConfigToken, + Surface saveScratchSurface) + { + FileTypeCollection fileTypes = FileTypes.GetFileTypes(); + + using (IFileSaveDialog sfd = SystemLayer.CommonDialogs.CreateFileSaveDialog()) + { + sfd.AddExtension = true; + sfd.CheckPathExists = true; + sfd.OverwritePrompt = true; + string filter = fileTypes.ToString(false, null, true, false); + sfd.Filter = filter; + + string localFileName; + FileType localFileType; + SaveConfigToken localSaveConfigToken; + GetDocumentSaveOptions(out localFileName, out localFileType, out localSaveConfigToken); + + if (Document.Layers.Count > 1 && + localFileType != null && + !localFileType.SupportsLayers) + { + localFileType = null; + } + + if (localFileType == null) + { + if (Document.Layers.Count == 1) + { + localFileType = PdnFileTypes.Png; + } + else + { + localFileType = PdnFileTypes.Pdn; + } + + localFileName = Path.ChangeExtension(localFileName, localFileType.DefaultExtension); + } + + if (localFileName == null) + { + string name = GetDefaultSaveName(); + string newName = Path.ChangeExtension(name, localFileType.DefaultExtension); + localFileName = Path.Combine(GetDefaultSavePath(), newName); + } + + // If the filename is only an extension (i.e. ".lmnop") then we must treat it specially + string fileNameOnly = Path.GetFileName(localFileName); + if (fileNameOnly.Length >= 1 && fileNameOnly[0] == '.') + { + sfd.FileName = localFileName; + } + else + { + sfd.FileName = Path.ChangeExtension(localFileName, null); + } + + sfd.FilterIndex = 1 + fileTypes.IndexOfFileType(localFileType); + sfd.InitialDirectory = Path.GetDirectoryName(localFileName); + sfd.Title = PdnResources.GetString("SaveAsDialog.Title"); + + DialogResult dr1 = ShowFileDialog(this, sfd); + bool result; + + if (dr1 != DialogResult.OK) + { + result = false; + } + else + { + localFileName = sfd.FileName; + FileType fileType2 = fileTypes[sfd.FilterIndex - 1]; + result = GetSaveConfigToken(fileType2, localSaveConfigToken, out localSaveConfigToken, saveScratchSurface); + localFileType = fileType2; + } + + if (result) + { + newFileName = localFileName; + newFileType = localFileType; + newSaveConfigToken = localSaveConfigToken; + } + else + { + newFileName = null; + newFileType = null; + newSaveConfigToken = null; + } + + return result; + } + } + + /// + /// Warns the user that we need to flatten the image. + /// + /// Returns DialogResult.Yes if they want to proceed or DialogResult.No if they don't. + private DialogResult WarnAboutFlattening() + { + Icon formIcon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileSaveIcon.png").Reference); + string formTitle = PdnResources.GetString("WarnAboutFlattening.Title"); + + string introText = PdnResources.GetString("WarnAboutFlattening.IntroText"); + Image taskImage = null; + + TaskButton flattenTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuImageFlattenIcon.png").Reference, + PdnResources.GetString("WarnAboutFlattening.FlattenTB.ActionText"), + PdnResources.GetString("WarnAboutFlattening.FlattenTB.ExplanationText")); + + TaskButton cancelTB = new TaskButton( + TaskButton.Cancel.Image, + PdnResources.GetString("WarnAboutFlattening.CancelTB.ActionText"), + PdnResources.GetString("WarnAboutFlattening.CancelTB.ExplanationText")); + + TaskButton clickedTB = TaskDialog.Show( + AppWorkspace, + formIcon, + formTitle, + taskImage, + true, + introText, + new TaskButton[] { flattenTB, cancelTB }, + flattenTB, + cancelTB, + (TaskDialog.DefaultPixelWidth96Dpi * 5) / 4); + + if (clickedTB == flattenTB) + { + return DialogResult.Yes; + } + else + { + return DialogResult.No; + } + } + + private static string GetDefaultSaveName() + { + return PdnResources.GetString("Untitled.FriendlyName"); + } + + private static string GetDefaultSavePath() + { + string myPics; + + try + { + myPics = Shell.GetVirtualPath(VirtualFolderName.UserPictures, false); + DirectoryInfo dirInfo = new DirectoryInfo(myPics); // validate + } + + catch (Exception) + { + myPics = ""; + } + + string dir = Settings.CurrentUser.GetString(SettingNames.LastFileDialogDirectory, null); + + if (dir == null) + { + dir = myPics; + } + else + { + try + { + DirectoryInfo dirInfo = new DirectoryInfo(dir); + + if (!dirInfo.Exists) + { + dir = myPics; + } + } + + catch (Exception) + { + dir = myPics; + } + } + + return dir; + } + + public bool DoSave() + { + return DoSave(false); + } + + /// + /// Does the dirty work for a File->Save operation. If any of the "Save Options" in the + /// DocumentWorkspace are null, this will call DoSaveAs(). If the image has more than 1 + /// layer but the file type they want to save with does not support layers, then it will + /// ask the user about flattening the image. + /// + /// + /// If true, will ask the user about flattening if the workspace's saveFileType does not + /// support layers and the image has more than 1 layer. + /// If false, then DoSaveAs will be called and the fileType will be prepopulated with + /// the .PDN type. + /// + /// true if the file was saved, false if the user cancelled + protected bool DoSave(bool tryToFlatten) + { + using (new PushNullToolMode(this)) + { + string newFileName; + FileType newFileType; + SaveConfigToken newSaveConfigToken; + + GetDocumentSaveOptions(out newFileName, out newFileType, out newSaveConfigToken); + + // if they haven't specified a filename, then revert to "Save As" behavior + if (newFileName == null) + { + return DoSaveAs(); + } + + // if we have a filename but no file type, try to infer the file type + if (newFileType == null) + { + FileTypeCollection fileTypes = FileTypes.GetFileTypes(); + string ext = Path.GetExtension(newFileName); + int index = fileTypes.IndexOfExtension(ext); + FileType inferredFileType = fileTypes[index]; + newFileType = inferredFileType; + } + + // if the image has more than 1 layer but is saving with a file type that + // does not support layers, then we must ask them if we may flatten the + // image first + if (Document.Layers.Count > 1 && !newFileType.SupportsLayers) + { + if (!tryToFlatten) + { + return DoSaveAs(); + } + else + { + DialogResult dr = WarnAboutFlattening(); + + if (dr == DialogResult.Yes) + { + ExecuteFunction(new FlattenFunction()); + } + else + { + return false; + } + } + } + + // get the configuration! + if (newSaveConfigToken == null) + { + Surface scratch = BorrowScratchSurface(this.GetType().Name + ".DoSave() calling GetSaveConfigToken()"); + + bool result; + try + { + result = GetSaveConfigToken(newFileType, newSaveConfigToken, out newSaveConfigToken, scratch); + } + + finally + { + ReturnScratchSurface(scratch); + } + + if (!result) + { + return false; + } + } + + // At this point fileName, fileType, and saveConfigToken must all be non-null + + // if the document supports custom headers, embed a thumbnail in there + if (newFileType.SupportsCustomHeaders) + { + using (new WaitCursorChanger(this)) + { + Utility.GCFullCollect(); + const int maxDim = 256; + + Surface thumb; + Surface flattened = BorrowScratchSurface(this.GetType().Name + ".DoSave() preparing embedded thumbnail"); + + try + { + Document.Flatten(flattened); + + if (Document.Width > maxDim || Document.Height > maxDim) + { + int width; + int height; + + if (Document.Width > Document.Height) + { + width = maxDim; + height = (Document.Height * maxDim) / Document.Width; + } + else + { + height = maxDim; + width = (Document.Width * maxDim) / Document.Height; + } + + int thumbWidth = Math.Max(1, width); + int thumbHeight = Math.Max(1, height); + + thumb = new Surface(thumbWidth, thumbHeight); + thumb.SuperSamplingFitSurface(flattened); + } + else + { + thumb = new Surface(flattened.Size); + thumb.CopySurface(flattened); + } + } + + finally + { + ReturnScratchSurface(flattened); + } + + Document thumbDoc = new Document(thumb.Width, thumb.Height); + BitmapLayer thumbLayer = new BitmapLayer(thumb); + BitmapLayer backLayer = new BitmapLayer(thumb.Width, thumb.Height); + backLayer.Surface.Clear(ColorBgra.Transparent); + thumb.Dispose(); + thumbDoc.Layers.Add(backLayer); + thumbDoc.Layers.Add(thumbLayer); + MemoryStream thumbPng = new MemoryStream(); + PropertyBasedSaveConfigToken pngToken = PdnFileTypes.Png.CreateDefaultSaveConfigToken(); + PdnFileTypes.Png.Save(thumbDoc, thumbPng, pngToken, null, null, false); + byte[] thumbBytes = thumbPng.ToArray(); + + string thumbString = Convert.ToBase64String(thumbBytes, Base64FormattingOptions.None); + thumbDoc.Dispose(); + + string thumbXml = ""; + Document.CustomHeaders = thumbXml; + } + } + + // save! + bool success = false; + Stream stream = null; + + try + { + stream = (Stream)new FileStream(newFileName, FileMode.Create, FileAccess.Write); + + using (new WaitCursorChanger(this)) + { + Utility.GCFullCollect(); + + SaveProgressDialog sd = new SaveProgressDialog(this); + Surface scratch = BorrowScratchSurface(this.GetType().Name + ".DoSave() handing off scratch surface to SaveProgressDialog.Save()"); + + try + { + sd.Save(stream, Document, newFileType, newSaveConfigToken, scratch); + } + + finally + { + ReturnScratchSurface(scratch); + } + + success = true; + + this.lastSaveTime = DateTime.Now; + + stream.Close(); + stream = null; + } + } + + catch (UnauthorizedAccessException) + { + Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.UnauthorizedAccessException")); + } + + catch (SecurityException) + { + Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.SecurityException")); + } + + catch (DirectoryNotFoundException) + { + Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.DirectoryNotFoundException")); + } + + catch (IOException) + { + Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.IOException")); + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.OutOfMemoryException")); + } + +#if !DEBUG + catch (Exception) + { + Utility.ErrorBox(this, PdnResources.GetString("SaveImage.Error.Exception")); + } +#endif + + finally + { + if (stream != null) + { + stream.Close(); + stream = null; + } + } + + if (success) + { + Shell.AddToRecentDocumentsList(newFileName); + } + else + { + return false; + } + + // reset the dirty bit so they won't be asked to save on quitting + Document.Dirty = false; + + // some misc. book keeping ... + AddToMruList(); + + // and finally, shout happiness by way of ... + return true; + } + } + + /// + /// Does the grunt work to do a File->Save As operation. + /// + /// true if the file was saved correctly, false if the user cancelled + public bool DoSaveAs() + { + using (new PushNullToolMode(this)) + { + string newFileName; + FileType newFileType; + SaveConfigToken newSaveConfigToken; + + Surface scratch = BorrowScratchSurface(this.GetType() + ".DoSaveAs() handing off scratch surface to DoSaveAsDialog()"); + + bool result; + try + { + result = DoSaveAsDialog(out newFileName, out newFileType, out newSaveConfigToken, scratch); + } + + finally + { + ReturnScratchSurface(scratch); + } + + if (result) + { + string oldFileName; + FileType oldFileType; + SaveConfigToken oldSaveConfigToken; + + GetDocumentSaveOptions(out oldFileName, out oldFileType, out oldSaveConfigToken); + SetDocumentSaveOptions(newFileName, newFileType, newSaveConfigToken); + + bool result2 = DoSave(true); + + if (!result2) + { + SetDocumentSaveOptions(oldFileName, oldFileType, oldSaveConfigToken); + } + + return result2; + } + else + { + return false; + } + } + } + + public static Document LoadDocument(Control owner, string fileName, out FileType fileTypeResult, ProgressEventHandler progressCallback) + { + FileTypeCollection fileTypes; + int ftIndex; + FileType fileType; + + fileTypeResult = null; + + try + { + fileTypes = FileTypes.GetFileTypes(); + ftIndex = fileTypes.IndexOfExtension(Path.GetExtension(fileName)); + + if (ftIndex == -1) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.ImageTypeNotRecognized")); + return null; + } + + fileType = fileTypes[ftIndex]; + fileTypeResult = fileType; + } + + catch (ArgumentException) + { + string format = PdnResources.GetString("LoadImage.Error.InvalidFileName.Format"); + string error = string.Format(format, fileName); + Utility.ErrorBox(owner, error); + return null; + } + + Document document = null; + + using (new WaitCursorChanger(owner)) + { + Utility.GCFullCollect(); + Stream stream = null; + + try + { + try + { + stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); + long totalBytes = 0; + + SiphonStream siphonStream = new SiphonStream(stream); + + IOEventHandler ioEventHandler = null; + ioEventHandler = + delegate(object sender, IOEventArgs e) + { + if (progressCallback != null) + { + totalBytes += (long)e.Count; + double percent = Utility.Clamp(100.0 * ((double)totalBytes / (double)siphonStream.Length), 0, 100); + progressCallback(null, new ProgressEventArgs(percent)); + } + }; + + siphonStream.IOFinished += ioEventHandler; + + using (new WaitCursorChanger(owner)) + { + document = fileType.Load(siphonStream); + + if (progressCallback != null) + { + progressCallback(null, new ProgressEventArgs(100.0)); + } + } + + siphonStream.IOFinished -= ioEventHandler; + siphonStream.Close(); + } + + catch (WorkerThreadException ex) + { + Type innerExType = ex.InnerException.GetType(); + ConstructorInfo ci = innerExType.GetConstructor(new Type[] { typeof(string), typeof(Exception) }); + + if (ci == null) + { + throw; + } + else + { + Exception ex2 = (Exception)ci.Invoke(new object[] { "Worker thread threw an exception of this type", ex.InnerException }); + throw ex2; + } + } + } + + catch (ArgumentException) + { + if (fileName.Length == 0) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.BlankFileName")); + } + else + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.ArgumentException")); + } + } + + catch (UnauthorizedAccessException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.UnauthorizedAccessException")); + } + + catch (SecurityException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.SecurityException")); + } + + catch (FileNotFoundException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.FileNotFoundException")); + } + + catch (DirectoryNotFoundException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.DirectoryNotFoundException")); + } + + catch (PathTooLongException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.PathTooLongException")); + } + + catch (IOException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.IOException")); + } + + catch (SerializationException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.SerializationException")); + } + + catch (OutOfMemoryException) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.OutOfMemoryException")); + } + + catch (Exception) + { + Utility.ErrorBox(owner, PdnResources.GetString("LoadImage.Error.Exception")); + } + + finally + { + if (stream != null) + { + stream.Close(); + stream = null; + } + } + } + + return document; + } + + public Surface RenderThumbnail(int maxEdgeLength, bool highQuality, bool forceUpToDate) + { + if (Document == null) + { + Surface ret = new Surface(maxEdgeLength, maxEdgeLength); + ret.Clear(ColorBgra.Transparent); + return ret; + } + + Size thumbSize = Utility.ComputeThumbnailSize(Document.Size, maxEdgeLength); + Surface thumb = new Surface(thumbSize); + thumb.Clear(ColorBgra.Transparent); + + RenderCompositionTo(thumb, highQuality, forceUpToDate); + + return thumb; + } + + Surface IThumbnailProvider.RenderThumbnail(int maxEdgeLength) + { + return RenderThumbnail(maxEdgeLength, true, false); + } + } +} diff --git a/src/DocumentWorkspace.resx b/src/DocumentWorkspace.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/DocumentWorkspaceAction.cs b/src/DocumentWorkspaceAction.cs new file mode 100644 index 0000000..3a6db08 --- /dev/null +++ b/src/DocumentWorkspaceAction.cs @@ -0,0 +1,54 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Provides a way to do a tool-less action that operates on the DocumentWorkspace. + /// DocumentActions must NOT touch directly the History -- they should return history + /// actions that can undo what they have already done. These history actions will + /// then be placed in to the history by whomever invoked the DocumentAction. + /// If the action does not affect the Document, it should return null from its + /// PerformAction method. + /// DocumentActions should ONLY mutate the DocumentWorkspace and any contained + /// objects. + /// + internal abstract class DocumentWorkspaceAction + { + private ActionFlags actionFlags; + public ActionFlags ActionFlags + { + get + { + return this.actionFlags; + } + } + + /// + /// Implement this to provide an action. You must return a HistoryMemento so that you + /// can be undone. However, you should return null if you didn't do anything that + /// affected the document. + /// + /// A HistoryMemento object that will be placed onto the HistoryStack. + public abstract HistoryMemento PerformAction(DocumentWorkspace documentWorkspace); + + /// + /// Initializes an instance of a class dervied from DocumentAction. + /// + /// The DocumentWorkspace to interact with. + /// Flags that describe action behavior or requirements. + public DocumentWorkspaceAction(ActionFlags actionFlags) + { + this.actionFlags = actionFlags; + SystemLayer.Tracing.LogFeature("DWAction(" + GetType().Name + ")"); + } + } +} diff --git a/src/Effects/AddNoiseEffect.cs b/src/Effects/AddNoiseEffect.cs new file mode 100644 index 0000000..5f6ef62 --- /dev/null +++ b/src/Effects/AddNoiseEffect.cs @@ -0,0 +1,219 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class AddNoiseEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("AddNoiseEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.AddNoiseEffect.png").Reference; + } + } + + static AddNoiseEffect() + { + InitLookup(); + } + + public AddNoiseEffect() + : base(StaticName, StaticImage, SubmenuNames.Noise, EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Intensity = 0, + Saturation = 1, + Coverage = 2 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Intensity, 64, 0, 100)); + props.Add(new Int32Property(PropertyNames.Saturation, 100, 0, 400)); + props.Add(new DoubleProperty(PropertyNames.Coverage, 100, 0, 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Intensity, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("AddNoiseEffect.Amount1Label")); + configUI.SetPropertyControlValue(PropertyNames.Saturation, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("AddNoiseEffect.Amount2Label")); + configUI.SetPropertyControlValue(PropertyNames.Coverage, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("AddNoiseEffect.Coverage")); + configUI.SetPropertyControlValue(PropertyNames.Coverage, ControlInfoPropertyNames.UseExponentialScale, true); + + return configUI; + } + + private int intensity; + private int saturation; + private double coverage; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.intensity = newToken.GetProperty(PropertyNames.Intensity).Value; + this.saturation = newToken.GetProperty(PropertyNames.Saturation).Value; + this.coverage = 0.01 * newToken.GetProperty(PropertyNames.Coverage).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + private const int tableSize = 16384; + private static int[] lookup; + + private static double NormalCurve(double x, double scale) + { + return scale * Math.Exp(-x * x / 2); + } + + private static void InitLookup() + { + int[] curve = new int[tableSize]; + int[] integral = new int[tableSize]; + + double l = 5; + double r = 10; + double scale = 50; + double sum = 0; + + while (r - l > 0.0000001) + { + sum = 0; + scale = (l + r) * 0.5; + + for (int i = 0; i < tableSize; ++i) + { + sum += NormalCurve(16.0 * ((double)i - tableSize / 2) / tableSize, scale); + + if (sum > 1000000) + { + break; + } + } + + if (sum > tableSize) + { + r = scale; + } + else if (sum < tableSize) + { + l = scale; + } + else + { + break; + } + } + + lookup = new int[tableSize]; + sum = 0; + int roundedSum = 0, lastRoundedSum; + + for (int i = 0; i < tableSize; ++i) + { + sum += NormalCurve(16.0 * ((double)i - tableSize / 2) / tableSize, scale); + lastRoundedSum = roundedSum; + roundedSum = (int)sum; + + for (int j = lastRoundedSum; j < roundedSum; ++j) + { + lookup[j] = (i - tableSize / 2) * 65536 / tableSize; + } + } + } + + [ThreadStatic] + private static Random threadRand = new Random(); + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + int dev = this.intensity * this.intensity / 4; + int sat = this.saturation * 4096 / 100; + + if (threadRand == null) + { + threadRand = new Random(unchecked(System.Threading.Thread.CurrentThread.GetHashCode() ^ + unchecked((int)DateTime.Now.Ticks))); + } + + Random localRand = threadRand; + int[] localLookup = lookup; + + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle rect = rois[ri]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra *srcPtr = SrcArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + ColorBgra *dstPtr = DstArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + + for (int x = 0; x < rect.Width; ++x) + { + if (localRand.NextDouble() > this.coverage) + { + *dstPtr = *srcPtr; + } + else + { + int r; + int g; + int b; + int i; + + r = localLookup[localRand.Next(tableSize)]; + g = localLookup[localRand.Next(tableSize)]; + b = localLookup[localRand.Next(tableSize)]; + + i = (4899 * r + 9618 * g + 1867 * b) >> 14; + + r = i + (((r - i) * sat) >> 12); + g = i + (((g - i) * sat) >> 12); + b = i + (((b - i) * sat) >> 12); + + dstPtr->R = Utility.ClampToByte(srcPtr->R + ((r * dev + 32768) >> 16)); + dstPtr->G = Utility.ClampToByte(srcPtr->G + ((g * dev + 32768) >> 16)); + dstPtr->B = Utility.ClampToByte(srcPtr->B + ((b * dev + 32768) >> 16)); + dstPtr->A = srcPtr->A; + } + + ++srcPtr; + ++dstPtr; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Effects/AmountEffectConfigDialog.cs b/src/Effects/AmountEffectConfigDialog.cs new file mode 100644 index 0000000..2bad3ca --- /dev/null +++ b/src/Effects/AmountEffectConfigDialog.cs @@ -0,0 +1,312 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class AmountEffectConfigDialog + : AmountEffectConfigDialogBase + { + public AmountEffectConfigDialog() + { + } + } + + public abstract class AmountEffectConfigDialogBase + : EffectConfigDialog + { + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TrackBar amountTrackBar; + private System.Windows.Forms.NumericUpDown amountUpDown; + private System.ComponentModel.IContainer components = null; + private PaintDotNet.HeaderLabel headerLabel; + public int sliderInitialValue = 2; + + internal AmountEffectConfigDialogBase() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + } + + public int SliderInitialValue + { + get + { + return sliderInitialValue; + } + + set + { + this.sliderInitialValue = value; + amountTrackBar.Value = value; + amountUpDown.Value = value; + FinishTokenUpdate(); + } + } + + public int SliderMinimum + { + get + { + return amountTrackBar.Minimum; + } + + set + { + amountTrackBar.Minimum = value; + amountUpDown.Minimum = (decimal)value; + } + } + + public int SliderMaximum + { + get + { + return amountTrackBar.Maximum; + } + + set + { + amountTrackBar.Maximum = value; + amountUpDown.Maximum = (decimal)value; + } + } + + public string SliderLabel + { + get + { + return headerLabel.Text; + } + + set + { + headerLabel.Text = value; + } + } + + public string SliderUnitsName + { + get + { + return label1.Text; + } + + set + { + label1.Text = value; + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.amountTrackBar = new System.Windows.Forms.TrackBar(); + this.amountUpDown = new System.Windows.Forms.NumericUpDown(); + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.headerLabel = new PaintDotNet.HeaderLabel(); + ((System.ComponentModel.ISupportInitialize)(this.amountTrackBar)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.amountUpDown)).BeginInit(); + this.SuspendLayout(); + // + // amountTrackBar + // + this.amountTrackBar.AutoSize = false; + this.amountTrackBar.Location = new System.Drawing.Point(3, 59); + this.amountTrackBar.Maximum = 100; + this.amountTrackBar.Minimum = 1; + this.amountTrackBar.Name = "amountTrackBar"; + this.amountTrackBar.Size = new System.Drawing.Size(174, 24); + this.amountTrackBar.TabIndex = 1; + this.amountTrackBar.TickFrequency = 10; + this.amountTrackBar.TickStyle = System.Windows.Forms.TickStyle.None; + this.amountTrackBar.Value = 1; + this.amountTrackBar.ValueChanged += new System.EventHandler(this.amountTrackBar_ValueChanged); + // + // amountUpDown + // + this.amountUpDown.Location = new System.Drawing.Point(16, 32); + this.amountUpDown.Minimum = new System.Decimal(new int[] { + 1, + 0, + 0, + 0}); + this.amountUpDown.Name = "amountUpDown"; + this.amountUpDown.Size = new System.Drawing.Size(64, 20); + this.amountUpDown.TabIndex = 0; + this.amountUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.amountUpDown.Value = new System.Decimal(new int[] { + 1, + 0, + 0, + 0}); + this.amountUpDown.Enter += new System.EventHandler(this.amountUpDown_Enter); + this.amountUpDown.ValueChanged += new System.EventHandler(this.amountUpDown_ValueChanged); + this.amountUpDown.Leave += new System.EventHandler(this.amountUpDown_Leave); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(96, 93); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 3; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(15, 93); + this.okButton.Name = "okButton"; + this.okButton.TabIndex = 2; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // label1 + // + this.label1.Location = new System.Drawing.Point(82, 30); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(72, 24); + this.label1.TabIndex = 2; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // headerLabel + // + this.headerLabel.Location = new System.Drawing.Point(6, 8); + this.headerLabel.Name = "headerLabel"; + this.headerLabel.Size = new System.Drawing.Size(170, 14); + this.headerLabel.TabIndex = 7; + this.headerLabel.TabStop = false; + this.headerLabel.Text = "Header"; + // + // AmountEffectConfigDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(177, 122); + this.Controls.Add(this.headerLabel); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.amountTrackBar); + this.Controls.Add(this.label1); + this.Controls.Add(this.amountUpDown); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "AmountEffectConfigDialog"; + this.Controls.SetChildIndex(this.amountUpDown, 0); + this.Controls.SetChildIndex(this.label1, 0); + this.Controls.SetChildIndex(this.amountTrackBar, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.headerLabel, 0); + ((System.ComponentModel.ISupportInitialize)(this.amountTrackBar)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.amountUpDown)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + protected override void InitialInitToken() + { + theEffectToken = new AmountEffectConfigToken(this.sliderInitialValue); + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + this.amountTrackBar.Value = ((AmountEffectConfigToken)effectToken).Amount; + } + + protected override void InitTokenFromDialog() + { + ((AmountEffectConfigToken)theEffectToken).Amount = amountTrackBar.Value; + } + + private void amountTrackBar_ValueChanged(object sender, System.EventArgs e) + { + if (amountTrackBar.Value != (int)amountUpDown.Value) + { + amountUpDown.Value = amountTrackBar.Value; + FinishTokenUpdate(); + } + } + + private void amountUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (amountTrackBar.Value != (int)amountUpDown.Value) + { + amountTrackBar.Value = (int)amountUpDown.Value; + FinishTokenUpdate(); + } + } + + private void okButton_Click(object sender, System.EventArgs e) + { + // if the user types, then presses Enter or clicks OK, this will make sure we take what they typed and not the value of the trackbar + amountUpDown_Leave(sender, e); + + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void cancelButton_Click(object sender, System.EventArgs e) + { + this.Close(); + } + + private void amountUpDown_Enter(object sender, System.EventArgs e) + { + amountUpDown.Select(0,amountUpDown.Text.Length); + } + + private void amountUpDown_Leave(object sender, System.EventArgs e) + { + Utility.ClipNumericUpDown(amountUpDown); + + if (Utility.CheckNumericUpDown(amountUpDown)) + { + amountUpDown.Value = decimal.Parse(amountUpDown.Text); + } + } + } +} + diff --git a/src/Effects/AmountEffectConfigDialog.resx b/src/Effects/AmountEffectConfigDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/AmountEffectConfigToken.cs b/src/Effects/AmountEffectConfigToken.cs new file mode 100644 index 0000000..585da83 --- /dev/null +++ b/src/Effects/AmountEffectConfigToken.cs @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + /// + /// Used for configuring effects that just need one variable, + /// an integer that specifies a range that describes "how much" + /// to apply the effect. + /// + public class AmountEffectConfigToken + : EffectConfigToken + { + private int amount; + public int Amount + { + get + { + return amount; + } + + set + { + amount = value; + } + } + + public override object Clone() + { + return new AmountEffectConfigToken(this); + } + + public AmountEffectConfigToken(int amount) + { + this.amount = amount; + } + + protected AmountEffectConfigToken(AmountEffectConfigToken copyMe) + : base(copyMe) + { + this.amount = copyMe.amount; + } + } +} diff --git a/src/Effects/AssemblyInfo.cs b/src/Effects/AssemblyInfo.cs new file mode 100644 index 0000000..8b58db5 --- /dev/null +++ b/src/Effects/AssemblyInfo.cs @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Paint.NET Effects")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Sometimes)] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: ComVisibleAttribute(false)] diff --git a/src/Effects/AutoLevelEffect.cs b/src/Effects/AutoLevelEffect.cs new file mode 100644 index 0000000..a6bbae1 --- /dev/null +++ b/src/Effects/AutoLevelEffect.cs @@ -0,0 +1,55 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class AutoLevelEffect + : InternalPropertyBasedEffect + { + private UnaryPixelOps.Level levels = null; + + protected override PropertyCollection OnCreatePropertyCollection() + { + return PropertyCollection.CreateEmpty(); + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + HistogramRgb histogram = new HistogramRgb(); + histogram.UpdateHistogram(srcArgs.Surface, this.EnvironmentParameters.GetSelection(dstArgs.Bounds)); + this.levels = histogram.MakeLevelsAuto(); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + if (this.levels.isValid) + { + this.levels.Apply(DstArgs.Surface, SrcArgs.Surface, rois, startIndex, length); + } + } + + public AutoLevelEffect() + : base(PdnResources.GetString("AutoLevel.Name"), + PdnResources.GetImageResource("Icons.AutoLevel.png").Reference, + null, + EffectFlags.None) + { + } + } +} diff --git a/src/Effects/BackgroundEffectRenderer.cs b/src/Effects/BackgroundEffectRenderer.cs new file mode 100644 index 0000000..6066f6f --- /dev/null +++ b/src/Effects/BackgroundEffectRenderer.cs @@ -0,0 +1,416 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Threading; + +namespace PaintDotNet.Effects +{ + /// + /// This class can be used to apply an effect using background worker threads + /// which raise an event when a certain amount of the effect has been processed. + /// You can use that event to update a status bar, display a preview of the + /// rendering so far, or whatever. + /// + /// Since two threads are used for rendering, this will improve performance on + /// dual processor systems, and possibly on systems that have HyperThreading. + /// + /// This class is NOT SAFE for multithreaded access. Note that the events will + /// be raised from arbitrary threads. + /// be raised from arbitrary threads. The only method that is safe to call from + /// a thread that is not managing Start(), Abort(), and Join() is AbortAsync(). + /// You may then query whether the rendering actually aborted by using the Abort + /// property. If it returns false, then AbortAsync() was not called in time to + /// abort anything, which means the rendering completed fully. + /// + public sealed class BackgroundEffectRenderer + : IDisposable + { + private Effect effect; + private EffectConfigToken effectToken; // this references the main token that is passed in to the constructor + private EffectConfigToken effectTokenCopy; // this copy of the token is updated every time you call Start() to make sure it is up to date. This is then passed to the threads, not the original one. + private PdnRegion renderRegion; + private Rectangle[][] tileRegions; + private PdnRegion[] tilePdnRegions; + private int tileCount; + private Threading.ThreadPool threadPool; + private RenderArgs dstArgs; + private RenderArgs srcArgs; + private int workerThreads; + private ArrayList exceptions = ArrayList.Synchronized(new ArrayList()); + private volatile bool aborted = false; + + public event RenderedTileEventHandler RenderedTile; + private void OnRenderedTile(RenderedTileEventArgs e) + { + if (RenderedTile != null) + { + RenderedTile(this, e); + } + } + + public event EventHandler FinishedRendering; + private void OnFinishedRendering() + { + if (FinishedRendering != null) + { + FinishedRendering(this, EventArgs.Empty); + } + } + + public event EventHandler StartingRendering; + private void OnStartingRendering() + { + if (StartingRendering != null) + { + StartingRendering(this, EventArgs.Empty); + } + } + + private sealed class RendererContext + { + private BackgroundEffectRenderer ber; + private EffectConfigToken token; + private int threadNumber; + private int startOffset; + + public RendererContext(BackgroundEffectRenderer ber, EffectConfigToken token, int threadNumber) + : this(ber, token, threadNumber, 0) + { + } + + public RendererContext(BackgroundEffectRenderer ber, EffectConfigToken token, int threadNumber, int startOffset) + { + this.ber = ber; + this.token = token; + this.threadNumber = threadNumber; + this.startOffset = startOffset; + } + + public void Renderer2(object ignored) + { + Renderer(); + } + + public void Renderer() + { + //using (new ThreadBackground(ThreadBackgroundFlags.Cpu)) + { + RenderImpl(); + } + } + + private void RenderImpl() + { + int inc = ber.workerThreads; + int start = this.threadNumber + (this.startOffset * inc); + int max = ber.tileCount; + + try + { + for (int tile = start; tile < max; tile += inc) + { + if (ber.threadShouldStop) + { + this.ber.aborted = true; + break; + } + + Rectangle[] subRegion = ber.tileRegions[tile]; + ber.effect.Render(this.token, ber.dstArgs, ber.srcArgs, subRegion); + PdnRegion subPdnRegion = ber.tilePdnRegions[tile]; + ber.OnRenderedTile(new RenderedTileEventArgs(subPdnRegion, ber.tileCount, tile)); + } + } + + catch (Exception ex) + { + ber.exceptions.Add(ex); + } + } + } + + public void ThreadFunction() + { + if (this.srcArgs.Surface.Scan0.MaySetAllowWrites) + { + this.srcArgs.Surface.Scan0.AllowWrites = false; + } + + try + { + this.effect.SetRenderInfo(this.effectTokenCopy, this.dstArgs, this.srcArgs); + + if (tileCount > 0) + { + Rectangle[] subRegion = this.tileRegions[0]; + + this.effect.Render(this.effectTokenCopy, this.dstArgs, this.srcArgs, subRegion); + + PdnRegion subPdnRegion = this.tilePdnRegions[0]; + OnRenderedTile(new RenderedTileEventArgs(subPdnRegion, this.tileCount, 0)); + } + + EffectConfigToken[] tokens = new EffectConfigToken[workerThreads]; + + int i; + for (i = 0; i < workerThreads; ++i) + { + if (this.threadShouldStop) + { + break; + } + + if (this.effectTokenCopy == null) + { + tokens[i] = null; + } + else + { + tokens[i] = (EffectConfigToken)this.effectTokenCopy.Clone(); + } + + RendererContext rc = new RendererContext(this, tokens[i], i, (i == 0) ? 1 : 0); + threadPool.QueueUserWorkItem(new WaitCallback(rc.Renderer2)); + } + + if (i == workerThreads) + { + threadPool.Drain(); + OnFinishedRendering(); + } + } + + catch (Exception ex) + { + this.exceptions.Add(ex); + } + + finally + { + threadPool.Drain(); + + Exception[] newExceptions = threadPool.Exceptions; + + if (newExceptions.Length > 0) + { + foreach (Exception exception in newExceptions) + { + this.exceptions.Add(exception); + } + } + + if (this.srcArgs.Surface.Scan0.MaySetAllowWrites) + { + this.srcArgs.Surface.Scan0.AllowWrites = true; + } + } + } + + private volatile bool threadShouldStop = false; + private Thread thread = null; + + public void Start() + { + Abort(); + this.aborted = false; + + if (this.effectToken != null) + { + this.effectTokenCopy = (EffectConfigToken)this.effectToken.Clone(); + } + + this.threadShouldStop = false; + OnStartingRendering(); + this.thread = new Thread(new ThreadStart(ThreadFunction)); + this.thread.Start(); + } + + public bool Aborted + { + get + { + return this.aborted; + } + } + + public void Abort() + { + if (this.thread != null) + { + this.threadShouldStop = true; + Join(); + this.threadPool.Drain(); + } + } + + // This is the only method that is safe to call from another thread + // If the abort was successful, then get_Aborted will return true + // after a Join(). + public void AbortAsync() + { + this.threadShouldStop = true; + } + + /// + /// Used to determine whether the rendering fully completed or not, and was not + /// aborted in any way. You can use this method to sleep until the rendering + /// finishes. Once this is set to the signaled state you should check the IsDone + /// property to make sure that the rendering was actually finished, and not + /// aborted. + /// + public void Join() + { + this.thread.Join(); + + if (this.exceptions.Count > 0) + { + Exception throwMe = (Exception)this.exceptions[0]; + this.exceptions.Clear(); + throw new WorkerThreadException("Worker thread threw an exception", throwMe); + } + } + + private Rectangle[] ConsolidateRects(Rectangle[] scans) + { + if (scans.Length == 0) + { + return scans; + } + + List cons = new List(); + int current = 0; + cons.Add(scans[0]); + + for (int i = 1; i < scans.Length; ++i) + { + if (scans[i].Left == cons[current].Left && + scans[i].Right == cons[current].Right && + scans[i].Top == cons[current].Bottom) + { + Rectangle cc = cons[current]; + cc.Height = scans[i].Bottom - cons[current].Top; + cons[current] = cc; + } + else + { + cons.Add(scans[i]); + current = cons.Count - 1; + } + } + + return cons.ToArray(); + } + + private Rectangle[][] SliceUpRegion(PdnRegion region, int sliceCount, Rectangle layerBounds) + { + Rectangle[][] slices = new Rectangle[sliceCount][]; + Rectangle[] regionRects = region.GetRegionScansReadOnlyInt(); + Scanline[] regionScans = Utility.GetRegionScans(regionRects); + + for (int i = 0; i < sliceCount; ++i) + { + int beginScan = (regionScans.Length * i) / sliceCount; + int endScan = Math.Min(regionScans.Length, (regionScans.Length * (i + 1)) / sliceCount); + + // Try to arrange it such that the maximum size of the first region + // is 1-pixel tall + if (i == 0) + { + endScan = Math.Min(endScan, beginScan + 1); + } + else if (i == 1) + { + beginScan = Math.Min(beginScan, 1); + } + + Rectangle[] newRects = Utility.ScanlinesToRectangles(regionScans, beginScan, endScan - beginScan); + + for (int j = 0; j < newRects.Length; ++j) + { + newRects[j].Intersect(layerBounds); + } + + Rectangle[] consRects = ConsolidateRects(newRects); + slices[i] = consRects; + } + + return slices; + } + + public BackgroundEffectRenderer(Effect effect, + EffectConfigToken effectToken, + RenderArgs dstArgs, + RenderArgs srcArgs, + PdnRegion renderRegion, + int tileCount, + int workerThreads) + { + this.effect = effect; + this.effectToken = effectToken; + this.dstArgs = dstArgs; + this.srcArgs = srcArgs; + this.renderRegion = renderRegion; + this.renderRegion.Intersect(dstArgs.Bounds); + + this.tileRegions = SliceUpRegion(renderRegion, tileCount, dstArgs.Bounds); + + this.tilePdnRegions = new PdnRegion[this.tileRegions.Length]; + for (int i = 0; i < this.tileRegions.Length; ++i) + { + PdnRegion pdnRegion = Utility.RectanglesToRegion(this.tileRegions[i]); + this.tilePdnRegions[i] = pdnRegion; + } + + this.tileCount = tileCount; + this.workerThreads = workerThreads; + + if (effect.CheckForEffectFlags(EffectFlags.SingleThreaded)) + { + this.workerThreads = 1; + } + + this.threadPool = new Threading.ThreadPool(this.workerThreads, false); + } + + ~BackgroundEffectRenderer() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.srcArgs != null) + { + this.srcArgs.Dispose(); + this.srcArgs = null; + } + + if (this.dstArgs != null) + { + this.dstArgs.Dispose(); + this.dstArgs = null; + } + } + } + } +} diff --git a/src/Effects/BlurEffect.cs b/src/Effects/BlurEffect.cs new file mode 100644 index 0000000..38b62a2 --- /dev/null +++ b/src/Effects/BlurEffect.cs @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [Obsolete("This class is obsolete, and exists only for compatibility with some legacy plugins. Use GaussianBlurEffect instead.")] + public sealed class BlurEffect + : Effect + { + private GaussianBlurEffect gbEffect; + private PropertyCollection gbProps; + private PropertyBasedEffectConfigToken gbToken; + + protected override void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs) + { + lock (this) + { + this.gbToken = new PropertyBasedEffectConfigToken(this.gbProps); + this.gbToken.SetPropertyValue(GaussianBlurEffect.PropertyNames.Radius, ((AmountEffectConfigToken)parameters).Amount); + this.gbEffect.SetRenderInfo(this.gbToken, dstArgs, srcArgs); + } + + base.OnSetRenderInfo(parameters, dstArgs, srcArgs); + } + + public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) + { + lock (this) + { + SetRenderInfo(parameters, dstArgs, srcArgs); + } + + this.gbEffect.Render(this.gbToken, dstArgs, srcArgs, rois, startIndex, length); + } + + public override EffectConfigDialog CreateConfigDialog() + { + throw new NotImplementedException(); + } + + public BlurEffect() + : base(GaussianBlurEffect.StaticName + " -- Obsolete", + null, + null, + EffectFlags.Configurable) + { + this.gbEffect = new GaussianBlurEffect(); + this.gbProps = this.gbEffect.CreatePropertyCollection(); + } + } +} diff --git a/src/Effects/BrightnessAndContrastAdjustment.cs b/src/Effects/BrightnessAndContrastAdjustment.cs new file mode 100644 index 0000000..0f44bf8 --- /dev/null +++ b/src/Effects/BrightnessAndContrastAdjustment.cs @@ -0,0 +1,198 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class BrightnessAndContrastAdjustment + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + Brightness = 0, + Contrast = 1 + } + + public static string StaticName + { + get + { + return PdnResources.GetString("BrightnessAndContrastAdjustment.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.BrightnessAndContrastAdjustment.png").Reference; + } + } + + private int brightness; + private int contrast; + private int multiply; + private int divide; + private byte[] rgbTable; + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Brightness, 0, -100, +100)); + props.Add(new Int32Property(PropertyNames.Contrast, 0, -100, +100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Brightness, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("BrightnessAndContrastAdjustment.Brightness")); + configUI.SetPropertyControlValue(PropertyNames.Contrast, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("BrightnessAndContrastAdjustmnet.Contrast")); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.brightness = newToken.GetProperty(PropertyNames.Brightness).Value; + this.contrast = newToken.GetProperty(PropertyNames.Contrast).Value; + + if (this.contrast < 0) + { + this.multiply = this.contrast + 100; + this.divide = 100; + } + else if (this.contrast > 0) + { + this.multiply = 100; + this.divide = 100 - this.contrast; + } + else + { + this.multiply = 1; + this.divide = 1; + } + + if (this.rgbTable == null) + { + this.rgbTable = new byte[65536]; + } + + if (this.divide == 0) + { + for (int intensity = 0; intensity < 256; ++intensity) + { + if (intensity + this.brightness < 128) + { + this.rgbTable[intensity] = 0; + } + else + { + this.rgbTable[intensity] = 255; + } + } + } + else if (this.divide == 100) + { + for (int intensity = 0; intensity < 256; ++intensity) + { + int shift = (intensity - 127) * this.multiply / this.divide + 127 - intensity + this.brightness; + + for (int col = 0; col < 256; ++col) + { + int index = (intensity * 256) + col; + this.rgbTable[index] = Utility.ClampToByte(col + shift); + } + } + } + else + { + for (int intensity = 0; intensity < 256; ++intensity) + { + int shift = (intensity - 127 + this.brightness) * this.multiply / this.divide + 127 - intensity; + + for (int col = 0; col < 256; ++col) + { + int index = (intensity * 256) + col; + this.rgbTable[index] = Utility.ClampToByte(col + shift); + } + } + } + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + for (int r = startIndex; r < startIndex + length; ++r) + { + Rectangle rect = rois[r]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* srcRowPtr = SrcArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + ColorBgra* dstRowPtr = DstArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + ColorBgra *dstRowEndPtr = dstRowPtr + rect.Width; + + if (divide == 0) + { + while (dstRowPtr < dstRowEndPtr) + { + ColorBgra col = *srcRowPtr; + int i = col.GetIntensityByte(); + uint c = this.rgbTable[i]; + dstRowPtr->Bgra = (col.Bgra & 0xff000000) | c | (c << 8) | (c << 16); + + ++dstRowPtr; + ++srcRowPtr; + } + } + else + { + while (dstRowPtr < dstRowEndPtr) + { + ColorBgra col = *srcRowPtr; + int i = col.GetIntensityByte(); + int shiftIndex = i * 256; + + col.R = this.rgbTable[shiftIndex + col.R]; + col.G = this.rgbTable[shiftIndex + col.G]; + col.B = this.rgbTable[shiftIndex + col.B]; + + *dstRowPtr = col; + ++dstRowPtr; + ++srcRowPtr; + } + } + } + } + + return; + } + + public BrightnessAndContrastAdjustment() + : base(StaticName, + StaticImage, + null, + EffectFlags.Configurable) + { + } + } +} diff --git a/src/Effects/BulgeEffect.cs b/src/Effects/BulgeEffect.cs new file mode 100644 index 0000000..a75367e Binary files /dev/null and b/src/Effects/BulgeEffect.cs differ diff --git a/src/Effects/CloudsEffect.cs b/src/Effects/CloudsEffect.cs new file mode 100644 index 0000000..f062cce --- /dev/null +++ b/src/Effects/CloudsEffect.cs @@ -0,0 +1,262 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class CloudsEffect + : InternalPropertyBasedEffect + { + // This is so that repetition of the effect with CTRL+F actually shows up differently. + private byte instanceSeed = unchecked((byte)DateTime.Now.Ticks); + + public static string StaticName + { + get + { + return PdnResources.GetString("CloudsEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.CloudsEffect.png").Reference; + } + } + + public CloudsEffect() + : base(StaticName, StaticImage, SubmenuNames.Render, EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Scale = 0, + Power = 1, + BlendOp = 2, + Seed = 3 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Scale, 250, 2, 1000)); + props.Add(new DoubleProperty(PropertyNames.Power, 0.5, 0.0, 1.0)); + + Type[] blendOpTypes = UserBlendOps.GetBlendOps(); + int defaultBlendOpIndex = Array.IndexOf(blendOpTypes, UserBlendOps.GetDefaultBlendOp()); + props.Add(new StaticListChoiceProperty(PropertyNames.BlendOp, blendOpTypes, 0, false)); + + props.Add(new Int32Property(PropertyNames.Seed, 0, 0, 255)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Scale, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("CloudsEffect.ConfigDialog.ScaleLabel")); + + configUI.SetPropertyControlValue(PropertyNames.Power, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("CloudsEffect.ConfigDialog.RoughnessLabel")); + configUI.SetPropertyControlValue(PropertyNames.Power, ControlInfoPropertyNames.SliderLargeChange, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Power, ControlInfoPropertyNames.SliderSmallChange, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Power, ControlInfoPropertyNames.UpDownIncrement, 0.01); + + PropertyControlInfo blendOpControl = configUI.FindControlForPropertyName(PropertyNames.BlendOp); + blendOpControl.ControlProperties[ControlInfoPropertyNames.DisplayName].Value = PdnResources.GetString("CloudsEffect.ConfigDialog.BlendModeHeader.Text"); + + Type[] blendOpTypes = UserBlendOps.GetBlendOps(); + foreach (Type blendOpType in blendOpTypes) + { + string blendOpDisplayName = UserBlendOps.GetBlendOpFriendlyName(blendOpType); + blendOpControl.SetValueDisplayName(blendOpType, blendOpDisplayName); + } + + configUI.SetPropertyControlType(PropertyNames.Seed, PropertyControlType.IncrementButton); + configUI.SetPropertyControlValue(PropertyNames.Seed, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("CloudsEffect.ConfigDialog.SeedHeader.Text")); + configUI.SetPropertyControlValue(PropertyNames.Seed, ControlInfoPropertyNames.ButtonText, PdnResources.GetString("CloudsEffect.ConfigDialog.ReseedButton.Text")); + configUI.SetPropertyControlValue(PropertyNames.Seed, ControlInfoPropertyNames.Description, PdnResources.GetString("CloudsEffect.ConfigDialog.UsageLabel")); + + return configUI; + } + + private int scale; + private byte seed; + private double power; + private UserBlendOp blendOp; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.scale = newToken.GetProperty(PropertyNames.Scale).Value; + + int intSeed = newToken.GetProperty(PropertyNames.Seed).Value; + this.seed = (byte)(intSeed ^ instanceSeed); + + this.power = newToken.GetProperty(PropertyNames.Power).Value; + + Type blendOpType = (Type)newToken.GetProperty(PropertyNames.BlendOp).Value; + this.blendOp = UserBlendOps.CreateBlendOp(blendOpType); + + if (this.blendOp is UserBlendOps.NormalBlendOp && + EnvironmentParameters.PrimaryColor.A == 255 && + EnvironmentParameters.SecondaryColor.A == 255) + { + // this is just an optimization + this.blendOp = null; + } + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + for (int i = startIndex; i < startIndex + length; ++i) + { + RenderClouds(this.DstArgs.Surface, renderRects[i], this.scale, this.seed, + this.power, EnvironmentParameters.PrimaryColor, EnvironmentParameters.SecondaryColor); + + if (blendOp != null) + { + blendOp.Apply(this.DstArgs.Surface, renderRects[i].Location, this.SrcArgs.Surface, + renderRects[i].Location, this.DstArgs.Surface, renderRects[i].Location, renderRects[i].Size); + } + } + } + + static CloudsEffect() + { + for (int i = 0; i < 256; i++) + { + permuteLookup[256 + i] = permutationTable[i]; + permuteLookup[i] = permutationTable[i]; + } + } + + // Adapted to 2-D version in C# from 3-D version in Java from http://mrl.nyu.edu/~perlin/noise/ + static private int[] permuteLookup = new int[512]; + + static private int[] permutationTable = new int[] + { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, + 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, + 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, + 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, + 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, + 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, + 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, + 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, + 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, + 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, + 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, + 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, + 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, + 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, + 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, + 195, 78, 66, 215, 61, 156, 180 + }; + + private static double Fade(double t) + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private static double Grad(int hash, double x, double y) + { + int h = hash & 15; + double u = h < 8 ? x : y; + double v = h < 4 ? y : h == 12 || h == 14 ? x : 0; + + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + private static double Noise(byte ix, byte iy, double x, double y, byte seed) + { + double u = Fade(x); + double v = Fade(y); + + int a = permuteLookup[ix + seed] + iy; + int aa = permuteLookup[a]; + int ab = permuteLookup[a + 1]; + int b = permuteLookup[ix + 1 + seed] + iy; + int ba = permuteLookup[b]; + int bb = permuteLookup[b + 1]; + + double gradAA = Grad(permuteLookup[aa], x, y); + double gradBA = Grad(permuteLookup[ba], x - 1, y); + + double edge1 = Utility.Lerp(gradAA, gradBA, u); + + double gradAB = Grad(permuteLookup[ab], x, y - 1); + double gradBB = Grad(permuteLookup[bb], x - 1, y - 1); + + double edge2 = Utility.Lerp(gradAB, gradBB, u); + + return Utility.Lerp(edge1, edge2, v); + } + + private unsafe static void RenderClouds(Surface surface, Rectangle rect, int scale, byte seed, double power, ColorBgra colorFrom, ColorBgra colorTo) + { + int w = surface.Width; + int h = surface.Height; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* ptr = surface.GetPointAddressUnchecked(rect.Left, y); + int dy = 2 * y - h; + + for (int x = rect.Left; x < rect.Right; ++x) + { + int dx = 2 * x - w; + double val = 0; + double mult = 1; + int div = scale; + + for (int i = 0; i < 12 && mult > 0.03 && div > 0; ++i) + { + double dxr = 65536 + (double)dx / (double)div; + double dyr = 65536 + (double)dy / (double)div; + + int dxd = (int)dxr; + int dyd = (int)dyr; + + dxr -= dxd; + dyr -= dyd; + + double noise = Noise( + unchecked((byte)dxd), + unchecked((byte)dyd), + dxr, //(double)dxr / div, + dyr, //(double)dyr / div, + (byte)(seed ^ i)); + + val += noise * mult; + div /= 2; + mult *= power; + } + + *ptr = ColorBgra.Lerp(colorFrom, colorTo, (val + 1) / 2); + ++ptr; + } + } + } + } +} diff --git a/src/Effects/ColorDifferenceEffect.cs b/src/Effects/ColorDifferenceEffect.cs new file mode 100644 index 0000000..634c4bf --- /dev/null +++ b/src/Effects/ColorDifferenceEffect.cs @@ -0,0 +1,137 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.Effects; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + /// + /// ColorDifferenctEffect is a base class for my difference effects + /// that have floating point (double) convolution filters. + /// its architecture is just like ConvolutionFilterEffect, adding a + /// function (RenderColorDifferenceEffect) called from Render in each + /// derived class. + /// It is also limited to 3x3 kernels. + /// (Chris Crosetto) + /// + public abstract class ColorDifferenceEffect + : InternalPropertyBasedEffect + { + public unsafe void RenderColorDifferenceEffect( + double[][] weights, + RenderArgs dstArgs, + RenderArgs srcArgs, + Rectangle[] rois, + int startIndex, + int length) + { + Surface dst = dstArgs.Surface; + Surface src = srcArgs.Surface; + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle rect = rois[i]; + + // loop through each line of target rectangle + for (int y = rect.Top; y < rect.Bottom; ++y) + { + int fyStart = 0; + int fyEnd = 3; + + if (y == src.Bounds.Top) fyStart = 1; + if (y == src.Bounds.Bottom - 1) fyEnd = 2; + + // loop through each point in the line + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + int fxStart = 0; + int fxEnd = 3; + + if (x == src.Bounds.Left) + { + fxStart = 1; + } + + if (x == src.Bounds.Right - 1) + { + fxEnd = 2; + } + + // loop through each weight + double rSum = 0.0; + double gSum = 0.0; + double bSum = 0.0; + + for (int fy = fyStart; fy < fyEnd; ++fy) + { + for (int fx = fxStart; fx < fxEnd; ++fx) + { + double weight = weights[fy][fx]; + ColorBgra c = src.GetPointUnchecked(x - 1 + fx, y - 1 + fy); + + rSum += weight * (double)c.R; + gSum += weight * (double)c.G; + bSum += weight * (double)c.B; + } + } + + int iRsum = (int)rSum; + int iGsum = (int)gSum; + int iBsum = (int)bSum; + + if (iRsum > 255) + { + iRsum = 255; + } + + if (iGsum > 255) + { + iGsum = 255; + } + + if (iBsum > 255) + { + iBsum = 255; + } + + if (iRsum < 0) + { + iRsum = 0; + } + + if (iGsum < 0) + { + iGsum = 0; + } + + if (iBsum < 0) + { + iBsum = 0; + } + + *dstPtr = ColorBgra.FromBgra((byte)iBsum, (byte)iGsum, (byte)iRsum, 255); + ++dstPtr; + } + } + } + } + + internal ColorDifferenceEffect(string name, Image image, string subMenuName, EffectFlags flags) + : base(name, image, subMenuName, flags) + { + } + } +} \ No newline at end of file diff --git a/src/Effects/ColorFillEffect.cs b/src/Effects/ColorFillEffect.cs new file mode 100644 index 0000000..46afc38 --- /dev/null +++ b/src/Effects/ColorFillEffect.cs @@ -0,0 +1,122 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + // This effect is for testing purposes only. +#if false + public sealed class ColorFillEffect + : InternalPropertyBasedEffect + { + public ColorFillEffect() + : base("Color Fill", null, null, EffectFlags.Configurable) + { + if (PdnInfo.IsFinalBuild) + { + throw new InvalidOperationException("This effect should never make it in to a released build"); + } + } + + public enum PropertyNames + { + AngleChooser, + CheckBox, + DoubleSlider, + DoubleVectorPanAndSlider, + DoubleVectorSlider, + Int32ColorWheel, + Int32IncrementButton, + Int32Slider, + StaticListDropDown, + StaticListRadioButton, + StringText, + } + + private ColorBgra color; + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.AngleChooser, 0, -180, +180)); + props.Add(new BooleanProperty(PropertyNames.CheckBox, true)); + props.Add(new DoubleProperty(PropertyNames.DoubleSlider, 0, 0, 100)); + props.Add(new DoubleVectorProperty(PropertyNames.DoubleVectorPanAndSlider, Pair.Create(0.0, 0.0), Pair.Create(-1.0, -1.0), Pair.Create(+1.0, +1.0))); + props.Add(new DoubleVectorProperty(PropertyNames.DoubleVectorSlider, Pair.Create(0.0, 0.0), Pair.Create(-1.0, -1.0), Pair.Create(+1.0, +1.0))); + props.Add(new Int32Property(PropertyNames.Int32ColorWheel, 0, 0, 0xffffff)); + props.Add(new Int32Property(PropertyNames.Int32IncrementButton, 0, 0, 255)); + props.Add(new Int32Property(PropertyNames.Int32Slider, 0, 0, 100)); + props.Add(StaticListChoiceProperty.CreateForEnum(PropertyNames.StaticListDropDown, GraphicsUnit.Millimeter, false)); + props.Add(StaticListChoiceProperty.CreateForEnum(PropertyNames.StaticListRadioButton, GraphicsUnit.Document, false)); + props.Add(new StringProperty(PropertyNames.StringText, "hello", 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlType(PropertyNames.AngleChooser, PropertyControlType.AngleChooser); + configUI.SetPropertyControlType(PropertyNames.CheckBox, PropertyControlType.CheckBox); + configUI.SetPropertyControlType(PropertyNames.DoubleSlider, PropertyControlType.Slider); + configUI.SetPropertyControlType(PropertyNames.DoubleVectorPanAndSlider, PropertyControlType.PanAndSlider); + configUI.SetPropertyControlType(PropertyNames.DoubleVectorSlider, PropertyControlType.Slider); + configUI.SetPropertyControlType(PropertyNames.Int32ColorWheel, PropertyControlType.ColorWheel); + configUI.SetPropertyControlType(PropertyNames.Int32IncrementButton, PropertyControlType.IncrementButton); + configUI.SetPropertyControlType(PropertyNames.Int32Slider, PropertyControlType.Slider); + configUI.SetPropertyControlType(PropertyNames.StaticListDropDown, PropertyControlType.DropDown); + configUI.SetPropertyControlType(PropertyNames.StaticListRadioButton, PropertyControlType.RadioButton); + configUI.SetPropertyControlType(PropertyNames.StringText, PropertyControlType.TextBox); + + foreach (object propertyName in Enum.GetValues(typeof(PropertyNames))) + { + configUI.SetPropertyControlValue(propertyName, ControlInfoPropertyNames.DisplayName, string.Empty); + } + + return configUI; + } + + protected override void OnCustomizeConfigUIWindowProperties(PropertyCollection props) + { + base.OnCustomizeConfigUIWindowProperties(props); + //props[ControlInfoPropertyNames.WindowIsSizable].Value = true; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + int colorValue = newToken.GetProperty(PropertyNames.Int32ColorWheel).Value; + this.color = ColorBgra.FromOpaqueInt32(colorValue); + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + foreach (Rectangle rect in renderRects) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + for (int x = rect.Left; x < rect.Right; ++x) + { + DstArgs.Surface[x, y] = this.color; + } + } + } + } + } +#endif +} \ No newline at end of file diff --git a/src/Effects/ConvolutionFilterEffect.cs b/src/Effects/ConvolutionFilterEffect.cs new file mode 100644 index 0000000..005b024 --- /dev/null +++ b/src/Effects/ConvolutionFilterEffect.cs @@ -0,0 +1,313 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public abstract class ConvolutionFilterEffect + : Effect + { + private sealed class FExtentKey + { + private int srcLength; + private int weightsLength; + + public override int GetHashCode() + { + return unchecked(srcLength + (weightsLength << 16)); + } + + public override bool Equals(object obj) + { + FExtentKey fek = (FExtentKey)obj; + return srcLength == fek.srcLength && weightsLength == fek.weightsLength; + } + + public FExtentKey(int srcLength, int weightsLength) + { + this.srcLength = srcLength; + this.weightsLength = weightsLength; + } + } + + private class FExtent + { + public int[] fStarts; + public int[] fEnds; + } + + private static Hashtable fCache = new Hashtable(); + private static Queue fCacheQ = new Queue(); + + private static FExtent GetFExtent(int srcLength, int weightsLength) + { + FExtentKey key = new FExtentKey(srcLength, weightsLength); + + FExtent extent; + lock (fCache) + { + extent = (FExtent)fCache[key]; + } + + int fOffset = -weightsLength / 2; + + if (extent == null) + { + extent = new FExtent(); + extent.fStarts = new int[srcLength]; + extent.fEnds = new int[srcLength]; + + for (int dst = 0; dst < srcLength; ++dst) + { + int startSrc = dst + fOffset; + + if (startSrc < 0) + { + extent.fStarts[dst] = -startSrc; + } + else + { + extent.fStarts[dst] = 0; + } + + int end = startSrc + weightsLength; + int endDelta = srcLength - end; + + if (endDelta < 0) + { + extent.fEnds[dst] = weightsLength + endDelta; + } + else + { + extent.fEnds[dst] = weightsLength; + } + } + + lock (fCache) + { + if (fCache.Count > 16) + { + object top = fCacheQ.Dequeue(); + fCache.Remove(top); + } + + fCache[key] = extent; + fCacheQ.Enqueue(key); + } + } + + return extent; + } + + /// + /// Normalizes the weight matrix so that it does not overflow an int when + /// multiplied with a 1-byte channel intensity, and a 1-byte alpha. In + /// order to do this, the sum and maximum must be less than 32768. The + /// values of the matrix will be scaled by a power of two to be just below 32768 + /// + /// The weight matrix to be normalized. + protected void NormalizeWeightMatrix(int[][] weights) + { + int max = 0; + int sum = 0; + int shift = 0; + int width = weights[0].Length; + int height = weights.Length; + + // Find the magnitude sum and maximum of the weights matrix + for (int y = 0; y < weights.Length; y++) + { + int[] row = weights[y]; + + for (int x = 0; x < row.Length; x++) + { + int mag = Math.Abs(row[x]); + max = Math.Max(max, mag); + sum += mag; + } + } + + max = Math.Max(sum, max); + while ((1 << shift) < max) + { + shift++; + } + + shift = 15 - shift; + + //shift it so that it's less than 32768 + for (int y = 0; y < weights.Length; y++) + { + int[] row = weights[y]; + + for (int x = 0; x < row.Length; x++) + { + if (shift < 0) + { + row[x] >>= -shift; + } + else if (shift > 0) + { + row[x] <<= shift; + } + } + } + } + + public unsafe void RenderConvolutionFilter(int[][] weights, int offset, RenderArgs dstArgs, RenderArgs srcArgs, + Rectangle[] rois, int startIndex, int length) + { + int weightsWidth = weights[0].Length; + int weightsHeight = weights.Length; + + int fYOffset = -(weightsHeight / 2); + int fXOffset = -(weightsWidth / 2); + + // we cache the beginning and ending horizontal indices into the weights matrix + // for every source pixel X location + // i.e. for src[x,y], where we're concerned with x, what weight[x,y] horizontal + // extent should we worry about? + // this way we end up with less branches and faster code (hopefully?!) + FExtent fxExtent = GetFExtent(srcArgs.Surface.Width, weightsWidth); + FExtent fyExtent = GetFExtent(srcArgs.Surface.Height, weightsHeight); + + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle roi = rois[ri]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra* dstPixel = dstArgs.Surface.GetPointAddressUnchecked(roi.Left, y); + int fyStart = fyExtent.fStarts[y]; + int fyEnd = fyExtent.fEnds[y]; + + for (int x = roi.Left; x < roi.Right; ++x) + { + int redSum = 0; + int greenSum = 0; + int blueSum = 0; + int alphaSum = 0; + int colorFactor = 0; + int alphaFactor = 0; + int fxStart = fxExtent.fStarts[x]; + int fxEnd = fxExtent.fEnds[x]; + + for (int fy = fyStart; fy < fyEnd; ++fy) + { + int srcY = y + fy + fYOffset; + int srcX1 = x + fXOffset + fxStart; + + ColorBgra* srcPixel = srcArgs.Surface.GetPointAddressUnchecked(srcX1, srcY); + int[] wRow = weights[fy]; + + for (int fx = fxStart; fx < fxEnd; ++fx) + { + int srcX = fx + srcX1; + int weight = wRow[fx]; + + ColorBgra c = *srcPixel; + + alphaFactor += weight; + weight = weight * (c.A + (c.A >> 7)); + colorFactor += weight; + weight >>= 8; + + redSum += c.R * weight; + blueSum += c.B * weight; + greenSum += c.G * weight; + alphaSum += c.A * weight; + + ++srcPixel; + } + } + + colorFactor /= 256; + + if (colorFactor != 0) + { + redSum /= colorFactor; + greenSum /= colorFactor; + blueSum /= colorFactor; + } + else + { + redSum = 0; + greenSum = 0; + blueSum = 0; + } + + if (alphaFactor != 0) + { + alphaSum /= alphaFactor; + } + else + { + alphaSum = 0; + } + + redSum += offset; + greenSum += offset; + blueSum += offset; + alphaSum += offset; + + #region clamp values to [0,255] + if (redSum < 0) + { + redSum = 0; + } + else if (redSum > 255) + { + redSum = 255; + } + + if (greenSum < 0) + { + greenSum = 0; + } + else if (greenSum > 255) + { + greenSum = 255; + } + + if (blueSum < 0) + { + blueSum = 0; + } + else if (blueSum > 255) + { + blueSum = 255; + } + + if (alphaSum < 0) + { + alphaSum = 0; + } + else if (alphaSum > 255) + { + alphaSum = 255; + } + #endregion + + *dstPixel = ColorBgra.FromBgra((byte)blueSum, (byte)greenSum, (byte)redSum, (byte)alphaSum); + ++dstPixel; + } + } + } + } + + internal ConvolutionFilterEffect(string name, Image image, EffectFlags flags) + : base(name, image, flags) + { + } + } +} diff --git a/src/Effects/CurvesEffect.cs b/src/Effects/CurvesEffect.cs new file mode 100644 index 0000000..b071e7b --- /dev/null +++ b/src/Effects/CurvesEffect.cs @@ -0,0 +1,48 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class CurvesEffect + : Effect + { + public CurvesEffect() + : base(PdnResources.GetString("CurvesEffect.Name"), + PdnResources.GetImageResource("Icons.CurvesEffect.png").Reference, + EffectFlags.Configurable) + { + } + + public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) + { + CurvesEffectConfigToken token = parameters as CurvesEffectConfigToken; + + if (token != null) + { + UnaryPixelOp uop = token.Uop; + + for (int i = startIndex; i < startIndex + length; ++i) + { + uop.Apply(dstArgs.Surface, srcArgs.Surface, rois[i]); + } + } + } + + public override EffectConfigDialog CreateConfigDialog() + { + return new CurvesEffectConfigDialog(); + } + } +} diff --git a/src/Effects/CurvesEffectConfigDialog.cs b/src/Effects/CurvesEffectConfigDialog.cs new file mode 100644 index 0000000..8020f85 --- /dev/null +++ b/src/Effects/CurvesEffectConfigDialog.cs @@ -0,0 +1,402 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class CurvesEffectConfigDialog + : EffectConfigDialog + { + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + private CurveControl curveControl; + private Dictionary curveControls; + private System.ComponentModel.IContainer components = null; + private PaintDotNet.HeaderLabel transferHeader; + private System.Windows.Forms.Button resetButton; + private System.Windows.Forms.ComboBox modeComboBox; + private System.EventHandler curveControlValueChangedDelegate; + private EventHandler> curveControlCoordinatesChangedDelegate; + private TableLayoutPanel tableLayoutMain; + private TableLayoutPanel tableLayoutPanelMask; + private EnumLocalizer colorTransferNames; + private CheckBox[] maskCheckBoxes; + private EventHandler maskCheckChanged; + private Label labelCoordinates; + private Label labelHelpText; + private bool finishTokenOnDropDownChanged = true; + + public CurvesEffectConfigDialog() + { + InitializeComponent(); + + curveControlValueChangedDelegate = this.curveControl_ValueChanged; + curveControlCoordinatesChangedDelegate = this.curveControl_CoordinatesChanged; + colorTransferNames = EnumLocalizer.Create(typeof(ColorTransferMode)); + + this.Text = PdnResources.GetString("CurvesEffectConfigDialog.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.resetButton.Text = PdnResources.GetString("CurvesEffectConfigDialog.ResetButton.Text"); + this.transferHeader.Text = PdnResources.GetString("CurvesEffectConfigDialog.TransferHeader.Text"); + this.labelHelpText.Text = PdnResources.GetString("CurvesEffectConfigDialog.HelpText.Text"); + this.modeComboBox.Items.Clear(); + this.modeComboBox.Items.AddRange(colorTransferNames.GetLocalizedNames()); + + this.maskCheckChanged = new EventHandler(MaskCheckChanged); + + this.curveControls = new Dictionary(); + + this.curveControls.Add(ColorTransferMode.Luminosity, new CurveControlLuminosity()); + this.curveControls.Add(ColorTransferMode.Rgb, new CurveControlRgb()); + } + + protected override void InitialInitToken() + { + CurvesEffectConfigToken token = new CurvesEffectConfigToken(); + theEffectToken = token; + } + + protected override void InitTokenFromDialog() + { + ((CurvesEffectConfigToken)EffectToken).ColorTransferMode = curveControl.ColorTransferMode; + ((CurvesEffectConfigToken)EffectToken).ControlPoints = (SortedList[])curveControl.ControlPoints.Clone(); + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + CurvesEffectConfigToken token = (CurvesEffectConfigToken)effectToken; + + bool oldValue = finishTokenOnDropDownChanged; + finishTokenOnDropDownChanged = false; + + switch (token.ColorTransferMode) + { + case ColorTransferMode.Luminosity: + modeComboBox.SelectedItem = colorTransferNames.EnumValueToLocalizedName(ColorTransferMode.Luminosity); + break; + + case ColorTransferMode.Rgb: + modeComboBox.SelectedItem = colorTransferNames.EnumValueToLocalizedName(ColorTransferMode.Rgb); + break; + } + + finishTokenOnDropDownChanged = oldValue; + + curveControl.ControlPoints = (SortedList[])token.ControlPoints.Clone(); + curveControl.Invalidate(); + curveControl.Update(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.transferHeader = new PaintDotNet.HeaderLabel(); + this.resetButton = new System.Windows.Forms.Button(); + this.modeComboBox = new System.Windows.Forms.ComboBox(); + this.labelCoordinates = new Label(); + this.labelHelpText = new Label(); + this.tableLayoutMain = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanelMask = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutMain.SuspendLayout(); + this.SuspendLayout(); + // + // cancelButton + // + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(210, 292); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(81, 23); + this.cancelButton.TabIndex = 5; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // okButton + // + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(130, 292); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(81, 23); + this.okButton.TabIndex = 4; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // transferHeader + // + this.tableLayoutMain.SetColumnSpan(this.transferHeader, 4); + this.transferHeader.Dock = System.Windows.Forms.DockStyle.Fill; + this.transferHeader.Location = new System.Drawing.Point(9, 9); + this.transferHeader.Name = "transferHeader"; + this.transferHeader.RightMargin = 0; + this.transferHeader.Margin = new Padding(1, 3, 1, 1); + this.transferHeader.Size = new System.Drawing.Size(115, 17); + this.transferHeader.TabIndex = 20; + this.transferHeader.TabStop = false; + // + // resetButton + // + this.resetButton.Location = new System.Drawing.Point(9, 292); + this.resetButton.Name = "resetButton"; + this.resetButton.Size = new System.Drawing.Size(81, 23); + this.resetButton.TabIndex = 3; + this.resetButton.FlatStyle = FlatStyle.System; + this.resetButton.Click += new System.EventHandler(this.resetButton_Click); + // + // modeComboBox + // + this.tableLayoutMain.SetColumnSpan(this.modeComboBox, 3); + this.modeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.modeComboBox.Location = new System.Drawing.Point(130, 9); + this.modeComboBox.Name = "modeComboBox"; + this.modeComboBox.Size = new System.Drawing.Size(90, 21); + this.modeComboBox.TabIndex = 23; + this.modeComboBox.SelectedIndexChanged += new System.EventHandler(this.modeComboBox_SelectedIndexChanged); + // + this.labelCoordinates.Dock = DockStyle.Fill; + this.labelCoordinates.TextAlign = ContentAlignment.MiddleCenter; + // + this.tableLayoutMain.SetColumnSpan(this.labelHelpText, 4); + this.labelHelpText.Dock = DockStyle.Fill; + // + // tableLayoutMain + // + this.tableLayoutMain.ColumnCount = 4; + this.tableLayoutMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 88F)); + this.tableLayoutMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 74F)); + this.tableLayoutMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 74F)); + this.tableLayoutMain.Controls.Add(this.resetButton, 0, 5); + this.tableLayoutMain.Controls.Add(this.okButton, 2, 5); + this.tableLayoutMain.Controls.Add(this.cancelButton, 3, 5); + this.tableLayoutMain.Controls.Add(this.transferHeader, 0, 0); + this.tableLayoutMain.Controls.Add(this.tableLayoutPanelMask, 0, 3); + this.tableLayoutMain.Controls.Add(this.modeComboBox, 0, 1); + this.tableLayoutMain.Controls.Add(this.labelCoordinates, 3, 1); + this.tableLayoutMain.Controls.Add(this.labelHelpText, 0, 4); + this.tableLayoutMain.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutMain.Location = new System.Drawing.Point(0, 0); + this.tableLayoutMain.Name = "tableLayoutMain"; + this.tableLayoutMain.Padding = new System.Windows.Forms.Padding(6); + this.tableLayoutMain.Margin = new Padding(2); + this.tableLayoutMain.RowCount = 4; + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize)); + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize)); + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 29F)); + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 29F)); + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize)); + this.tableLayoutMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutMain.Size = new System.Drawing.Size(4,4); + this.tableLayoutMain.TabIndex = 24; + // + // tableLayoutPanelMask + // + this.tableLayoutPanelMask.ColumnCount = 3; + this.tableLayoutMain.SetColumnSpan(this.tableLayoutPanelMask, 4); + this.tableLayoutPanelMask.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanelMask.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanelMask.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanelMask.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanelMask.Location = new System.Drawing.Point(8, 262); + this.tableLayoutPanelMask.Margin = new System.Windows.Forms.Padding(2); + this.tableLayoutPanelMask.Name = "tableLayoutPanelMask"; + this.tableLayoutPanelMask.RowCount = 1; + this.tableLayoutPanelMask.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanelMask.Size = new System.Drawing.Size(277, 25); + this.tableLayoutPanelMask.TabIndex = 24; + // + // CurvesEffectConfigDialog + // + this.AcceptButton = this.okButton; + this.CancelButton = this.cancelButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.ClientSize = new System.Drawing.Size(276, 382); + this.MinimumSize = new Size(260, 276); + this.Controls.Add(this.tableLayoutMain); + this.Name = "CurvesEffectConfigDialog"; + this.Controls.SetChildIndex(this.tableLayoutMain, 0); + this.tableLayoutMain.ResumeLayout(false); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; + this.ResumeLayout(false); + } + #endregion + + protected override void OnLoad(EventArgs e) + { + this.okButton.Select(); + base.OnLoad(e); + } + + private void okButton_Click(object sender, System.EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void cancelButton_Click(object sender, System.EventArgs e) + { + this.Close(); + } + + private void curveControl_ValueChanged(object sender, EventArgs e) + { + this.FinishTokenUpdate(); + } + + private void curveControl_CoordinatesChanged(object sender, EventArgs e) + { + Point pt = e.Data; + string newText; + + if (pt.X >= 0) + { + string format = PdnResources.GetString("CurvesEffectConfigDialog.Coordinates.Format"); + newText = string.Format(format, pt.X, pt.Y); + } + else + { + newText = string.Empty; + } + + if (newText != labelCoordinates.Text) + { + labelCoordinates.Text = newText; + labelCoordinates.Update(); + } + } + + private void resetButton_Click(object sender, System.EventArgs e) + { + curveControl.ResetControlPoints(); + this.FinishTokenUpdate(); + } + + private void MaskCheckChanged(object sender, System.EventArgs e) + { + for (int i = 0; i < maskCheckBoxes.Length; ++i) + { + if (maskCheckBoxes[i] == sender) + { + curveControl.SetSelected(i, maskCheckBoxes[i].Checked); + } + } + + UpdateCheckboxEnables(); + } + + private void modeComboBox_SelectedIndexChanged(object sender, System.EventArgs e) + { + CurveControl newCurveControl; + ColorTransferMode colorTransferMode; + + if (modeComboBox.SelectedIndex >= 0) + { + colorTransferMode = (ColorTransferMode)colorTransferNames.LocalizedNameToEnumValue(modeComboBox.SelectedItem.ToString()); + } + else + { + colorTransferMode = ColorTransferMode.Rgb; + } + + newCurveControl = curveControls[colorTransferMode]; + + if (curveControl != newCurveControl) + { + tableLayoutMain.Controls.Remove(curveControl); + + curveControl = newCurveControl; + + curveControl.Bounds = new Rectangle(0, 0, 258, 258); + curveControl.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + //curveControl.ResetControlPoints(); + tableLayoutMain.SetColumnSpan(this.curveControl, 4); + curveControl.Dock = System.Windows.Forms.DockStyle.Fill; + curveControl.ValueChanged += curveControlValueChangedDelegate; + curveControl.CoordinatesChanged += curveControlCoordinatesChangedDelegate; + tableLayoutMain.Controls.Add(curveControl, 0, 2); + + if (finishTokenOnDropDownChanged) + { + FinishTokenUpdate(); + } + + int channels = newCurveControl.Channels; + + maskCheckBoxes = new CheckBox[channels]; + + this.tableLayoutPanelMask.Controls.Clear(); + this.tableLayoutPanelMask.ColumnCount = channels; + + for (int i = 0; i < channels; ++i) + { + CheckBox checkbox = new CheckBox(); + + checkbox.Dock = DockStyle.Fill; + checkbox.Checked = curveControl.GetSelected(i); + checkbox.CheckedChanged += maskCheckChanged; + checkbox.Text = curveControl.GetChannelName(i); + checkbox.FlatStyle = FlatStyle.System; + + this.tableLayoutPanelMask.Controls.Add(checkbox, i, 0); + this.tableLayoutPanelMask.ColumnStyles[i].SizeType = SizeType.Percent; + this.tableLayoutPanelMask.ColumnStyles[i].Width = 100; + maskCheckBoxes[i] = checkbox; + } + + UpdateCheckboxEnables(); + } + } + + private void UpdateCheckboxEnables() + { + int countChecked = 0; + + for (int i = 0; i < maskCheckBoxes.Length; ++i) + { + if (maskCheckBoxes[i].Checked) + { + ++countChecked; + } + } + + if (maskCheckBoxes.Length == 1) + { + maskCheckBoxes[0].Enabled = false; + } + } + } +} diff --git a/src/Effects/CurvesEffectConfigToken.cs b/src/Effects/CurvesEffectConfigToken.cs new file mode 100644 index 0000000..78b7aab --- /dev/null +++ b/src/Effects/CurvesEffectConfigToken.cs @@ -0,0 +1,148 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.Effects +{ + public class CurvesEffectConfigToken + : EffectConfigToken + { + private SortedList[] controlPoints; + public SortedList[] ControlPoints + { + get + { + return this.controlPoints; + } + + set + { + this.uop = null; + this.controlPoints = value; + } + } + + private ColorTransferMode colorTransferMode; + public ColorTransferMode ColorTransferMode + { + get + { + return this.colorTransferMode; + } + + set + { + this.uop = null; + this.colorTransferMode = value; + } + } + + [NonSerialized] + private UnaryPixelOp uop; + + public UnaryPixelOp Uop + { + get + { + if (this.uop == null) + { + this.uop = MakeUop(); + } + + return uop; + } + } + + private UnaryPixelOp MakeUop() + { + UnaryPixelOp uopRet; + byte[][] transferCurves; + int entries; + + switch (colorTransferMode) + { + case ColorTransferMode.Rgb: + UnaryPixelOps.ChannelCurve cc = new UnaryPixelOps.ChannelCurve(); + transferCurves = new byte[][] { cc.CurveR, cc.CurveG, cc.CurveB }; + entries = 256; + uopRet = cc; + break; + + case ColorTransferMode.Luminosity: + UnaryPixelOps.LuminosityCurve lc = new UnaryPixelOps.LuminosityCurve(); + transferCurves = new byte[][] { lc.Curve }; + entries = 256; + uopRet = lc; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + + int channels = transferCurves.Length; + + for (int channel = 0; channel < channels; ++channel) + { + SortedList channelControlPoints = controlPoints[channel]; + IList xa = channelControlPoints.Keys; + IList ya = channelControlPoints.Values; + SplineInterpolator interpolator = new SplineInterpolator(); + int length = channelControlPoints.Count; + + for (int i = 0; i < length; ++i) + { + interpolator.Add(xa[i], ya[i]); + } + + for (int i = 0; i < entries; ++i) + { + transferCurves[channel][i] = Utility.ClampToByte(interpolator.Interpolate(i)); + } + } + + return uopRet; + } + + public override object Clone() + { + return new CurvesEffectConfigToken(this); + } + + public CurvesEffectConfigToken() + { + controlPoints = new SortedList[1]; + + for (int i = 0; i < this.controlPoints.Length; ++i) + { + SortedList newList = new SortedList(); + + newList.Add(0, 0); + newList.Add(255, 255); + controlPoints[i] = newList; + } + + colorTransferMode = ColorTransferMode.Luminosity; + } + + protected CurvesEffectConfigToken(CurvesEffectConfigToken copyMe) + : base(copyMe) + { + this.uop = copyMe.Uop; + this.colorTransferMode = copyMe.ColorTransferMode; + this.controlPoints = (SortedList[])copyMe.controlPoints.Clone(); + } + } +} diff --git a/src/Effects/DentsEffect.cs b/src/Effects/DentsEffect.cs new file mode 100644 index 0000000..9db0891 --- /dev/null +++ b/src/Effects/DentsEffect.cs @@ -0,0 +1,195 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (C) 2006-2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class DentsEffect + : WarpEffectBase + { + public DentsEffect() + : base(PdnResources.GetString("DentsEffect.Name"), + PdnResources.GetImageResource("Icons.DentsEffectIcon.png").Reference, + SubmenuNames.Distort, + EffectFlags.Configurable) + { + EdgeBehavior = WarpEdgeBehavior.Reflect; + } + + // This is so that each repetition of the effect shows up differently. + private byte instanceSeed = unchecked((byte)DateTime.Now.Ticks); + + private double scaleR; + private double refractionScale; + private double theta; + private double roughness; + private double detail; + private byte seed; + + public enum PropertyNames + { + Scale = 0, + Refraction = 1, + Roughness = 2, + Tension = 3, + Quality = 4, + Seed = 5 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Scale, 25, 1, 200)); + props.Add(new DoubleProperty(PropertyNames.Refraction, 50, 0, 200)); + props.Add(new DoubleProperty(PropertyNames.Roughness, 10, 0, 100)); + props.Add(new DoubleProperty(PropertyNames.Tension, 10, 0, 100)); + + props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + props.Add(new Int32Property(PropertyNames.Seed, 0, 0, 255)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo info = base.OnCreateConfigUI(props); + + info.SetPropertyControlValue( + PropertyNames.Scale, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("DentsEffect.ConfigDialog.ScaleLabel")); + + info.SetPropertyControlValue(PropertyNames.Scale, ControlInfoPropertyNames.UseExponentialScale, true); + + info.SetPropertyControlValue( + PropertyNames.Refraction, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("DentsEffect.ConfigDialog.RefractionLabel")); + + info.SetPropertyControlValue(PropertyNames.Refraction, ControlInfoPropertyNames.UseExponentialScale, true); + + info.SetPropertyControlValue( + PropertyNames.Roughness, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("DentsEffect.ConfigDialog.RoughnessLabel")); + + info.SetPropertyControlValue( + PropertyNames.Tension, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("DentsEffect.ConfigDialog.TensionLabel")); + + info.SetPropertyControlValue(PropertyNames.Tension, ControlInfoPropertyNames.UseExponentialScale, true); + + info.SetPropertyControlValue( + PropertyNames.Quality, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("DentsEffect.ConfigDialog.QualityLabel")); + + info.SetPropertyControlType( + PropertyNames.Seed, + PropertyControlType.IncrementButton); + + info.SetPropertyControlValue( + PropertyNames.Seed, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("DentsEffect.ConfigDialog.SeedLabel")); + + info.SetPropertyControlValue( + PropertyNames.Seed, + ControlInfoPropertyNames.ButtonText, + PdnResources.GetString("DentsEffect.ConfigDialog.SeedButtonText")); + + return info; + } + + protected override void OnSetRenderInfo2(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + double scale = newToken.GetProperty(PropertyNames.Scale).Value; + + double refraction = newToken.GetProperty(PropertyNames.Refraction).Value; + double detail1 = newToken.GetProperty(PropertyNames.Roughness).Value; + double detail2 = detail1; + double roughness = detail2; + + double turbulence = newToken.GetProperty(PropertyNames.Tension).Value; + + int quality = newToken.GetProperty(PropertyNames.Quality).Value; + byte newSeed = (byte)newToken.GetProperty(PropertyNames.Seed).Value; + + this.seed = (byte)(this.instanceSeed ^ newSeed); + + this.scaleR = (400.0 / base.DefaultRadius) / scale; + this.refractionScale = (refraction / 100.0) / scaleR; + this.theta = Math.PI * 2.0 * turbulence / 10.0; + this.roughness = roughness / 100.0; + + double detail3 = 1.0 + (detail2 / 10.0); + + // we don't want the perlin noise frequency components exceeding + // the nyquist limit, so we will limit 'detail' appropriately + double maxDetail = Math.Floor(Math.Log(this.scaleR) / Math.Log(0.5)); + + if (detail3 > maxDetail && maxDetail >= 1.0) + { + this.detail = maxDetail; + } + else + { + this.detail = detail3; + } + + base.Quality = quality; + + base.OnSetRenderInfo2(newToken, dstArgs, srcArgs); + } + + protected override void InverseTransform(ref TransformData data) + { + double x = data.X; + double y = data.Y; + + double ix = x * scaleR; + double iy = y * scaleR; + + double bumpAngle = this.theta * PerlinNoise2D.Noise(ix, iy, this.detail, this.roughness, this.seed); + + data.X = x + (this.refractionScale * Math.Sin(-bumpAngle)); + data.Y = y + (this.refractionScale * Math.Cos(bumpAngle)); + } + } +} diff --git a/src/Effects/DesaturateEffect.cs b/src/Effects/DesaturateEffect.cs new file mode 100644 index 0000000..6ab090b --- /dev/null +++ b/src/Effects/DesaturateEffect.cs @@ -0,0 +1,43 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class DesaturateEffect + : InternalPropertyBasedEffect + { + private UnaryPixelOps.Desaturate desaturateOp; + + protected override PropertyCollection OnCreatePropertyCollection() + { + return PropertyCollection.CreateEmpty(); + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + this.desaturateOp.Apply(DstArgs.Surface, SrcArgs.Surface, rois, startIndex, length); + } + + public DesaturateEffect() + : base(PdnResources.GetString("DesaturateEffect.Name"), + PdnResources.GetImageResource("Icons.DesaturateEffect.png").Reference, + null, + EffectFlags.None) + { + this.desaturateOp = new UnaryPixelOps.Desaturate(); + } + } +} diff --git a/src/Effects/EdgeDetectEffect.cs b/src/Effects/EdgeDetectEffect.cs new file mode 100644 index 0000000..124e121 --- /dev/null +++ b/src/Effects/EdgeDetectEffect.cs @@ -0,0 +1,104 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class EdgeDetectEffect + : ColorDifferenceEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("EdgeDetectEffect.Name"); + } + } + + public enum PropertyNames + { + Angle = 0 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Angle, 45.0, -180.0, +180.0)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("AngleChooserConfigDialog.AngleHeader.Text")); + configUI.SetPropertyControlType(PropertyNames.Angle, PropertyControlType.AngleChooser); + + return configUI; + } + + private double angle; + private double[][] weights; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + + this.weights = new double[3][]; + for (int i = 0; i < this.weights.Length; ++i) + { + this.weights[i] = new double[3]; + } + + // adjust and convert angle to radians + double r = (double)this.angle * 2.0 * Math.PI / 360.0; + + // angle delta for each weight + double dr = Math.PI / 4.0; + + // for r = 0 this builds an edge detect filter pointing straight left + + this.weights[0][0] = Math.Cos(r + dr); + this.weights[0][1] = Math.Cos(r + 2.0 * dr); + this.weights[0][2] = Math.Cos(r + 3.0 * dr); + + this.weights[1][0] = Math.Cos(r); + this.weights[1][1] = 0; + this.weights[1][2] = Math.Cos(r + 4.0 * dr); + + this.weights[2][0] = Math.Cos(r - dr); + this.weights[2][1] = Math.Cos(r - 2.0 * dr); + this.weights[2][2] = Math.Cos(r - 3.0 * dr); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + public EdgeDetectEffect() + : base(StaticName, + PdnResources.GetImageResource("Icons.EdgeDetectEffect.png").Reference, + SubmenuNames.Stylize, + EffectFlags.Configurable) + { + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + base.RenderColorDifferenceEffect(this.weights, DstArgs, SrcArgs, rois, startIndex, length); + } + } +} diff --git a/src/Effects/Effect.cs b/src/Effects/Effect.cs new file mode 100644 index 0000000..3584244 --- /dev/null +++ b/src/Effects/Effect.cs @@ -0,0 +1,318 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public abstract class Effect + { + private const string shortcutKeysObsoleteString = + "Effects may not specify their own ShortcutKeys. Use one of the constructor overloads that does not take a ShortcutKeys parameter."; + + private string name; + private Image image; + private string subMenuName; + private EffectEnvironmentParameters envParams; + private EffectFlags effectFlags; + + private bool setRenderInfoCalled = false; + + internal protected bool SetRenderInfoCalled + { + get + { + return this.setRenderInfoCalled; + } + } + + /// + /// Returns the category of the effect. If there is no EffectCategoryAttribute + /// applied to the runtime type, then the default category, EffectCategory.Effect, + /// will be returned. + /// + /// + /// This controls which menu in the user interface the effect is placed in to. + /// + public EffectCategory Category + { + get + { + object[] attributes = this.GetType().GetCustomAttributes(true); + + foreach (Attribute attribute in attributes) + { + if (attribute is EffectCategoryAttribute) + { + return ((EffectCategoryAttribute)attribute).Category; + } + } + + return EffectCategory.Effect; + } + } + + public EffectEnvironmentParameters EnvironmentParameters + { + get + { + return this.envParams; + } + + set + { + this.envParams = value; + } + } + + [Obsolete] + public EffectDirectives EffectDirectives + { + get + { + if ((this.effectFlags & EffectFlags.SingleThreaded) == EffectFlags.SingleThreaded) + { + return EffectDirectives.SingleThreaded; + } + else + { + return EffectDirectives.None; + } + } + } + + public EffectFlags EffectFlags + { + get + { + return this.effectFlags; + } + } + + public bool CheckForEffectFlags(EffectFlags flags) + { + return (EffectFlags & flags) == flags; + } + + public string SubMenuName + { + get + { + return this.subMenuName; + } + } + + public string Name + { + get + { + return this.name; + } + } + + public Image Image + { + get + { + return this.image; + } + } + + [Obsolete("This property is obsolete. There is no replacement.")] + public Keys ShortcutKeys + { + get + { + return Keys.None; + } + } + + [Obsolete("Use CheckForEffectFlags() instead")] + public bool IsConfigurable + { + get + { + return (EffectFlags & EffectFlags.Configurable) == EffectFlags.Configurable; + } + } + + public void SetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.setRenderInfoCalled = true; + OnSetRenderInfo(parameters, dstArgs, srcArgs); + } + + protected virtual void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs) + { + } + + /// + /// Performs the effect's rendering. The source is to be treated as read-only, + /// and only the destination pixels within the given rectangle-of-interest are + /// to be written to. However, in order to compute the destination pixels, + /// any pixels from the source may be utilized. + /// + /// The parameters to the effect. If IsConfigurable is true, then this must not be null. + /// Describes the destination surface. + /// Describes the source surface. + /// The list of rectangles that describes the region of interest. + /// The index within roi to start enumerating from. + /// The number of rectangles to enumerate from roi. + public abstract void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length); + + public void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois) + { + Render(parameters, dstArgs, srcArgs, rois, 0, rois.Length); + } + + public void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, PdnRegion roi) + { + Rectangle[] scans = roi.GetRegionScansReadOnlyInt(); + Render(parameters, dstArgs, srcArgs, scans, 0, scans.Length); + } + + public virtual EffectConfigDialog CreateConfigDialog() + { + if (CheckForEffectFlags(EffectFlags.Configurable)) + { + throw new NotImplementedException("If IsConfigurable is true, then CreateConfigDialog() must be implemented"); + } + else + { + return null; + } + } + + /// + /// This is a helper function. It allows you to render an effect "in place." + /// That is, you don't need both a destination and a source Surface. + /// + public void RenderInPlace(RenderArgs srcAndDstArgs, PdnRegion roi) + { + using (Surface renderSurface = new Surface(srcAndDstArgs.Surface.Size)) + { + using (RenderArgs renderArgs = new RenderArgs(renderSurface)) + { + Rectangle[] scans = roi.GetRegionScansReadOnlyInt(); + Render(null, renderArgs, srcAndDstArgs, scans); + srcAndDstArgs.Surface.CopySurface(renderSurface, roi); + } + } + } + + public void RenderInPlace(RenderArgs srcAndDstArgs, Rectangle roi) + { + using (PdnRegion region = new PdnRegion(roi)) + { + RenderInPlace(srcAndDstArgs, region); + } + } + + public Effect(string name, Image image) + : this(name, image, EffectFlags.None) + { + } + + public Effect(string name, Image image, EffectFlags flags) + : this(name, image, null, flags) + { + } + + [Obsolete] + public Effect(string name, Image image, bool isConfigurable) + : this(name, image, null, isConfigurable ? EffectFlags.Configurable : EffectFlags.None) + { + } + + [Obsolete(shortcutKeysObsoleteString, true)] + public Effect(string name, Image image, Keys shortcutKeys) + : this(name, image, null) + { + } + + [Obsolete(shortcutKeysObsoleteString, true)] + public Effect(string name, Image image, Keys shortcutKeys, bool isConfigurable) + : this(name, image, null, isConfigurable) + { + } + + [Obsolete(shortcutKeysObsoleteString, true)] + public Effect(string name, Image image, Keys shortcutKeys, string subMenuName) + : this(name, image, subMenuName, EffectDirectives.None) + { + } + + public Effect(string name, Image image, string subMenuName) + : this(name, image, subMenuName, EffectFlags.None) + { + } + + [Obsolete(shortcutKeysObsoleteString, true)] + public Effect(string name, Image image, Keys shortcutKeys, string subMenuName, bool isConfigurable) + : this(name, image, subMenuName, EffectDirectives.None, isConfigurable) + { + } + + [Obsolete] + public Effect(string name, Image image, string subMenuName, EffectDirectives effectDirectives) + : this(name, image, subMenuName, effectDirectives == EffectDirectives.SingleThreaded ? EffectFlags.SingleThreaded : EffectFlags.None) + { + } + + [Obsolete] + public Effect(string name, Image image, string subMenuName, bool isConfigurable) + : this(name, image, subMenuName, isConfigurable ? EffectFlags.Configurable : EffectFlags.None) + { + } + + [Obsolete(shortcutKeysObsoleteString, true)] + public Effect(string name, Image image, Keys shortcutKeys, string subMenuName, EffectDirectives effectDirectives) + : this(name, image, subMenuName, effectDirectives, false) + { + } + + [Obsolete(shortcutKeysObsoleteString, true)] + public Effect(string name, Image image, Keys shortcutKeys, string subMenuName, EffectDirectives effectDirectives, bool isConfigurable) + : this(name, image, subMenuName, effectDirectives, isConfigurable) + { + } + + /// + /// Base constructor for the Effect class. + /// + /// A unique name for the effect. + /// A 16x16 icon for the effect that will show up in the menu. + /// The name of a sub-menu to place the effect into. Pass null for no sub-menu. + /// A set of flags indicating important information about the effect. + /// A flag indicating whether the effect is configurable. If this is true, then CreateConfigDialog must be implemented. + /// + /// Do not include the word 'effect' in the name parameter. + /// The shortcut key is only honored for effects with the [EffectCategory(EffectCategory.Adjustment)] attribute. + /// The sub-menu parameter can be used to group effects. The name parameter must still be unique. + /// + [Obsolete] + public Effect(string name, Image image, string subMenuName, EffectDirectives effectDirectives, bool isConfigurable) + : this(name, image, subMenuName, + (effectDirectives == EffectDirectives.SingleThreaded ? EffectFlags.SingleThreaded : EffectFlags.None) | + (isConfigurable ? EffectFlags.Configurable : EffectFlags.None)) + { + } + + public Effect(string name, Image image, string subMenuName, EffectFlags effectFlags) + { + this.name = name; + this.image = image; + this.subMenuName = subMenuName; + this.effectFlags = effectFlags; + this.envParams = EffectEnvironmentParameters.DefaultParameters; + } + } +} diff --git a/src/Effects/EffectCategory.cs b/src/Effects/EffectCategory.cs new file mode 100644 index 0000000..a1a2d29 --- /dev/null +++ b/src/Effects/EffectCategory.cs @@ -0,0 +1,38 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + /// + /// Categories for effects that determine their placement within + /// Paint.NET's menu hierarchy. + /// + public enum EffectCategory + { + /// + /// The default category for an effect. This will place effects in to the "Effects" menu. + /// + Effect, + + /// + /// Signifies that this effect should be an "Image Adjustment", placing the effect in + /// the "Adjustments" submenu in the "Layers" menu. + /// These types of effects are typically quick to execute. They are also preferably + /// "unary" (see EffectTypeHint) but are not required to be. + /// + Adjustment, + + /// + /// Signifies that this effect should not be displayed in any menu. + /// + DoNotDisplay + } +} diff --git a/src/Effects/EffectCategoryAttribute.cs b/src/Effects/EffectCategoryAttribute.cs new file mode 100644 index 0000000..8c2dd9d --- /dev/null +++ b/src/Effects/EffectCategoryAttribute.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + /// + /// Allows you to categorize an Effect to place it in the appropriate menu + /// within Paint.NET. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class EffectCategoryAttribute : + Attribute + { + private EffectCategory category; + public EffectCategory Category + { + get + { + return category; + } + } + + public EffectCategoryAttribute(EffectCategory category) + { + this.category = category; + } + } +} diff --git a/src/Effects/EffectConfigDialog.cs b/src/Effects/EffectConfigDialog.cs new file mode 100644 index 0000000..109968d --- /dev/null +++ b/src/Effects/EffectConfigDialog.cs @@ -0,0 +1,254 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Drawing; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public class EffectConfigDialog + : PdnBaseForm + { + private Surface effectSourceSurface; + private PdnRegion effectSelection = null; + + /// + /// This is the surface that will be used as the source for rendering. + /// Its contents will not change for the lifetime of this dialog box + /// ("lifetime" being defined as "until Close() is called") + /// Treat this object as read-only. In your OnLoad method, feel free + /// to do any analysis of this surface to populate the dialog box. + /// + [Browsable(false)] + public Surface EffectSourceSurface + { + get + { + return effectSourceSurface; + } + + set + { + effectSourceSurface = value; + } + } + + [Browsable(false)] + public PdnRegion Selection + { + get + { + if (this.DesignMode) + { + return null; + } + else + { + if (effectSelection == null || effectSelection.IsEmpty()) + { + effectSelection = new PdnRegion(); + effectSelection.MakeInfinite(); + } + + return effectSelection; + } + } + + set + { + if (effectSelection != null) + { + effectSelection.Dispose(); + effectSelection = null; + } + + effectSelection = value; + } + } + + internal virtual void OnBeforeConstructor(object context) + { + } + + internal EffectConfigDialog(object context) + { + OnBeforeConstructor(context); + Constructor(); + } + + public EffectConfigDialog() + { + Constructor(); + } + + private void Constructor() + { + InitializeComponent(); + InitialInitToken(); + effectSelection = new PdnRegion(); + effectSelection.MakeInfinite(); + } + + private void InitializeComponent() + { + SuspendLayout(); + + // + // EffectConfigDialog + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(282, 253); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "EffectConfigDialog"; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + + ResumeLayout(false); + } + + /// + /// Overrides Form.OnLoad. + /// + /// + /// + /// Derived classes MUST call this base method if they override it! + /// + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + + try + { + InitDialogFromToken(); + FinishTokenUpdate(); + } + + catch (Exception ex) + { + SystemLayer.Tracing.Ping(ex.ToString()); + throw; + } + } + + [Browsable(false)] + public event EventHandler EffectTokenChanged; + protected virtual void OnEffectTokenChanged() + { + if (EffectTokenChanged != null) + { + EffectTokenChanged(this, EventArgs.Empty); + } + } + + [Obsolete("Use FinishTokenUpdate() instead", true)] + public void UpdateToken() + { + FinishTokenUpdate(); + } + + public void FinishTokenUpdate() + { + InitTokenFromDialog(); + OnEffectTokenChanged(); + } + + /// + /// This method must be overriden in the derived classes. + /// In this you initialize the default values for the token, and + /// thus the default values for the dialog box. + /// The job of this function is to initialize this.theEffectToken with + /// a non-null reference. + /// + protected virtual void InitialInitToken() + { + //throw new InvalidOperationException("InitialInitToken was not implemented, or the derived method called the base method"); + } + + /// + /// This method must be overridden in derived classes. + /// In this method you must take the values from the given EffectToken + /// and use them to properly initialize the dialog's user interface elements. + /// Make sure to read values from the passed-in effectToken + /// + protected virtual void InitDialogFromToken(EffectConfigToken effectTokenCopy) + { + //throw new InvalidOperationException("InitDialogFromToken was not implemented, or the derived method called the base method"); + } + + protected void InitDialogFromToken() + { + // If we don't check for null, we get awful errors in the designer. + // Good idea to check for that anyway, yeah? + if (theEffectToken != null) + { + InitDialogFromToken((EffectConfigToken)theEffectToken.Clone()); + } + } + + /// + /// This method must be overridden in derived classes. + /// In this method you must take the values from the dialog box + /// and use them to properly initialize theEffectToken. + /// + protected virtual void InitTokenFromDialog() + { + //throw new InvalidOperationException("InitTokenFromDialog was not implemented, or the derived method called the base method"); + } + + private Effect effect; + + [Browsable(false)] + public Effect Effect + { + get + { + return effect; + } + + set + { + effect = value; + + if (effect.Image != null) + { + this.Icon = Utility.ImageToIcon(effect.Image, Utility.TransparentKey); + } + else + { + this.Icon = null; + } + } + } + + protected EffectConfigToken theEffectToken; + + [Browsable(false)] + public EffectConfigToken EffectToken + { + get + { + return theEffectToken; + } + + set + { + theEffectToken = value; + OnEffectTokenChanged(); + InitDialogFromToken(); + } + } + } +} diff --git a/src/Effects/EffectConfigDialog.resx b/src/Effects/EffectConfigDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/EffectConfigDialog`2.cs b/src/Effects/EffectConfigDialog`2.cs new file mode 100644 index 0000000..4d99eed --- /dev/null +++ b/src/Effects/EffectConfigDialog`2.cs @@ -0,0 +1,79 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Drawing; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public abstract class EffectConfigDialog + : EffectConfigDialog + where TToken : EffectConfigToken + where TEffect : Effect + { + protected abstract TToken CreateInitialToken(); + + protected override sealed void InitialInitToken() + { + this.theEffectToken = CreateInitialToken(); + } + + protected abstract void InitDialogFromToken(TToken effectTokenCopy); + + protected override sealed void InitDialogFromToken(EffectConfigToken effectTokenCopy) + { + InitDialogFromToken((TToken)effectTokenCopy); + } + + protected abstract void LoadIntoTokenFromDialog(TToken writeValuesHere); + + protected override sealed void InitTokenFromDialog() + { + LoadIntoTokenFromDialog((TToken)this.theEffectToken); + } + + public new TEffect Effect + { + get + { + return (TEffect)base.Effect; + } + + set + { + base.Effect = value; + } + } + + public new TToken EffectToken + { + get + { + return (TToken)base.EffectToken; + } + + set + { + base.EffectToken = value; + } + } + + internal EffectConfigDialog(object context) + : base(context) + { + } + + public EffectConfigDialog() + { + } + } +} \ No newline at end of file diff --git a/src/Effects/EffectConfigToken.cs b/src/Effects/EffectConfigToken.cs new file mode 100644 index 0000000..2c0725b --- /dev/null +++ b/src/Effects/EffectConfigToken.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + [Serializable] + public abstract class EffectConfigToken + : ICloneable + { + /// + /// This should simply call "new myType(this)" ... do not call base class' + /// implementation of Clone, as this is handled by the constructors. + /// + public abstract object Clone(); + + public EffectConfigToken() + { + } + + protected EffectConfigToken(EffectConfigToken copyMe) + { + } + } +} + diff --git a/src/Effects/EffectDirectives.cs b/src/Effects/EffectDirectives.cs new file mode 100644 index 0000000..7de8bf4 --- /dev/null +++ b/src/Effects/EffectDirectives.cs @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + /// + /// Flags that specify important information that an effect rendering host + /// must be aware of and take into consideration when executing a particular + /// effect. + /// + [Obsolete] + [Flags] + public enum EffectDirectives + { + /// + /// No special directive. + /// + None = 0, + + /// + /// Specifies that the effect must only execute in one thread at a time. + /// Normally multiple threads are used in order to increase performance + /// (esp. on dual processor / dual core systems). + /// + /// + /// This does not prevent multiple threads from being used to execute the effect, + /// but guarantees that only one rendering thread will be active at any given + /// time. + /// + SingleThreaded = 1, + } +} diff --git a/src/Effects/EffectEnvironmentParameters.cs b/src/Effects/EffectEnvironmentParameters.cs new file mode 100644 index 0000000..d497eef --- /dev/null +++ b/src/Effects/EffectEnvironmentParameters.cs @@ -0,0 +1,144 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.Effects +{ + public class EffectEnvironmentParameters + : IDisposable + { + public static EffectEnvironmentParameters DefaultParameters + { + get + { + return new EffectEnvironmentParameters(ColorBgra.FromBgra(255, 255, 255, 255), + ColorBgra.FromBgra(0, 0, 0, 255), + 2.0f, + new PdnRegion(), + null); + } + } + + private ColorBgra primaryColor = ColorBgra.FromBgra(0, 0, 0, 0); + private ColorBgra secondaryColor = ColorBgra.FromBgra(0, 0, 0, 0); + private float brushWidth = 0.0f; + private PdnRegion selection; + private bool haveIntersectedSelection = false; + private Surface sourceSurface; + + [Obsolete("This property has been renamed. Use PrimaryColor instead.", true)] + public ColorBgra ForeColor + { + get + { + return PrimaryColor; + } + } + + public ColorBgra PrimaryColor + { + get + { + return this.primaryColor; + } + } + + [Obsolete("This property has been renamed. Use SecondaryColor instead.")] + public ColorBgra BackColor + { + get + { + return SecondaryColor; + } + } + + public ColorBgra SecondaryColor + { + get + { + return this.secondaryColor; + } + } + + public float BrushWidth + { + get + { + return this.brushWidth; + } + } + + public Surface SourceSurface + { + get + { + return this.sourceSurface; + } + } + + /// + /// Gets the user's currently selected area. + /// + /// + /// The bounding rectangle of the surface you will be rendering to. + /// The region returned will be clipped to this bounding rectangle. + /// + /// + /// Note that calls to Render() will already be clipped to this selection area. + /// This data is only useful when an effect wants to change its rendering based + /// on what the user has selected. For instance, This is used by Auto-Levels to + /// only calculate new levels based on what the user has selected + /// + public PdnRegion GetSelection(Rectangle boundingRect) + { + if (!this.haveIntersectedSelection) + { + this.selection.Intersect(boundingRect); + this.haveIntersectedSelection = true; + } + + return this.selection; + } + + public EffectEnvironmentParameters(ColorBgra primaryColor, ColorBgra secondaryColor, float brushWidth, PdnRegion selection, Surface sourceSurface) + { + this.primaryColor = primaryColor; + this.secondaryColor = secondaryColor; + this.brushWidth = brushWidth; + this.selection = (PdnRegion)selection.Clone(); + this.sourceSurface = sourceSurface; + } + + ~EffectEnvironmentParameters() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.selection != null) + { + this.selection.Dispose(); + this.selection = null; + } + } + } + } +} diff --git a/src/Effects/EffectFlags.cs b/src/Effects/EffectFlags.cs new file mode 100644 index 0000000..18ce11f --- /dev/null +++ b/src/Effects/EffectFlags.cs @@ -0,0 +1,24 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.Effects +{ + [Flags] + public enum EffectFlags + : ulong + { + None = 0, + Configurable = 1, + SingleThreaded = 0x8000000000000000 + } +} diff --git a/src/Effects/EffectTypeHint.cs b/src/Effects/EffectTypeHint.cs new file mode 100644 index 0000000..242457b --- /dev/null +++ b/src/Effects/EffectTypeHint.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + [Obsolete] + [Flags] + public enum EffectTypeHint + : int + { + /// + /// Specifies that Paint.NET may make no special assumptions about the effect. + /// This is the default. + /// + NoHints = 0, + + /// + /// Specifies that the effect does its rendering in such a way that changes + /// to a source pixel (x,y) only requires re-rendering of destination pixel + /// (x,y) and none others. + /// For example, Desaturate is Unary, whereas Blur is not. + /// Auto-Levels is not unary because changings any pixel requires the levels + /// computation to be recomputed which in turn affects all other pixels. + /// + Unary = 1, + + /// + /// Specifies that an effect is fast to render. "Fast" is defined as being fast + /// enough, in general, to be used for real-time rendering. This may be used + /// in the future for an implementation of "effect layers" (layers that apply + /// an effect as part of the rendering pipeline). + /// For example, Desaturate and Invert Colors are fast whereas Blur is not. + /// + Fast = 2 + } +} diff --git a/src/Effects/EffectTypeHintAttribute.cs b/src/Effects/EffectTypeHintAttribute.cs new file mode 100644 index 0000000..14733dc --- /dev/null +++ b/src/Effects/EffectTypeHintAttribute.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + [Obsolete] + public sealed class EffectTypeHintAttribute + : Attribute + { + private EffectTypeHint effectTypeHint; + public EffectTypeHint EffectTypeHint + { + get + { + return effectTypeHint; + } + } + + public EffectTypeHintAttribute(EffectTypeHint effectTypeHint) + { + this.effectTypeHint = effectTypeHint; + } + } +} diff --git a/src/Effects/Effect`1.cs b/src/Effects/Effect`1.cs new file mode 100644 index 0000000..e6b585c --- /dev/null +++ b/src/Effects/Effect`1.cs @@ -0,0 +1,118 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public abstract class Effect + : Effect + where TToken : EffectConfigToken + { + private TToken token; + private RenderArgs dstArgs; + private RenderArgs srcArgs; + + protected TToken Token + { + get + { + return this.token; + } + } + + protected RenderArgs DstArgs + { + get + { + return this.dstArgs; + } + } + + protected RenderArgs SrcArgs + { + get + { + return this.srcArgs; + } + } + + protected abstract void OnRender(Rectangle[] renderRects, int startIndex, int length); + + public void Render(Rectangle[] renderRects, int startIndex, int length) + { + if (!this.SetRenderInfoCalled && !this.RenderInfoAvailable) + { + throw new InvalidOperationException("SetRenderInfo() was not called, nor was render info available implicitely"); + } + + OnRender(renderRects, startIndex, length); + } + + protected virtual void OnSetRenderInfo(TToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + } + + protected override sealed void OnSetRenderInfo(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.token = (TToken)parameters; + this.dstArgs = dstArgs; + this.srcArgs = srcArgs; + + this.OnSetRenderInfo((TToken)parameters, dstArgs, srcArgs); + + base.OnSetRenderInfo(parameters, dstArgs, srcArgs); + } + + private bool renderInfoAvailable = false; + internal protected bool RenderInfoAvailable + { + get + { + return this.renderInfoAvailable; + } + } + + public override sealed void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) + { + if (!this.SetRenderInfoCalled) + { + lock (this) + { + this.token = (TToken)parameters; + this.dstArgs = dstArgs; + this.srcArgs = srcArgs; + + this.renderInfoAvailable = true; + + OnSetRenderInfo(this.token, this.dstArgs, this.srcArgs); + } + } + + Render(rois, startIndex, length); + } + + public Effect(string name, Image image) + : base(name, image) + { + } + + public Effect(string name, Image image, string subMenuName) + : base(name, image, subMenuName) + { + } + + public Effect(string name, Image image, string subMenuName, EffectFlags flags) + : base(name, image, subMenuName, flags) + { + } + } +} \ No newline at end of file diff --git a/src/Effects/Effects.csproj b/src/Effects/Effects.csproj new file mode 100644 index 0000000..95d45cb --- /dev/null +++ b/src/Effects/Effects.csproj @@ -0,0 +1,308 @@ + + + Local + 9.0.21022 + 2.0 + {2E4E8805-00F7-4B18-A967-C23994BBCE75} + Debug + AnyCPU + + + + + PaintDotNet.Effects + + + JScript + Grid + IE50 + false + Library + PaintDotNet.Effects + OnBuildSuccess + + + + + + + 2.0 + + + bin\Debug\ + true + 276824064 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + bin\Release\ + true + 276824064 + false + + + TRACE + + + true + 512 + false + + + true + false + false + true + 4 + pdbonly + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + + System + + + + System.Drawing + + + System.Windows.Forms + + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + {66681BB0-955D-451D-A466-94C045B1CF4A} + Data + + + Core + {1EADE568-A866-4DD4-9898-0A151E3F0E26} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + Resources + {0B173113-1F9B-4939-A62F-A176336F13AC} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + {80572820-93A5-4278-A513-D902BEA2639C} + SystemLayer + + + + + Code + + + Form + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + Code + + + + Code + + + + Form + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Form + + + Form + + + Code + + + Code + + + Code + + + + + Code + + + Code + + + + Code + + + + Code + + + + + Code + + + + + Code + + + + Code + + + Form + + + Code + + + + + + Code + + + Code + + + + UserControl + + + + Code + + + + Code + + + + Form + + + + + Code + + + Code + + + + Code + + + UserControl + + + + Form + + + + Code + + + Code + + + + + + Form + + + Code + + + + + Form + + + Code + + + + + + + + + + + + + + @rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/Effects/EffectsCollection.cs b/src/Effects/EffectsCollection.cs new file mode 100644 index 0000000..cf6940d --- /dev/null +++ b/src/Effects/EffectsCollection.cs @@ -0,0 +1,354 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace PaintDotNet.Effects +{ + public sealed class EffectsCollection + { + private Assembly[] assemblies; + private List effects; + + // Assembly, TypeName, Exception[] + private List> loaderExceptions = + new List>(); + + private void AddLoaderException(Triple loaderException) + { + lock (this) + { + this.loaderExceptions.Add(loaderException); + } + } + + public Triple[] GetLoaderExceptions() + { + lock (this) + { + return this.loaderExceptions.ToArray(); + } + } + + public EffectsCollection(List assemblies) + { + this.assemblies = assemblies.ToArray(); + this.effects = null; + } + + public EffectsCollection(List effects) + { + this.assemblies = null; + this.effects = new List(effects); + } + + public Type[] Effects + { + get + { + lock (this) + { + if (this.effects == null) + { + List> errors = new List>(); + this.effects = GetEffectsFromAssemblies(this.assemblies, errors); + + for (int i = 0; i < errors.Count; ++i) + { + AddLoaderException(errors[i]); + } + } + } + + return this.effects.ToArray(); + } + } + + private static Version GetAssemblyVersionFromType(Type type) + { + try + { + Assembly assembly = type.Assembly; + AssemblyName assemblyName = new AssemblyName(assembly.FullName); + return assemblyName.Version; + } + + catch (Exception) + { + return new Version(0, 0, 0, 0); + } + } + + private static bool CheckForGuidOnType(Type type, Guid guid) + { + try + { + object[] attributes = type.GetCustomAttributes(typeof(GuidAttribute), true); + + foreach (GuidAttribute guidAttr in attributes) + { + if (new Guid(guidAttr.Value) == guid) + { + return true; + } + } + } + + catch (Exception) + { + } + + return false; + } + + private static bool CheckForAnyGuidOnType(Type type, Guid[] guids) + { + foreach (Guid guid in guids) + { + if (CheckForGuidOnType(type, guid)) + { + return true; + } + } + + return false; + } + + private static readonly Guid[] deprecatedEffectGuids = + new Guid[] + { + new Guid("9A1EB3D9-0A36-4d32-9BB2-707D6E5A9D2C"), // TwistEffect from old DistortionEffects.dll + new Guid("3154E367-6B4D-4960-B4D8-F6D06E1C9C24"), // TileEffect from old DistortionEffects.dll + new Guid("1445F876-356D-4a7c-B726-50457F6E7AEF"), // PolarInversionEffect or BulgeEffect from old DistortionEffects.dll (accidentally put same guid on both) + new Guid("270DCBF1-CE42-411e-9885-162E2BFA8265"), // GlowEffect from old GlowEffect.dll + }; + + private static string UnstableMessage + { + get + { + return PdnResources.GetString("BlockedPluginException.UnstablePlugin"); + } + } + + private static string BuiltInMessage + { + get + { + return PdnResources.GetString("BlockedPluginException.PluginIsNowBuiltIn"); + } + } + + // effectNamespace, effectName, maxVersion, reason + private static readonly Quadruple[] blockedEffects = + new Quadruple[] + { + // pyrochild's Film effect, v1.0.0.0, does some seriously weird stuff that hard crashes us no matter what + Quadruple.Create("FilmEffect", "FilmEffect", new Version(1, 0, int.MaxValue, int.MaxValue), UnstableMessage), + + // Ed Harvey's Threshold effect, v1.0, uses a timer in its config dialog that NullRef's on us + Quadruple.Create("EdHarvey.Edfects.Effects", "ThresholdEffect", new Version (1, 0, int.MaxValue, int.MaxValue), UnstableMessage), + + // BoltBait's InkSketch, which is now built-in to Paint.NET + Quadruple.Create("InkSketch", "EffectPlugin", new Version(1, 0, int.MaxValue, int.MaxValue), BuiltInMessage), + + // Portrait, which is now built-in to Paint.NET ("Soften Portrait") + Quadruple.Create("PortraitEffect", "EffectPlugin", new Version(1, 0, int.MaxValue, int.MaxValue), BuiltInMessage), + + // Reduce Noice, which is now built-in to Paint.NET + Quadruple.Create("HistogramEffects", "ReduceNoiseEffect", new Version (1, 1, 0, 0), BuiltInMessage), + + // Fragment, which is now built-in + Quadruple.Create("EdHarvey.Edfects.Effects", "FragmentEffect", new Version(3, 20, int.MaxValue, int.MaxValue), BuiltInMessage), + + // Posterize, which is now built-in + Quadruple.Create("EdHarvey.Edfects.Effects", "PosterizeEffect", new Version(3, 31, int.MaxValue, int.MaxValue), BuiltInMessage), + + // Some of pyrochild's effects were using some code in PaintDotNet.exe that is now marked internal. + // For some reason it is not caught by the plugin crash dialog in non-Debug builds. + Quadruple.Create("pyrochild.effects.splatter", "Splatter", new Version(1, 0, int.MaxValue, int.MaxValue), UnstableMessage), + Quadruple.Create("zachwalker.CurvesPlus", "CurvesPlus", new Version(2, 4, int.MaxValue, int.MaxValue), UnstableMessage), + + // Older versions of these were accessing code in paintdotnet.exe, or were using the EventHandler that was moved + Quadruple.Create("pyrochild.effects.gradientmapping", "GradientMapping", new Version(2, 1, int.MaxValue, int.MaxValue), UnstableMessage), + + // GREYCstoration, has known stability issues, and is also superceded by the built-in "Reduce Noise" anyway + Quadruple.Create("GREYCstoration", "BaseEffect", new Version(0, 9, int.MaxValue, int.MaxValue), UnstableMessage), + Quadruple.Create("GREYCstoration", "RestoreEffect", new Version(0, 9, int.MaxValue, int.MaxValue), UnstableMessage) + }; + + private static Dictionary>> indexedBlockedEffects; + + private static Exception IsBannedEffect(Type effectType) + { + // Never block a built-in effect. + if (effectType.Assembly == typeof(Effect).Assembly) + { + return null; + } + + // Make an index of that big Quadruple[] up there. Otherwise we may never scale well because + // we'll end up with the performance proportional to installed plugins * blocked plugins. + if (indexedBlockedEffects == null) + { + lock (blockedEffects) + { + indexedBlockedEffects = new Dictionary>>(StringComparer.InvariantCultureIgnoreCase); + + foreach (var blockedEffect in blockedEffects) + { + string blockedEffectNamespace = blockedEffect.First; + var blockedEffectDetails = blockedEffect.GetTriple234(); + + List> blockedEffectDetailsSet; + if (!indexedBlockedEffects.TryGetValue(blockedEffectNamespace, out blockedEffectDetailsSet)) + { + blockedEffectDetailsSet = new List>(); + indexedBlockedEffects.Add(blockedEffectNamespace, blockedEffectDetailsSet); + } + + blockedEffectDetailsSet.Add(blockedEffectDetails); + } + } + } + + // Gather the effect's details. + Version assemblyVersion = GetAssemblyVersionFromType(effectType); + string effectNamespace = effectType.Namespace; + string effectName = effectType.Name; + +#if DEBUG + /* + // Makes it easy to copy+paste into the big Quadruple[] up there + SystemLayer.Tracing.Ping(string.Format( + "IsBannedEffect -- Quadruple.Create(\"{0}\", \"{1}\", new Version({2}, {3}, {4}, {5}), ...)", + effectNamespace, + effectName, + assemblyVersion.Major, + assemblyVersion.Minor, + assemblyVersion.Build, + assemblyVersion.Revision)); + * */ +#endif + + // Block list #1 + List> blockedEffectDetailsList2; + if (indexedBlockedEffects.TryGetValue(effectNamespace, out blockedEffectDetailsList2)) + { + foreach (var blockedEffectDetails2 in blockedEffectDetailsList2) + { + if (0 == string.Compare(effectName, blockedEffectDetails2.First, StringComparison.InvariantCultureIgnoreCase) && + assemblyVersion <= blockedEffectDetails2.Second) + { + return new BlockedPluginException(blockedEffectDetails2.Third); + } + } + } + + // Block list #2 -- Block based on Guid and namespace + if (CheckForAnyGuidOnType(effectType, deprecatedEffectGuids) && + (0 == string.Compare(effectNamespace, "GlowEffect", StringComparison.InvariantCultureIgnoreCase) || + 0 == string.Compare(effectNamespace, "DistortionEffects", StringComparison.InvariantCultureIgnoreCase))) + { + return new BlockedPluginException(); + } + + return null; + } + + private static List GetEffectsFromAssemblies(Assembly[] assemblies, IList> errorsResult) + { + List effects = new List(); + + foreach (Assembly assembly in assemblies) + { + GetEffectsFromAssembly(assembly, effects, errorsResult); + } + + List removeUs = new List(); + + foreach (Type effectType in effects) + { + Exception bannedEx = IsBannedEffect(effectType); + + if (bannedEx != null) + { + removeUs.Add(effectType); + errorsResult.Add(Triple.Create(effectType.Assembly, effectType, bannedEx)); + } + } + + foreach (Type removeThisType in removeUs) + { + effects.Remove(removeThisType); + } + + return effects; + } + + private static void GetEffectsFromAssembly(Assembly assembly, IList effectsResult, IList> errorsResult) + { + try + { + Type[] types = GetTypesFromAssembly(assembly, errorsResult); + + foreach (Type type in types) + { + if (type.IsSubclassOf(typeof(Effect)) && !type.IsAbstract && !Utility.IsObsolete(type, false)) + { + effectsResult.Add(type); + } + } + } + + catch (ReflectionTypeLoadException) + { + } + } + + private static Type[] GetTypesFromAssembly(Assembly assembly, IList> errorsResult) + { + Type[] types; + + try + { + types = assembly.GetTypes(); + } + + catch (ReflectionTypeLoadException rex) + { + List typesList = new List(); + Type[] rexTypes = rex.Types; + + foreach (Type rexType in rexTypes) + { + if (rexType != null) + { + typesList.Add(rexType); + } + } + + foreach (Exception loadEx in rex.LoaderExceptions) + { + // Set Type to null, and the error dialog will look at the exception, see it is a TypeLoadException, + // and use the TypeName property from there. + errorsResult.Add(Triple.Create(assembly, null, loadEx)); + } + + types = typesList.ToArray(); + } + + return types; + } + } +} diff --git a/src/Effects/EmbossEffect.cs b/src/Effects/EmbossEffect.cs new file mode 100644 index 0000000..fe06af9 --- /dev/null +++ b/src/Effects/EmbossEffect.cs @@ -0,0 +1,175 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class EmbossEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("EmbossEffect.Name"); + } + } + + public enum PropertyNames + { + Angle = 0 + } + + public EmbossEffect() + : base(StaticName, + PdnResources.GetImageResource("Icons.EmbossEffect.png").Reference, + SubmenuNames.Stylize, + EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Angle, 0.0, -180.0, +180.0)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("AngleChooserConfigDialog.AngleHeader.Text")); + configUI.SetPropertyControlType(PropertyNames.Angle, PropertyControlType.AngleChooser); + + return configUI; + } + + private double angle; + private double[][] weights; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + + // adjust and convert angle to radians + double r = (double)this.angle * 2.0 * Math.PI / 360.0; + + // angle delta for each weight + double dr = Math.PI / 4.0; + + // for r = 0 this builds an emboss filter pointing straight left + this.weights = new double[3][]; + + for (int i = 0; i < 3; ++i) + { + this.weights[i] = new double[3]; + } + + this.weights[0][0] = Math.Cos(r + dr); + this.weights[0][1] = Math.Cos(r + 2.0 * dr); + this.weights[0][2] = Math.Cos(r + 3.0 * dr); + + this.weights[1][0] = Math.Cos(r); + this.weights[1][1] = 0; + this.weights[1][2] = Math.Cos(r + 4.0 * dr); + + this.weights[2][0] = Math.Cos(r - dr); + this.weights[2][1] = Math.Cos(r - 2.0 * dr); + this.weights[2][2] = Math.Cos(r - 3.0 * dr); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle rect = rois[i]; + + // loop through each line of target rectangle + for (int y = rect.Top; y < rect.Bottom; ++y) + { + int fyStart = 0; + int fyEnd = 3; + + if (y == src.Bounds.Top) + { + fyStart = 1; + } + + if (y == src.Bounds.Bottom - 1) + { + fyEnd = 2; + } + + // loop through each point in the line + ColorBgra *dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + int fxStart = 0; + int fxEnd = 3; + + if (x == src.Bounds.Left) + { + fxStart = 1; + } + + if (x == src.Bounds.Right - 1) + { + fxEnd = 2; + } + + // loop through each weight + double sum = 0.0; + + for (int fy = fyStart; fy < fyEnd; ++fy) + { + for (int fx = fxStart; fx < fxEnd; ++fx) + { + double weight = this.weights[fy][fx]; + ColorBgra c = src.GetPointUnchecked(x - 1 + fx, y - 1 + fy); + double intensity = (double)c.GetIntensityByte(); + sum += weight * intensity; + } + } + + int iSum = (int)sum + 128; + + if (iSum > 255) + { + iSum = 255; + } + else if (iSum < 0) + { + iSum = 0; + } + + *dstPtr = ColorBgra.FromBgra((byte)iSum, (byte)iSum, (byte)iSum, 255); + + ++dstPtr; + } + } + } + } + } +} diff --git a/src/Effects/FragmentEffect.cs b/src/Effects/FragmentEffect.cs new file mode 100644 index 0000000..ab57f33 --- /dev/null +++ b/src/Effects/FragmentEffect.cs @@ -0,0 +1,161 @@ +// Copyright (c) 2007,2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; + +namespace PaintDotNet.Effects +{ + public sealed class FragmentEffect + : InternalPropertyBasedEffect + { + public FragmentEffect() + : base(PdnResources.GetString("FragmentEffect.Name"), + PdnResources.GetImageResource("Icons.FragmentEffectIcon.png").Reference, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Fragments = 0, + Rotation = 1, + Distance = 2 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List properties = new List(); + + properties.Add(new Int32Property(PropertyNames.Fragments, 4, 2, 50)); + properties.Add(new Int32Property(PropertyNames.Distance, 8, 0, 100)); + properties.Add(new DoubleProperty(PropertyNames.Rotation, 0, 0, 360)); + + return new PropertyCollection(properties); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = PropertyBasedEffect.CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue( + PropertyNames.Fragments, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("FragmentEffect.ConfigDialog.Fragments.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.Distance, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("FragmentEffect.ConfigDialog.Distance.DisplayName")); + + configUI.SetPropertyControlValue( + PropertyNames.Rotation, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("FragmentEffect.ConfigDialog.Rotation.DisplayName")); + + configUI.SetPropertyControlType(PropertyNames.Rotation, PropertyControlType.AngleChooser); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + int fragments = newToken.GetProperty(PropertyNames.Fragments).Value; + double rotation = newToken.GetProperty(PropertyNames.Rotation).Value; + int distance = newToken.GetProperty(PropertyNames.Distance).Value; + + RecalcPointOffsets(fragments, rotation, distance); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + private void RecalcPointOffsets(int fragments, double rotationAngle, int distance) + { + double pointStep = 2 * Math.PI / (double)fragments; + double rotationRadians = ((rotationAngle - 90.0) * Math.PI) / 180.0; + double offsetAngle = pointStep; + + this.pointOffsets = new Point[fragments]; + + for (int i = 0; i < fragments; i++) + { + double currentRadians = rotationRadians + (pointStep * i); + + this.pointOffsets[i] = new Point( + (int)Math.Round(distance * -Math.Sin(currentRadians), MidpointRounding.AwayFromZero), + (int)Math.Round(distance * -Math.Cos(currentRadians), MidpointRounding.AwayFromZero)); + } + } + + private Point[] pointOffsets = null; + + protected override unsafe void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + int poLength = this.pointOffsets.Length; + Point *pointOffsets = stackalloc Point[poLength]; + for (int i = 0; i < poLength; ++i) + { + pointOffsets[i] = this.pointOffsets[i]; + } + + ColorBgra* samples = stackalloc ColorBgra[poLength]; + + for (int n = startIndex; n < startIndex + length; ++n) + { + Rectangle rect = renderRects[n]; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; x++) + { + int sampleCount = 0; + + for (int i = 0; i < poLength; ++i) + { + int u = x - pointOffsets[i].X; + int v = y - pointOffsets[i].Y; + + if (u >= 0 && u < src.Bounds.Width && v >= 0 && v < src.Bounds.Height) + { + samples[sampleCount] = src.GetPointUnchecked(u, v); + ++sampleCount; + } + } + + *dstPtr = ColorBgra.Blend(samples, sampleCount); + ++dstPtr; + } + } + } + } + } +} diff --git a/src/Effects/FrostedGlassEffect.cs b/src/Effects/FrostedGlassEffect.cs new file mode 100644 index 0000000..d545ec7 --- /dev/null +++ b/src/Effects/FrostedGlassEffect.cs @@ -0,0 +1,162 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Original C++ implementation by Jason Waltman as part of "Filter Explorer," +// http://www.jasonwaltman.com/thesis/index.html + +using PaintDotNet; +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using PaintDotNet.Effects; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class FrostedGlassEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("FrostedGlassEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.FrostedGlassEffect.png").Reference; + } + } + + [ThreadStatic] + private static Random threadRand; + + public FrostedGlassEffect() + : base(StaticName, + StaticImage, + SubmenuNames.Distort, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + MaxScatterRadius = 0, + MinScatterRadius = 1, + NumSamples = 2 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.MaxScatterRadius, 3.0, 0.0, 200.0)); + props.Add(new DoubleProperty(PropertyNames.MinScatterRadius, 0.0, 0.0, 200.0)); + props.Add(new Int32Property(PropertyNames.NumSamples, 2, 1, 8)); + + List propertyRules = new List(); + propertyRules.Add(new SoftMutuallyBoundMinMaxRule(PropertyNames.MinScatterRadius, PropertyNames.MaxScatterRadius)); + + PropertyCollection propertyCollection = new PropertyCollection(props, propertyRules); + return propertyCollection; + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.MaxScatterRadius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("FrostedGlassEffect.ConfigDialog.MaxScatterRadius.DisplayName")); + configUI.FindControlForPropertyName(PropertyNames.MaxScatterRadius).ControlProperties["UseExponentialScale"].Value = true; + configUI.SetPropertyControlValue(PropertyNames.MinScatterRadius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("FrostedGlassEffect.ConfigDialog.MinScatterRadius.DisplayName")); + configUI.FindControlForPropertyName(PropertyNames.MinScatterRadius).ControlProperties["UseExponentialScale"].Value = true; + configUI.SetPropertyControlValue(PropertyNames.NumSamples, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("FrostedGlassEffect.ConfigDialog.NumSamples.DisplayName")); + + return configUI; + } + + private double minRadius; + private double maxRadius; + private int sampleCount; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + double minRadiusP = newToken.GetProperty(PropertyNames.MinScatterRadius).Value; + + this.minRadius = Math.Min(minRadiusP, Math.Min(srcArgs.Width, srcArgs.Height) / 2); + this.maxRadius = newToken.GetProperty(PropertyNames.MaxScatterRadius).Value; + this.sampleCount = newToken.GetProperty(PropertyNames.NumSamples).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + double radiusDelta = this.maxRadius - this.minRadius; + double effectiveRadiusDelta = radiusDelta; + + if (threadRand == null) + { + threadRand = new Random(unchecked(System.Threading.Thread.CurrentThread.GetHashCode() ^ + unchecked((int)DateTime.Now.Ticks))); + } + + ColorBgra* samples = stackalloc ColorBgra[sampleCount]; + + Random localRand = threadRand; + + for (int r = startIndex; r < startIndex + length; ++r) + { + Rectangle roi = rois[r]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddressUnchecked(roi.Left, y); + + for (int x = roi.Left; x < roi.Right; ++x) + { + for (int sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) + { + double srcX; + double srcY; + + while (true) + { + double angle = localRand.NextDouble() * Math.PI * 2.0; + double distanceDelta = localRand.NextDouble() * effectiveRadiusDelta; + double distance = distanceDelta + this.minRadius; + + srcX = x + (Math.Cos(angle) * distance); + srcY = y + (Math.Sin(angle) * distance); + + if (srcX >= 0 && srcX <= SrcArgs.Width && srcY >= 0 && srcY <= SrcArgs.Height) + { + break; + } + } + + samples[sampleIndex] = SrcArgs.Surface.GetBilinearSample((float)srcX, (float)srcY); + } + + ColorBgra result = ColorBgra.Blend(samples, sampleCount); + + dstPtr->Bgra = result.Bgra; + ++dstPtr; + } + } + } + } + } +} diff --git a/src/Effects/GaussianBlurEffect.cs b/src/Effects/GaussianBlurEffect.cs new file mode 100644 index 0000000..1bf9068 --- /dev/null +++ b/src/Effects/GaussianBlurEffect.cs @@ -0,0 +1,356 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class GaussianBlurEffect + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + Radius = 0 + } + + public static string StaticName + { + get + { + return PdnResources.GetString("BlurEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.BlurEffect.png").Reference; + } + } + + + public GaussianBlurEffect() + : base(StaticName, + StaticImage, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Radius, 2, 0, 200)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + // TODO: add units text property to slider? + configUI.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("BlurEffect.ConfigDialog.SliderLabel")); + //aecg.SliderUnitsName = PdnResources.GetString("BlurEffect.ConfigDialog.SliderUnitsName"); + + return configUI; + } + + public static int[] CreateGaussianBlurRow(int amount) + { + int size = 1 + (amount * 2); + int[] weights = new int[size]; + + for (int i = 0; i <= amount; ++i) + { + // 1 + aa - aa + 2ai - ii + weights[i] = 16 * (i + 1); + weights[weights.Length - i - 1] = weights[i]; + } + + return weights; + } + + [Obsolete("Do not use this method. It will be removed in a future release.")] + public static int[][] CreateGaussianBlurMatrix(int amount) + { + int size = 1 + (amount * 2); + int center = size / 2; + int[][] weights = new int[size][]; + + for (int i = 0; i < size; ++i) + { + weights[i] = new int[size]; + + for (int j = 0; j < size; ++j) + { + weights[i][j] = (int)(16 * Math.Sqrt(((j - center) * (j - center)) + ((i - center) * (i - center)))); + } + } + + int max = 0; + for (int i = 0; i < size; ++i) + { + for (int j = 0; j < size; ++j) + { + if (weights[i][j] > max) + { + max = weights[i][j]; + } + } + } + + for (int i = 0; i < size; ++i) + { + for (int j = 0; j < size; ++j) + { + weights[i][j] = max - weights[i][j]; + } + } + + return weights; + } + + private int radius; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.radius = newToken.GetProperty(PropertyNames.Radius).Value; + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + if (this.radius == 0) + { + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + DstArgs.Surface.CopySurface(SrcArgs.Surface, rois[ri].Location, rois[ri]); + } + + return; + } + + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + int r = this.radius; + int[] w = CreateGaussianBlurRow(r); + int wlen = w.Length; + + int localStoreSize = wlen * 6 * sizeof(long); + byte* localStore = stackalloc byte[localStoreSize]; + byte* p = localStore; + + long* waSums = (long*)p; + p += wlen * sizeof(long); + + long* wcSums = (long*)p; + p += wlen * sizeof(long); + + long* aSums = (long*)p; + p += wlen * sizeof(long); + + long* bSums = (long*)p; + p += wlen * sizeof(long); + + long* gSums = (long*)p; + p += wlen * sizeof(long); + + long* rSums = (long*)p; + p += wlen * sizeof(long); + + ulong arraysLength = (ulong)(sizeof(long) * wlen); + + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle rect = rois[ri]; + + if (rect.Height >= 1 && rect.Width >= 1) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + Memory.SetToZero(localStore, (ulong)localStoreSize); + + long waSum = 0; + long wcSum = 0; + long aSum = 0; + long bSum = 0; + long gSum = 0; + long rSum = 0; + + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + for (int wx = 0; wx < wlen; ++wx) + { + int srcX = rect.Left + wx - r; + waSums[wx] = 0; + wcSums[wx] = 0; + aSums[wx] = 0; + bSums[wx] = 0; + gSums[wx] = 0; + rSums[wx] = 0; + + if (srcX >= 0 && srcX < src.Width) + { + for (int wy = 0; wy < wlen; ++wy) + { + int srcY = y + wy - r; + + if (srcY >= 0 && srcY < src.Height) + { + ColorBgra c = src.GetPointUnchecked(srcX, srcY); + int wp = w[wy]; + + waSums[wx] += wp; + wp *= c.A + (c.A >> 7); + wcSums[wx] += wp; + wp >>= 8; + + aSums[wx] += wp * c.A; + bSums[wx] += wp * c.B; + gSums[wx] += wp * c.G; + rSums[wx] += wp * c.R; + } + } + + int wwx = w[wx]; + waSum += wwx * waSums[wx]; + wcSum += wwx * wcSums[wx]; + aSum += wwx * aSums[wx]; + bSum += wwx * bSums[wx]; + gSum += wwx * gSums[wx]; + rSum += wwx * rSums[wx]; + } + } + + wcSum >>= 8; + + if (waSum == 0 || wcSum == 0) + { + dstPtr->Bgra = 0; + } + else + { + int alpha = (int)(aSum / waSum); + int blue = (int)(bSum / wcSum); + int green = (int)(gSum / wcSum); + int red = (int)(rSum / wcSum); + + dstPtr->Bgra = ColorBgra.BgraToUInt32(blue, green, red, alpha); + } + + ++dstPtr; + + for (int x = rect.Left + 1; x < rect.Right; ++x) + { + for (int i = 0; i < wlen - 1; ++i) + { + waSums[i] = waSums[i + 1]; + wcSums[i] = wcSums[i + 1]; + aSums[i] = aSums[i + 1]; + bSums[i] = bSums[i + 1]; + gSums[i] = gSums[i + 1]; + rSums[i] = rSums[i + 1]; + } + + waSum = 0; + wcSum = 0; + aSum = 0; + bSum = 0; + gSum = 0; + rSum = 0; + + int wx; + for (wx = 0; wx < wlen - 1; ++wx) + { + long wwx = (long)w[wx]; + waSum += wwx * waSums[wx]; + wcSum += wwx * wcSums[wx]; + aSum += wwx * aSums[wx]; + bSum += wwx * bSums[wx]; + gSum += wwx * gSums[wx]; + rSum += wwx * rSums[wx]; + } + + wx = wlen - 1; + + waSums[wx] = 0; + wcSums[wx] = 0; + aSums[wx] = 0; + bSums[wx] = 0; + gSums[wx] = 0; + rSums[wx] = 0; + + int srcX = x + wx - r; + + if (srcX >= 0 && srcX < src.Width) + { + for (int wy = 0; wy < wlen; ++wy) + { + int srcY = y + wy - r; + + if (srcY >= 0 && srcY < src.Height) + { + ColorBgra c = src.GetPointUnchecked(srcX, srcY); + int wp = w[wy]; + + waSums[wx] += wp; + wp *= c.A + (c.A >> 7); + wcSums[wx] += wp; + wp >>= 8; + + aSums[wx] += wp * (long)c.A; + bSums[wx] += wp * (long)c.B; + gSums[wx] += wp * (long)c.G; + rSums[wx] += wp * (long)c.R; + } + } + + int wr = w[wx]; + waSum += (long)wr * waSums[wx]; + wcSum += (long)wr * wcSums[wx]; + aSum += (long)wr * aSums[wx]; + bSum += (long)wr * bSums[wx]; + gSum += (long)wr * gSums[wx]; + rSum += (long)wr * rSums[wx]; + } + + wcSum >>= 8; + + if (waSum == 0 || wcSum == 0) + { + dstPtr->Bgra = 0; + } + else + { + int alpha = (int)(aSum / waSum); + int blue = (int)(bSum / wcSum); + int green = (int)(gSum / wcSum); + int red = (int)(rSum / wcSum); + + dstPtr->Bgra = ColorBgra.BgraToUInt32(blue, green, red, alpha); + } + + ++dstPtr; + } + } + } + } + } + } +} diff --git a/src/Effects/GlowEffect.cs b/src/Effects/GlowEffect.cs new file mode 100644 index 0000000..1e9826d --- /dev/null +++ b/src/Effects/GlowEffect.cs @@ -0,0 +1,137 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class GlowEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("GlowEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.GlowEffect.png").Reference; + } + } + + public enum PropertyNames + { + Radius = 0, + Brightness = 1, + Contrast = 2 + } + + private GaussianBlurEffect blurEffect = new GaussianBlurEffect(); + private PropertyCollection blurProps; + private BrightnessAndContrastAdjustment bcAdjustment = new BrightnessAndContrastAdjustment(); + private PropertyCollection bcProps; + + private UserBlendOps.ScreenBlendOp screenBlendOp = new UserBlendOps.ScreenBlendOp(); + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Radius, 6, 1, 20)); + props.Add(new Int32Property(PropertyNames.Brightness, 10, -100, +100)); + props.Add(new Int32Property(PropertyNames.Contrast, 10, -100, +100)); + + return new PropertyCollection(props); + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + PropertyCollection blurValues = this.blurProps.Clone(); + blurValues[GaussianBlurEffect.PropertyNames.Radius].Value = newToken.GetProperty(PropertyNames.Radius).Value; + PropertyBasedEffectConfigToken blurToken = new PropertyBasedEffectConfigToken(blurValues); + this.blurEffect.SetRenderInfo(blurToken, dstArgs, srcArgs); + + PropertyCollection bcValues = this.bcProps.Clone(); + + bcValues[BrightnessAndContrastAdjustment.PropertyNames.Brightness].Value = + newToken.GetProperty(PropertyNames.Brightness).Value; + + bcValues[BrightnessAndContrastAdjustment.PropertyNames.Contrast].Value = + newToken.GetProperty(PropertyNames.Contrast).Value; + + PropertyBasedEffectConfigToken bcToken = new PropertyBasedEffectConfigToken(bcValues); + this.bcAdjustment.SetRenderInfo(bcToken, dstArgs, dstArgs); // have to do adjustment in place, hence dstArgs for both 'args' parameters + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender( + Rectangle[] rois, + int startIndex, + int length) + { + // First we blur the source, and write the result to the destination surface + // Then we apply Brightness/Contrast with the input as the dst, and the output as the dst + // Third, we apply the Screen blend operation so that dst = dst OVER src + + this.blurEffect.Render(rois, startIndex, length); + this.bcAdjustment.Render(rois, startIndex, length); + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle roi = rois[i]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddressUnchecked(roi.Left, y); + ColorBgra* srcPtr = SrcArgs.Surface.GetPointAddressUnchecked(roi.Left, y); + + screenBlendOp.Apply(dstPtr, srcPtr, dstPtr, roi.Width); + } + } + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("GlowEffect.Amount1.Name")); + configUI.SetPropertyControlValue(PropertyNames.Brightness, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("GlowEffect.Amount2.Name")); + configUI.SetPropertyControlValue(PropertyNames.Contrast, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("GlowEffect.Amount3.Name")); + + return configUI; + } + + public GlowEffect() + : base(StaticName, StaticImage, SubmenuNames.Photo, EffectFlags.Configurable) + { + this.blurEffect = new GaussianBlurEffect(); + this.blurProps = this.blurEffect.CreatePropertyCollection(); + + this.bcAdjustment = new BrightnessAndContrastAdjustment(); + this.bcProps = this.bcAdjustment.CreatePropertyCollection(); + + this.screenBlendOp = new UserBlendOps.ScreenBlendOp(); + } + } +} diff --git a/src/Effects/HueAndSaturationAdjustment.cs b/src/Effects/HueAndSaturationAdjustment.cs new file mode 100644 index 0000000..0f8e010 --- /dev/null +++ b/src/Effects/HueAndSaturationAdjustment.cs @@ -0,0 +1,115 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class HueAndSaturationAdjustment + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("HueAndSaturationAdjustment.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.HueAndSaturationAdjustment.png").Reference; + } + } + + public HueAndSaturationAdjustment() + : base(StaticName, + StaticImage, + null, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Hue = 0, + Saturation = 1, + Lightness = 2 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Hue, 0, -180, +180)); + props.Add(new Int32Property(PropertyNames.Saturation, 100, 0, 200)); + props.Add(new Int32Property(PropertyNames.Lightness, 0, -100, +100)); + + return new PropertyCollection(props); + } + + private int hue; + private int saturation; + private int lightness; + private UnaryPixelOp pixelOp; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.hue = newToken.GetProperty(PropertyNames.Hue).Value; + this.saturation = newToken.GetProperty(PropertyNames.Saturation).Value; + this.lightness = newToken.GetProperty(PropertyNames.Lightness).Value; + + // map the range [0,100] -> [0,100] and the range [101,200] -> [103,400] + if (this.saturation > 100) + { + this.saturation = ((this.saturation - 100) * 3) + 100; + } + + if (this.hue == 0 && this.saturation == 100 && this.lightness == 0) + { + this.pixelOp = new UnaryPixelOps.Identity(); + } + else + { + this.pixelOp = new UnaryPixelOps.HueSaturationLightness(this.hue, this.saturation, this.lightness); + } + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Hue, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("HueAndSaturationAdjustment.Amount1Label")); + configUI.SetPropertyControlValue(PropertyNames.Saturation, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("HueAndSaturationAdjustment.Amount2Label")); + configUI.SetPropertyControlValue(PropertyNames.Lightness, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("HueAndSaturationAdjustment.Amount3Label")); + + return configUI; + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + this.pixelOp.Apply(dst, src, rois, startIndex, length); + } + } +} diff --git a/src/Effects/InkSketchEffect.cs b/src/Effects/InkSketchEffect.cs new file mode 100644 index 0000000..0860818 --- /dev/null +++ b/src/Effects/InkSketchEffect.cs @@ -0,0 +1,236 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// This effect was graciously provided by David Issel, aka BoltBait. His original +// copyright and license (MIT License) are reproduced below. + +/* +InkSketchEffect.cs +Copyright (c) 2007 David Issel +Contact info: BoltBait@hotmail.com http://www.BoltBait.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class InkSketchEffect + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + InkOutline = 0, + Coloring = 1 + } + + public static string StaticName + { + get + { + return PdnResources.GetString("InkSketchEffect.Name"); + } + } + + public static Image StaticIcon + { + get + { + return PdnResources.GetImageResource("Icons.InkSketchEffectIcon.png").Reference; + } + } + + public InkSketchEffect() + : base(StaticName, StaticIcon, SubmenuNames.Artistic, EffectFlags.Configurable) + { + this.glowEffect = new GlowEffect(); + this.glowProps = this.glowEffect.CreatePropertyCollection(); + } + + static InkSketchEffect() + { + conv = new int[5][]; + + for (int i = 0; i < conv.Length; ++i) + { + conv[i] = new int[5]; + } + + conv[0] = new int[] { -1, -1, -1, -1, -1 }; + conv[1] = new int[] { -1, -1, -1, -1, -1 }; + conv[2] = new int[] { -1, -1, 30, -1, -1 }; + conv[3] = new int[] { -1, -1, -1, -1, -1 }; + conv[4] = new int[] { -1, -1, -5, -1, -1 }; + } + + private static readonly int[][] conv; + private const int size = 5; + private const int radius = (size - 1) / 2; + + private UnaryPixelOps.Desaturate desaturateOp = new UnaryPixelOps.Desaturate(); + private GlowEffect glowEffect; + private PropertyCollection glowProps; + private UserBlendOps.DarkenBlendOp darkenOp = new UserBlendOps.DarkenBlendOp(); + + private int inkOutline; + private int coloring; + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.InkOutline, 50, 0, 99)); + props.Add(new Int32Property(PropertyNames.Coloring, 50, 0, 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.InkOutline, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("InkSketchEffect.ConfigDialog.InkOutlineLabel")); + configUI.SetPropertyControlValue(PropertyNames.Coloring, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("InkSketchEffect.ConfigDialog.ColoringLabel")); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.inkOutline = newToken.GetProperty(PropertyNames.InkOutline).Value; + this.coloring = newToken.GetProperty(PropertyNames.Coloring).Value; + + PropertyBasedEffectConfigToken glowToken = new PropertyBasedEffectConfigToken(this.glowProps); + glowToken.SetPropertyValue(GlowEffect.PropertyNames.Radius, 6); + glowToken.SetPropertyValue(GlowEffect.PropertyNames.Brightness, -(this.coloring - 50) * 2); + glowToken.SetPropertyValue(GlowEffect.PropertyNames.Contrast, -(this.coloring - 50) * 2); + this.glowEffect.SetRenderInfo(glowToken, dstArgs, srcArgs); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + // Glow backgound + this.glowEffect.Render(rois, startIndex, length); + + // Create black outlines by finding the edges of objects + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle roi = rois[i]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + int top = y - radius; + int bottom = y + radius + 1; + + if (top < 0) + { + top = 0; + } + + if (bottom > DstArgs.Height) + { + bottom = DstArgs.Height; + } + + ColorBgra* srcPtr = SrcArgs.Surface.GetPointAddress(roi.X, y); + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddress(roi.X, y); + + for (int x = roi.Left; x < roi.Right; ++x) + { + int left = x - radius; + int right = x + radius + 1; + + if (left < 0) + { + left = 0; + } + + if (right > DstArgs.Width) + { + right = DstArgs.Width; + } + + int r = 0; + int g = 0; + int b = 0; + + for (int v = top; v < bottom; v++) + { + ColorBgra* pRow = SrcArgs.Surface.GetRowAddress(v); + int j = v - y + radius; + + for (int u = left; u < right; u++) + { + int i1 = u - x + radius; + int w = conv[j][i1]; + + ColorBgra* pRef = pRow + u; + + r += pRef->R * w; + g += pRef->G * w; + b += pRef->B * w; + } + } + + ColorBgra topLayer = ColorBgra.FromBgr( + Utility.ClampToByte(b), + Utility.ClampToByte(g), + Utility.ClampToByte(r)); + + // Desaturate + topLayer = this.desaturateOp.Apply(topLayer); + + // Adjust Brightness and Contrast + if (topLayer.R > (this.inkOutline * 255 / 100)) + { + topLayer = ColorBgra.FromBgra(255, 255, 255, topLayer.A); + } + else + { + topLayer = ColorBgra.FromBgra(0, 0, 0, topLayer.A); + } + + // Change Blend Mode to Darken + ColorBgra myPixel = this.darkenOp.Apply(topLayer, *dstPtr); + *dstPtr = myPixel; + + ++srcPtr; + ++dstPtr; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Effects/InternalPropertyBasedEffect.cs b/src/Effects/InternalPropertyBasedEffect.cs new file mode 100644 index 0000000..6790e08 --- /dev/null +++ b/src/Effects/InternalPropertyBasedEffect.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public abstract class InternalPropertyBasedEffect + : PropertyBasedEffect + { + protected override void OnCustomizeConfigUIWindowProperties(PropertyCollection props) + { + props[ControlInfoPropertyNames.WindowIsSizable].Value = false; + base.OnCustomizeConfigUIWindowProperties(props); + } + + internal InternalPropertyBasedEffect(string name, Image image, string subMenuName, EffectFlags flags) + : base(name, image, subMenuName, flags) + { + } + } +} diff --git a/src/Effects/InvertColorsEffect.cs b/src/Effects/InvertColorsEffect.cs new file mode 100644 index 0000000..ccdc360 --- /dev/null +++ b/src/Effects/InvertColorsEffect.cs @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class InvertColorsEffect + : InternalPropertyBasedEffect + { + private UnaryPixelOps.Invert invertOp; + + protected override PropertyCollection OnCreatePropertyCollection() + { + return PropertyCollection.CreateEmpty(); + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + this.invertOp.Apply(DstArgs.Surface, SrcArgs.Surface, rois, startIndex, length); + } + + public InvertColorsEffect() + : base(PdnResources.GetString("InvertColorsEffect.Name"), + PdnResources.GetImageResource("Icons.InvertColorsEffect.png").Reference, + null, + EffectFlags.None) + { + this.invertOp = new UnaryPixelOps.Invert(); + } + } +} diff --git a/src/Effects/JuliaFractalEffect.cs b/src/Effects/JuliaFractalEffect.cs new file mode 100644 index 0000000..a755e89 --- /dev/null +++ b/src/Effects/JuliaFractalEffect.cs @@ -0,0 +1,183 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace PaintDotNet.Effects +{ + public sealed class JuliaFractalEffect + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + Factor = 0, + Zoom = 1, + Angle = 2, + Quality = 3 + } + + public static string StaticName + { + get + { + return PdnResources.GetString("JuliaFractalEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.JuliaFractalEffectIcon.png").Reference; + } + } + + private double factor; + private double zoom; + private double angle; + private double angleTheta; + private int quality; + + private static readonly double log2_10000 = Math.Log(10000); + + public JuliaFractalEffect() + : base(StaticName, + StaticImage, + SubmenuNames.Render, + EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Factor, 4.0, 1.0, 10.0)); + props.Add(new DoubleProperty(PropertyNames.Zoom, 1, 0.1, 50)); + props.Add(new DoubleProperty(PropertyNames.Angle, 0.0, -180.0, +180.0)); + props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + + return new PropertyCollection(props, new PropertyCollectionRule[0]); + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.zoom = newToken.GetProperty(PropertyNames.Zoom).Value; + this.quality = newToken.GetProperty(PropertyNames.Quality).Value; + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + this.angleTheta = (this.angle * Math.PI * 2) / 360.0; + this.factor = newToken.GetProperty(PropertyNames.Factor).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Factor, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("JuliaFractalEffect.ConfigDialog.Factor.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("JuliaFractalEffect.ConfigDialog.Quality.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Zoom, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("JuliaFractalEffect.ConfigDialog.Zoom.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("JuliaFractalEffect.ConfigDialog.Angle.DisplayName")); + configUI.SetPropertyControlType(PropertyNames.Angle, PropertyControlType.AngleChooser); + + return configUI; + } + + private static double Julia(double x, double y, double r, double i) + { + double c = 0; + + while (c < 256 && x * x + y * y < 10000) + { + double t = x; + x = x * x - y * y + r; + y = 2 * t * y + i; + ++c; + } + + c -= 2 - 2 * log2_10000 / Math.Log(x * x + y * y); + + return c; + } + + protected override unsafe void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + const double jr = 0.3125; + const double ji = 0.03; + + int w = DstArgs.Width; + int h = DstArgs.Height; + double invH = 1.0 / h; + double invZoom = 1.0 / this.zoom; + double invQuality = 1.0 / this.quality; + double aspect = (double)h / (double)w; + int count = this.quality * this.quality + 1; + double invCount = 1.0 / (double)count; + + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle rect = renderRects[ri]; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; x++) + { + int r = 0; + int g = 0; + int b = 0; + int a = 0; + + for (double i = 0; i < count; i++) + { + double u = (2.0 * x - w + (i * invCount)) * invH; + double v = (2.0 * y - h + ((i * invQuality) % 1)) * invH; + + double radius = Math.Sqrt((u * u) + (v * v)); + double radiusP = radius; + double theta = Math.Atan2(v, u); + double thetaP = theta + this.angleTheta; + + double uP = radiusP * Math.Cos(thetaP); + double vP = radiusP * Math.Sin(thetaP); + + double jX = (uP - vP * aspect) * invZoom; + double jY = (vP + uP * aspect) * invZoom; + + double j = Julia(jX, jY, jr, ji); + + double c = this.factor * j; + + b += Utility.ClampToByte(c - 768); + g += Utility.ClampToByte(c - 512); + r += Utility.ClampToByte(c - 256); + a += Utility.ClampToByte(c - 0); + } + + *dstPtr = ColorBgra.FromBgra( + Utility.ClampToByte(b / count), + Utility.ClampToByte(g / count), + Utility.ClampToByte(r / count), + Utility.ClampToByte(a / count)); + + ++dstPtr; + } + } + } + } + } +} diff --git a/src/Effects/LevelsEffect.cs b/src/Effects/LevelsEffect.cs new file mode 100644 index 0000000..8d78689 --- /dev/null +++ b/src/Effects/LevelsEffect.cs @@ -0,0 +1,38 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class LevelsEffect + : Effect + { + public override EffectConfigDialog CreateConfigDialog() + { + return new LevelsEffectConfigDialog(); + } + + public override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) + { + UnaryPixelOps.Level levels = (parameters as LevelsEffectConfigToken).Levels; + levels.Apply(dstArgs.Surface, srcArgs.Surface, rois, startIndex, length); + } + + public LevelsEffect() + : base(PdnResources.GetString("LevelsEffect.Name"), + PdnResources.GetImageResource("Icons.LevelsEffect.png").Reference, + EffectFlags.Configurable) + { + } + } +} diff --git a/src/Effects/LevelsEffectConfigDialog.cs b/src/Effects/LevelsEffectConfigDialog.cs new file mode 100644 index 0000000..c9e2bd6 --- /dev/null +++ b/src/Effects/LevelsEffectConfigDialog.cs @@ -0,0 +1,1042 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class LevelsEffectConfigDialog + : EffectConfigDialog + { + private bool[] mask = new bool[3]; + private uint ignore = 0; + private System.Windows.Forms.CheckBox redMaskCheckBox; + private System.Windows.Forms.CheckBox greenMaskCheckBox; + private System.Windows.Forms.CheckBox blueMaskCheckBox; + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button autoButton; + private System.Windows.Forms.Button resetButton; + private System.Windows.Forms.ToolTip tooltipProvider; + private TableLayoutPanel tableLayoutPanel2; + private ColorGradientControl gradientInput; + private NumericUpDown outputHiUpDown; + private Panel swatchOutHigh; + private NumericUpDown outputGammaUpDown; + private Panel swatchOutMid; + private Panel swatchOutLow; + private NumericUpDown outputLowUpDown; + private ColorGradientControl gradientOutput; + private HistogramControl histogramOutput; + private HistogramControl histogramInput; + private NumericUpDown inputHiUpDown; + private Panel swatchInHigh; + private NumericUpDown inputLoUpDown; + private Panel swatchInLow; + private HeaderLabel headerHistogramOutput; + private HeaderLabel headerControlsOutput; + private HeaderLabel headerControlsInput; + private HeaderLabel headerHistogramInput; + private TableLayoutPanel tableMain; + private System.ComponentModel.IContainer components; + + public LevelsEffectConfigDialog() + { + InitializeComponent(); + + this.Text = PdnResources.GetString("LevelsEffectConfigDialog.Text"); + this.headerControlsOutput.Text = PdnResources.GetString("LevelsEffectConfigDialog.OutputGroupBox.Text"); + this.tooltipProvider.SetToolTip(this.outputGammaUpDown, PdnResources.GetString("LevelsEffectConfigDialog.OutputGammaUpDown.ToolTipText")); + this.tooltipProvider.SetToolTip(this.swatchOutHigh, PdnResources.GetString("LevelsEffectConfigDialog.SwatchOutHigh.ToolTipText")); + this.tooltipProvider.SetToolTip(this.swatchOutLow, PdnResources.GetString("LevelsEffectConfigDialog.SwatchOutLow.ToolTipText")); + this.headerHistogramOutput.Text = PdnResources.GetString("LevelsEffectConfigDialog.OutputHistogramGroupBox.Text"); + this.tooltipProvider.SetToolTip(this.histogramOutput, PdnResources.GetString("LevelsEffectConfigDialog.HistogramOutput.ToolTipText")); + this.headerHistogramInput.Text = PdnResources.GetString("LevelsEffectConfigDialog.InputHistogramGroupBox.Text"); + this.tooltipProvider.SetToolTip(this.histogramInput, PdnResources.GetString("LevelsEffectConfigDialog.HistogramInput.ToolTipText")); + this.headerControlsInput.Text = PdnResources.GetString("LevelsEffectConfigDialog.InputGroupBox.Text"); + this.tooltipProvider.SetToolTip(this.swatchInHigh, PdnResources.GetString("LevelsEffectConfigDialog.SwatchInHigh.ToolTipText")); + this.tooltipProvider.SetToolTip(this.swatchInLow, PdnResources.GetString("LevelsEffectConfigDialog.SwatchInLow.ToolTipText")); + this.redMaskCheckBox.Text = PdnResources.GetString("LevelsEffectConfigDialog.RedMaskCheckBox.Text"); + this.tooltipProvider.SetToolTip(this.redMaskCheckBox, PdnResources.GetString("LevelsEffectConfigDialog.RedMaskCheckBox.ToolTipText")); + this.greenMaskCheckBox.Text = PdnResources.GetString("LevelsEffectConfigDialog.GreenMaskCheckBox.Text"); + this.tooltipProvider.SetToolTip(this.greenMaskCheckBox, PdnResources.GetString("LevelsEffectConfigDialog.GreenMaskCheckBox.ToolTipText")); + this.blueMaskCheckBox.Text = PdnResources.GetString("LevelsEffectConfigDialog.BlueMaskCheckBox.Text"); + this.tooltipProvider.SetToolTip(this.blueMaskCheckBox, PdnResources.GetString("LevelsEffectConfigDialog.BlueMaskCheckBox.ToolTipText")); + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.autoButton.Text = PdnResources.GetString("LevelsEffectConfigDialog.AutoButton.Text"); + this.tooltipProvider.SetToolTip(this.autoButton, PdnResources.GetString("LevelsEffectConfigDialog.AutoButton.ToolTipText")); + this.resetButton.Text = PdnResources.GetString("LevelsEffectConfigDialog.ResetButton.Text"); + } + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + PaintDotNet.HistogramRgb histogramRgb1 = new PaintDotNet.HistogramRgb(); + PaintDotNet.HistogramRgb histogramRgb2 = new PaintDotNet.HistogramRgb(); + this.redMaskCheckBox = new System.Windows.Forms.CheckBox(); + this.greenMaskCheckBox = new System.Windows.Forms.CheckBox(); + this.blueMaskCheckBox = new System.Windows.Forms.CheckBox(); + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.autoButton = new System.Windows.Forms.Button(); + this.resetButton = new System.Windows.Forms.Button(); + this.tooltipProvider = new System.Windows.Forms.ToolTip(this.components); + this.tableMain = new System.Windows.Forms.TableLayoutPanel(); + this.headerHistogramOutput = new PaintDotNet.HeaderLabel(); + this.headerControlsOutput = new PaintDotNet.HeaderLabel(); + this.headerControlsInput = new PaintDotNet.HeaderLabel(); + this.headerHistogramInput = new PaintDotNet.HeaderLabel(); + this.swatchInLow = new System.Windows.Forms.Panel(); + this.inputHiUpDown = new System.Windows.Forms.NumericUpDown(); + this.swatchInHigh = new System.Windows.Forms.Panel(); + this.inputLoUpDown = new System.Windows.Forms.NumericUpDown(); + this.swatchOutLow = new System.Windows.Forms.Panel(); + this.outputGammaUpDown = new System.Windows.Forms.NumericUpDown(); + this.swatchOutHigh = new System.Windows.Forms.Panel(); + this.outputHiUpDown = new System.Windows.Forms.NumericUpDown(); + this.gradientInput = new PaintDotNet.ColorGradientControl(); + this.swatchOutMid = new System.Windows.Forms.Panel(); + this.gradientOutput = new PaintDotNet.ColorGradientControl(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.histogramInput = new PaintDotNet.HistogramControl(); + this.histogramOutput = new PaintDotNet.HistogramControl(); + this.outputLowUpDown = new System.Windows.Forms.NumericUpDown(); + this.tableMain.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.inputHiUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.inputLoUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.outputGammaUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.outputHiUpDown)).BeginInit(); + this.tableLayoutPanel2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.outputLowUpDown)).BeginInit(); + this.SuspendLayout(); + // + // redMaskCheckBox + // + this.redMaskCheckBox.Checked = true; + this.redMaskCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.redMaskCheckBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.redMaskCheckBox.Location = new System.Drawing.Point(165, 3); + this.redMaskCheckBox.Name = "redMaskCheckBox"; + this.redMaskCheckBox.Size = new System.Drawing.Size(34, 23); + this.redMaskCheckBox.TabIndex = 8; + this.redMaskCheckBox.Click += new System.EventHandler(this.redMaskCheckBox_CheckedChanged); + this.redMaskCheckBox.CheckedChanged += new System.EventHandler(this.redMaskCheckBox_CheckedChanged); + this.redMaskCheckBox.FlatStyle = FlatStyle.System; + // + // greenMaskCheckBox + // + this.greenMaskCheckBox.Checked = true; + this.greenMaskCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.greenMaskCheckBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.greenMaskCheckBox.Location = new System.Drawing.Point(205, 3); + this.greenMaskCheckBox.Name = "greenMaskCheckBox"; + this.greenMaskCheckBox.Size = new System.Drawing.Size(34, 23); + this.greenMaskCheckBox.TabIndex = 9; + this.greenMaskCheckBox.Click += new System.EventHandler(this.greenMaskCheckBox_CheckedChanged); + this.greenMaskCheckBox.CheckedChanged += new System.EventHandler(this.greenMaskCheckBox_CheckedChanged); + this.greenMaskCheckBox.FlatStyle = FlatStyle.System; + // + // blueMaskCheckBox + // + this.blueMaskCheckBox.Checked = true; + this.blueMaskCheckBox.CheckState = System.Windows.Forms.CheckState.Checked; + this.blueMaskCheckBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.blueMaskCheckBox.Location = new System.Drawing.Point(245, 3); + this.blueMaskCheckBox.Name = "blueMaskCheckBox"; + this.blueMaskCheckBox.Size = new System.Drawing.Size(34, 23); + this.blueMaskCheckBox.TabIndex = 10; + this.blueMaskCheckBox.Click += new System.EventHandler(this.blueMaskCheckBox_CheckedChanged); + this.blueMaskCheckBox.CheckedChanged += new System.EventHandler(this.blueMaskCheckBox_CheckedChanged); + this.blueMaskCheckBox.FlatStyle = FlatStyle.System; + // + // okButton + // + this.okButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(285, 3); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 11; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // cancelButton + // + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(366, 3); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(77, 23); + this.cancelButton.TabIndex = 12; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // autoButton + // + this.autoButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.autoButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.autoButton.Location = new System.Drawing.Point(3, 3); + this.autoButton.Name = "autoButton"; + this.autoButton.Size = new System.Drawing.Size(75, 23); + this.autoButton.TabIndex = 6; + this.autoButton.Click += new System.EventHandler(this.autoButton_Click); + // + // resetButton + // + this.resetButton.Dock = System.Windows.Forms.DockStyle.Fill; + this.resetButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.resetButton.Location = new System.Drawing.Point(84, 3); + this.resetButton.Name = "resetButton"; + this.resetButton.Size = new System.Drawing.Size(75, 23); + this.resetButton.TabIndex = 7; + this.resetButton.Click += new System.EventHandler(this.resetButton_Click); + // + // tableMain + // + this.tableMain.ColumnCount = 6; + this.tableMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F)); + this.tableMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50F)); + this.tableMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableMain.Controls.Add(this.headerHistogramOutput, 5, 0); + this.tableMain.Controls.Add(this.headerControlsOutput, 3, 0); + this.tableMain.Controls.Add(this.headerControlsInput, 1, 0); + this.tableMain.Controls.Add(this.headerHistogramInput, 0, 0); + this.tableMain.Controls.Add(this.swatchInLow, 1, 7); + this.tableMain.Controls.Add(this.inputHiUpDown, 1, 1); + this.tableMain.Controls.Add(this.swatchInHigh, 1, 2); + this.tableMain.Controls.Add(this.inputLoUpDown, 1, 8); + this.tableMain.Controls.Add(this.swatchOutLow, 4, 7); + this.tableMain.Controls.Add(this.outputGammaUpDown, 4, 4); + this.tableMain.Controls.Add(this.swatchOutHigh, 4, 2); + this.tableMain.Controls.Add(this.outputHiUpDown, 4, 1); + this.tableMain.Controls.Add(this.gradientInput, 2, 1); + this.tableMain.Controls.Add(this.swatchOutMid, 4, 5); + this.tableMain.Controls.Add(this.gradientOutput, 3, 1); + this.tableMain.Controls.Add(this.tableLayoutPanel2, 0, 9); + this.tableMain.Controls.Add(this.histogramInput, 0, 1); + this.tableMain.Controls.Add(this.histogramOutput, 5, 1); + this.tableMain.Controls.Add(this.outputLowUpDown, 4, 8); + this.tableMain.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableMain.Location = new System.Drawing.Point(0, 0); + this.tableMain.Name = "tableMain"; + this.tableMain.RowCount = 10; + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 35F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableMain.Size = new System.Drawing.Size(452, 211); + this.tableMain.TabStop = false; + // + // headerHistogramOutput + // + this.headerHistogramOutput.Dock = System.Windows.Forms.DockStyle.Fill; + this.headerHistogramOutput.Location = new System.Drawing.Point(319, 3); + this.headerHistogramOutput.Name = "headerHistogramOutput"; + this.headerHistogramOutput.RightMargin = 3; + this.headerHistogramOutput.Size = new System.Drawing.Size(130, 14); + this.headerHistogramOutput.TabStop = false; + // + // headerControlsOutput + // + this.tableMain.SetColumnSpan(this.headerControlsOutput, 2); + this.headerControlsOutput.Dock = System.Windows.Forms.DockStyle.Fill; + this.headerControlsOutput.Location = new System.Drawing.Point(229, 3); + this.headerControlsOutput.Name = "headerControlsOutput"; + this.headerControlsOutput.RightMargin = 3; + this.headerControlsOutput.Size = new System.Drawing.Size(84, 14); + this.headerControlsOutput.TabStop = false; + // + // headerControlsInput + // + this.tableMain.SetColumnSpan(this.headerControlsInput, 2); + this.headerControlsInput.Dock = System.Windows.Forms.DockStyle.Fill; + this.headerControlsInput.Location = new System.Drawing.Point(139, 3); + this.headerControlsInput.Name = "headerControlsInput"; + this.headerControlsInput.RightMargin = 3; + this.headerControlsInput.Size = new System.Drawing.Size(84, 14); + this.headerControlsInput.TabStop = false; + // + // headerHistogramInput + // + this.headerHistogramInput.Dock = System.Windows.Forms.DockStyle.Fill; + this.headerHistogramInput.Location = new System.Drawing.Point(3, 3); + this.headerHistogramInput.Name = "headerHistogramInput"; + this.headerHistogramInput.RightMargin = 3; + this.headerHistogramInput.Size = new System.Drawing.Size(130, 14); + this.headerHistogramInput.TabStop = false; + // + // swatchInLow + // + this.swatchInLow.BackColor = System.Drawing.Color.Black; + this.swatchInLow.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.swatchInLow.Dock = System.Windows.Forms.DockStyle.Fill; + this.swatchInLow.Location = new System.Drawing.Point(139, 127); + this.swatchInLow.Name = "swatchInLow"; + this.swatchInLow.Size = new System.Drawing.Size(44, 20); + this.swatchInLow.TabStop = false; + this.swatchInLow.DoubleClick += swatch_DoubleClick; + // + // inputHiUpDown + // + this.inputHiUpDown.Dock = System.Windows.Forms.DockStyle.Fill; + this.inputHiUpDown.Location = new System.Drawing.Point(139, 23); + this.inputHiUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.inputHiUpDown.Name = "inputHiUpDown"; + this.inputHiUpDown.Size = new System.Drawing.Size(44, 20); + this.inputHiUpDown.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.inputHiUpDown.ValueChanged += new System.EventHandler(this.txtInputHi_ValueChanged); + this.inputHiUpDown.Validating += new System.ComponentModel.CancelEventHandler(this.txtInputHi_Validating); + this.inputHiUpDown.TabIndex = 1; + // + // swatchInHigh + // + this.swatchInHigh.BackColor = System.Drawing.Color.White; + this.swatchInHigh.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.swatchInHigh.Dock = System.Windows.Forms.DockStyle.Fill; + this.swatchInHigh.Location = new System.Drawing.Point(139, 49); + this.swatchInHigh.Name = "swatchInHigh"; + this.swatchInHigh.Size = new System.Drawing.Size(44, 20); + this.swatchInHigh.TabStop = false; + this.swatchInHigh.DoubleClick += swatch_DoubleClick; + // + // inputLoUpDown + // + this.inputLoUpDown.Dock = System.Windows.Forms.DockStyle.Fill; + this.inputLoUpDown.Location = new System.Drawing.Point(139, 153); + this.inputLoUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.inputLoUpDown.Name = "inputLoUpDown"; + this.inputLoUpDown.Size = new System.Drawing.Size(44, 20); + this.inputLoUpDown.TabIndex = 4; + this.inputLoUpDown.ValueChanged += new System.EventHandler(this.txtInputLo_ValueChanged); + this.inputLoUpDown.Validating += new System.ComponentModel.CancelEventHandler(this.txtInputLo_Validating); + // + // swatchOutLow + // + this.swatchOutLow.BackColor = System.Drawing.Color.Black; + this.swatchOutLow.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.swatchOutLow.Dock = System.Windows.Forms.DockStyle.Fill; + this.swatchOutLow.Location = new System.Drawing.Point(269, 127); + this.swatchOutLow.Name = "swatchOutLow"; + this.swatchOutLow.Size = new System.Drawing.Size(44, 20); + this.swatchOutLow.TabStop = false; + this.swatchOutLow.DoubleClick += swatch_DoubleClick; + // + // outputGammaUpDown + // + this.outputGammaUpDown.DecimalPlaces = 2; + this.outputGammaUpDown.Dock = System.Windows.Forms.DockStyle.Fill; + this.outputGammaUpDown.Increment = new decimal(new int[] { + 1, + 0, + 0, + 65536}); + this.outputGammaUpDown.Location = new System.Drawing.Point(269, 75); + this.outputGammaUpDown.Maximum = new decimal(new int[] { + 100, + 0, + 0, + 65536}); + this.outputGammaUpDown.Minimum = new decimal(new int[] { + 1, + 0, + 0, + 65536}); + this.outputGammaUpDown.Name = "outputGammaUpDown"; + this.outputGammaUpDown.Size = new System.Drawing.Size(44, 20); + this.outputGammaUpDown.TabIndex = 3; + this.outputGammaUpDown.Value = new decimal(new int[] { + 1, + 0, + 0, + 0}); + this.outputGammaUpDown.ValueChanged += new EventHandler(outputGammaUpDown_ValueChanged); + this.outputGammaUpDown.Validating += new System.ComponentModel.CancelEventHandler(this.outputGammaUpDown_Validating); + // + // swatchOutHigh + // + this.swatchOutHigh.BackColor = System.Drawing.Color.White; + this.swatchOutHigh.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.swatchOutHigh.Dock = System.Windows.Forms.DockStyle.Fill; + this.swatchOutHigh.Location = new System.Drawing.Point(269, 49); + this.swatchOutHigh.Name = "swatchOutHigh"; + this.swatchOutHigh.Size = new System.Drawing.Size(44, 20); + this.swatchOutHigh.TabStop = false; + this.swatchOutHigh.DoubleClick += swatch_DoubleClick; + // + // outputHiUpDown + // + this.outputHiUpDown.Dock = System.Windows.Forms.DockStyle.Fill; + this.outputHiUpDown.Location = new System.Drawing.Point(269, 23); + this.outputHiUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.outputHiUpDown.Name = "outputHiUpDown"; + this.outputHiUpDown.Size = new System.Drawing.Size(44, 20); + this.outputHiUpDown.TabIndex = 2; + this.outputHiUpDown.Value = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.outputHiUpDown.Validating += new System.ComponentModel.CancelEventHandler(this.outputHiUpDown_Validating); + this.outputHiUpDown.ValueChanged += new EventHandler(outputHiUpDown_ValueChanged); + // + // gradientInput + // + this.gradientInput.MinColor = System.Drawing.Color.Black; + this.gradientInput.Count = 2; + this.gradientInput.Dock = System.Windows.Forms.DockStyle.Fill; + this.gradientInput.Location = new System.Drawing.Point(189, 23); + this.gradientInput.Name = "gradientInput"; + this.tableMain.SetRowSpan(this.gradientInput, 8); + this.gradientInput.Size = new System.Drawing.Size(34, 150); + this.gradientInput.MaxColor = System.Drawing.Color.White; + this.gradientInput.Value = 0; + this.gradientInput.ValueChanged += new PaintDotNet.IndexEventHandler(this.gradientInput_ValueChanged); + this.gradientInput.TabStop = false; + // + // swatchOutMid + // + this.swatchOutMid.BackColor = System.Drawing.Color.White; + this.swatchOutMid.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.swatchOutMid.Dock = System.Windows.Forms.DockStyle.Fill; + this.swatchOutMid.Location = new System.Drawing.Point(269, 101); + this.swatchOutMid.Name = "swatchOutMid"; + this.swatchOutMid.Size = new System.Drawing.Size(44, 20); + this.swatchOutMid.TabStop = false; + // + // gradientOutput + // + this.gradientOutput.MinColor = System.Drawing.Color.Black; + this.gradientOutput.Count = 3; + this.gradientOutput.Dock = System.Windows.Forms.DockStyle.Fill; + this.gradientOutput.Location = new System.Drawing.Point(229, 23); + this.gradientOutput.Name = "gradientOutput"; + this.tableMain.SetRowSpan(this.gradientOutput, 8); + this.gradientOutput.Size = new System.Drawing.Size(34, 150); + this.gradientOutput.MaxColor = System.Drawing.Color.White; + this.gradientOutput.Value = 0; + this.gradientOutput.ValueChanged += new PaintDotNet.IndexEventHandler(this.gradientOutput_ValueChanged); + this.gradientOutput.TabStop = false; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.ColumnCount = 9; + this.tableMain.SetColumnSpan(this.tableLayoutPanel2, 6); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 81F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 81F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 40F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 81F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 83F)); + this.tableLayoutPanel2.Controls.Add(this.blueMaskCheckBox, 5, 0); + this.tableLayoutPanel2.Controls.Add(this.greenMaskCheckBox, 4, 0); + this.tableLayoutPanel2.Controls.Add(this.redMaskCheckBox, 3, 0); + this.tableLayoutPanel2.Controls.Add(this.autoButton, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.resetButton, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.okButton, 7, 0); + this.tableLayoutPanel2.Controls.Add(this.cancelButton, 8, 0); + this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 179); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 1; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(446, 29); + // + // histogramInput + // + this.histogramInput.Dock = System.Windows.Forms.DockStyle.Fill; + this.histogramInput.FlipHorizontal = true; + this.histogramInput.FlipVertical = false; + this.histogramInput.Histogram = histogramRgb1; + this.histogramInput.Location = new System.Drawing.Point(3, 23); + this.histogramInput.Name = "histogramInput"; + this.tableMain.SetRowSpan(this.histogramInput, 8); + this.histogramInput.Size = new System.Drawing.Size(130, 150); + this.histogramInput.TabStop = false; + // + // histogramOutput + // + this.histogramOutput.Dock = System.Windows.Forms.DockStyle.Fill; + this.histogramOutput.FlipHorizontal = false; + this.histogramOutput.FlipVertical = false; + this.histogramOutput.Histogram = histogramRgb2; + this.histogramOutput.Location = new System.Drawing.Point(319, 23); + this.histogramOutput.Name = "histogramOutput"; + this.tableMain.SetRowSpan(this.histogramOutput, 8); + this.histogramOutput.Size = new System.Drawing.Size(130, 150); + this.histogramOutput.TabStop = false; + // + // outputLowUpDown + // + this.outputLowUpDown.Dock = System.Windows.Forms.DockStyle.Fill; + this.outputLowUpDown.Location = new System.Drawing.Point(269, 153); + this.outputLowUpDown.Maximum = new decimal(new int[] { + 255, + 0, + 0, + 0}); + this.outputLowUpDown.Name = "outputLowUpDown"; + this.outputLowUpDown.Size = new System.Drawing.Size(44, 20); + this.outputLowUpDown.TabIndex = 5; + this.outputLowUpDown.ValueChanged += new System.EventHandler(this.outputLowUpDown_ValueChanged); + this.outputLowUpDown.Validating += new System.ComponentModel.CancelEventHandler(this.outputLowUpDown_Validating); + // + // LevelsEffectConfigDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(452, 211); + this.Controls.Add(this.tableMain); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; + this.MaximizeBox = true; + this.MinimumSize = new System.Drawing.Size(460, 245); + this.Name = "LevelsEffectConfigDialog"; + this.Load += new System.EventHandler(this.LevelsEffectConfigDialog_Load); + this.Controls.SetChildIndex(this.tableMain, 0); + this.tableMain.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.inputHiUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.inputLoUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.outputGammaUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.outputHiUpDown)).EndInit(); + this.tableLayoutPanel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.outputLowUpDown)).EndInit(); + this.ResumeLayout(false); + + } + + private void SetEnabledControls(bool enabled) + { + this.inputHiUpDown.Enabled = enabled; + this.outputHiUpDown.Enabled = enabled; + this.inputLoUpDown.Enabled = enabled; + this.outputLowUpDown.Enabled = enabled; + this.outputGammaUpDown.Enabled = enabled; + this.gradientInput.Enabled = enabled; + this.gradientOutput.Enabled = enabled; + } + + private void MaskChanged() + { + bool anyOn = mask[0] || mask[1] || mask[2]; + + SetEnabledControls(anyOn); + + ColorBgra top = ColorBgra.Black; + + top.Bgra |= mask[0] ? (uint)0xFF : 0; + top.Bgra |= mask[1] ? (uint)0xFF00 : 0; + top.Bgra |= mask[2] ? (uint)0xFF0000 : 0; + + gradientInput.MaxColor = top.ToColor(); + gradientOutput.MaxColor = top.ToColor(); + + for (int i = 0; i < 3; ++i) + { + histogramInput.SetSelected(i, mask[i]); + histogramOutput.SetSelected(i, mask[i]); + } + + ignore++; + InitDialogFromToken(); + ignore--; + } + + private int MaskAvg(ColorBgra before) + { + int count = 0, total = 0; + + for (int c = 0; c < 3; c++) + { + if (mask[c]) + { + total += before[c]; + count++; + } + } + + if (count > 0) + { + return total / count; + } + else + { + return 0; + } + } + + private ColorBgra UpdateByMask(ColorBgra before, byte val) + { + ColorBgra after = before; + int average = -1, oldaverage = -1; + + if (!(mask[0] || mask[1] || mask[2])) + { + return before; + } + + do + { + float factor; + + oldaverage = average; + average = MaskAvg(after); + + if (average == 0) + { + break; + } + factor = (float)val / average; + + for (int c = 0; c < 3; c++) + { + if (mask[c]) + { + after[c] = (byte)Utility.ClampToByte(after[c] * factor); + } + } + } while (average != val && oldaverage != average); + + while (average != val) + { + average = MaskAvg(after); + int diff = val - average; + + for (int c = 0; c < 3; c++) + { + if (mask[c]) + { + after[c] = (byte)Utility.ClampToByte(after[c] + diff); + } + } + } + + after.A = 255; + return after; + } + + private void LevelsEffectConfigDialog_Load(object sender, System.EventArgs e) + { + histogramInput.Histogram.UpdateHistogram(this.EffectSourceSurface, this.Selection); + mask[0] = true; + mask[1] = true; + mask[2] = true; + MaskChanged(); + UpdateOutputHistogram(); + } + + private void UpdateOutputHistogram() + { + ((HistogramRgb)this.histogramOutput.Histogram).SetFromLeveledHistogram((HistogramRgb)this.histogramInput.Histogram, ((LevelsEffectConfigToken)this.theEffectToken).Levels); + this.histogramOutput.Update(); + } + + protected override void InitialInitToken() + { + theEffectToken = new LevelsEffectConfigToken(); + } + + private void UpdateGammaByMask(UnaryPixelOps.Level levels, float val) + { + float average = -1; + + if (!(mask[0] || mask[1] || mask[2])) + { + return; + } + + do + { + average = MaskGamma(levels); + float factor = val / average; + + for (int c = 0; c < 3; c++) + { + if (mask[c]) + { + levels.SetGamma(c, factor * levels.GetGamma(c)); + } + } + } while (Math.Abs(val - average) > 0.001); + } + + protected override void InitTokenFromDialog() + { + UnaryPixelOps.Level levels = ((LevelsEffectConfigToken)theEffectToken).Levels; + + levels.ColorOutHigh = UpdateByMask(levels.ColorOutHigh, (byte)outputHiUpDown.Value); + levels.ColorOutLow = UpdateByMask(levels.ColorOutLow, (byte)outputLowUpDown.Value); + + levels.ColorInHigh = UpdateByMask(levels.ColorInHigh, (byte)inputHiUpDown.Value); + levels.ColorInLow = UpdateByMask(levels.ColorInLow, (byte)inputLoUpDown.Value); + + UpdateGammaByMask(levels, (float)outputGammaUpDown.Value); + + swatchInHigh.BackColor = levels.ColorInHigh.ToColor(); + swatchInHigh.Invalidate(); + + swatchInLow.BackColor = levels.ColorInLow.ToColor(); + swatchInLow.Invalidate(); + + swatchOutHigh.BackColor = levels.ColorOutHigh.ToColor(); + swatchOutHigh.Invalidate(); + + swatchOutMid.BackColor = levels.Apply(((HistogramRgb)histogramInput.Histogram).GetMeanColor()).ToColor(); + swatchOutMid.Invalidate(); + + swatchOutLow.BackColor = levels.ColorOutLow.ToColor(); + swatchOutLow.Invalidate(); + } + + private float MaskGamma(UnaryPixelOps.Level levels) + { + int count = 0; + float total = 0; + + for (int c = 0; c < 3; c++) + { + if (mask[c]) + { + total += levels.GetGamma(c); + count++; + } + } + + if (count > 0) + { + return total / count; + } + else + { + return 1; + } + + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + UnaryPixelOps.Level levels = ((LevelsEffectConfigToken)effectToken).Levels; + + float gamma = MaskGamma(levels); + int lo = MaskAvg(levels.ColorOutLow); + int hi = MaskAvg(levels.ColorOutHigh); + int md = (int)(lo + (hi - lo) * Math.Pow(0.5, gamma)); + + outputHiUpDown.Value = hi; + outputGammaUpDown.Value = (decimal)gamma; + outputLowUpDown.Value = lo; + inputHiUpDown.Value = MaskAvg(levels.ColorInHigh); + inputLoUpDown.Value = MaskAvg(levels.ColorInLow); + + gradientOutput.SetValue(0, lo); + gradientOutput.SetValue(1, md); + gradientOutput.SetValue(2, hi); + + swatchInHigh.BackColor = levels.ColorInHigh.ToColor(); + swatchInLow.BackColor = levels.ColorInLow.ToColor(); + swatchOutMid.BackColor = levels.Apply(((HistogramRgb)histogramInput.Histogram).GetMeanColor()).ToColor(); + swatchOutMid.Invalidate(); + swatchOutHigh.BackColor = levels.ColorOutHigh.ToColor(); + swatchOutLow.BackColor = levels.ColorOutLow.ToColor(); + } + + private void UpdateLevels() + { + FinishTokenUpdate(); + UpdateOutputHistogram(); + } + + private void gradientOutput_ValueChanged(object sender, IndexEventArgs e) + { + if (ignore == 0) + { + int lo = gradientOutput.GetValue(0), md, hi = gradientOutput.GetValue(2); + md = (int)(lo + (hi - lo) * Math.Pow(0.5, (double)outputGammaUpDown.Value)); + ignore++; + + switch (e.Index) + { + case 0: + outputLowUpDown.Text = lo.ToString(); + break; + + case 1: + md = gradientOutput.GetValue(1); + outputGammaUpDown.Value = (decimal)Utility.Clamp(1 / Math.Log(0.5, (float)(md - lo) / (float)(hi - lo)), 0.1, 10.0); + break; + + case 2: + outputHiUpDown.Text = hi.ToString(); + break; + } + + gradientOutput.SetValue(1, md); + UpdateLevels(); + ignore--; + } + } + + private void outputHiUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (ignore == 0) + { + ignore++; + gradientOutput.SetValue(2, (int)outputHiUpDown.Value); + UpdateLevels(); + ignore--; + } + } + + private void outputGammaUpDown_ValueChanged(object sender, System.EventArgs e) + { + int lo = gradientOutput.GetValue(0); + int hi = gradientOutput.GetValue(2); + int md = (int)(lo + (hi - lo) * Math.Pow(0.5, (double)outputGammaUpDown.Value)); + + gradientOutput.SetValue(1, md); + + if (ignore == 0) + { + ignore++; + UpdateLevels(); + ignore--; + } + } + + private void outputLowUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (ignore == 0) + { + ignore++; + gradientOutput.SetValue(0, (int)outputLowUpDown.Value); + UpdateLevels(); + ignore--; + } + } + + private void gradientInput_ValueChanged(object sender, IndexEventArgs e) + { + if (ignore == 0) + { + int lo = gradientInput.GetValue(0), hi = gradientInput.GetValue(1); + ignore++; + + switch (e.Index) + { + case 0: + inputLoUpDown.Text = lo.ToString(); + break; + + case 1: + inputHiUpDown.Text = hi.ToString(); + break; + } + + UpdateLevels(); + ignore--; + } + } + + private void txtInputHi_ValueChanged(object sender, System.EventArgs e) + { + gradientInput.SetValue(1, (int)inputHiUpDown.Value); + + if (ignore == 0) + { + ignore++; + UpdateLevels(); + ignore--; + } + } + + private void txtInputLo_ValueChanged(object sender, System.EventArgs e) + { + gradientInput.SetValue(0, (int)inputLoUpDown.Value); + + if (ignore == 0) + { + ignore++; + UpdateLevels(); + ignore--; + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout (levent); + } + + private void okButton_Click(object sender, System.EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + } + + private void cancelButton_Click(object sender, System.EventArgs e) + { + this.Close(); + } + + private void resetButton_Click(object sender, System.EventArgs e) + { + ((LevelsEffectConfigToken)this.EffectToken).Levels = new UnaryPixelOps.Level(); + ignore++; + InitDialogFromToken(); + ignore--; + UpdateLevels(); + } + + private void autoButton_Click(object sender, System.EventArgs e) + { + ((LevelsEffectConfigToken)this.EffectToken).Levels = ((HistogramRgb)histogramInput.Histogram).MakeLevelsAuto(); + + ignore++; + InitDialogFromToken(); + ignore--; + UpdateLevels(); + } + + private void swatch_DoubleClick(object sender, System.EventArgs e) + { + SystemLayer.Tracing.Ping((sender as Control).Name); + + UnaryPixelOps.Level levels = ((LevelsEffectConfigToken)theEffectToken).Levels; + + using (ColorDialog cd = new ColorDialog()) + { + if ((sender is Panel)) + { + cd.Color = ((Panel)sender).BackColor; + cd.AnyColor = true; + + if (cd.ShowDialog(this) == DialogResult.OK) + { + ColorBgra col = ColorBgra.FromColor(cd.Color); + + if (sender == swatchInLow) + { + levels.ColorInLow = col; + } + else if (sender == swatchInHigh) + { + levels.ColorInHigh = col; + } + else if (sender == swatchOutLow) + { + levels.ColorOutLow = col; + } + else if (sender == swatchOutMid) + { + ColorBgra lo = levels.ColorInLow; + ColorBgra md = ((HistogramRgb)histogramInput.Histogram).GetMeanColor(); + ColorBgra hi = levels.ColorInHigh; + ColorBgra out_lo = levels.ColorOutLow; + ColorBgra out_hi = levels.ColorOutHigh; + + for (int i = 0; i < 3; i++) + { + double logA = (col[i] - out_lo[i]) / (out_hi[i] - out_lo[i]); + double logBase = (md[i] - lo[i]) / (hi[i] - lo[i]); + double logVal = (logBase == 1.0) ? 0.0 : Math.Log(logA, logBase); + + levels.SetGamma(i, (float)Utility.Clamp(logVal, 0.1, 10.0)); + } + } + else if (sender == swatchOutHigh) + { + levels.ColorOutHigh = col; + } + else if (sender == swatchInHigh) + { + levels.ColorInHigh = col; + } + + InitDialogFromToken(); + } + } + } + } + + private void blueMaskCheckBox_CheckedChanged(object sender, System.EventArgs e) + { + mask[0] = blueMaskCheckBox.Checked; + MaskChanged(); + } + + private void greenMaskCheckBox_CheckedChanged(object sender, System.EventArgs e) + { + mask[1] = greenMaskCheckBox.Checked; + MaskChanged(); + } + + private void redMaskCheckBox_CheckedChanged(object sender, System.EventArgs e) + { + mask[2] = redMaskCheckBox.Checked; + MaskChanged(); + } + + private void txtInputHi_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + txtInputHi_ValueChanged(sender, EventArgs.Empty); + } + + private void outputHiUpDown_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + outputHiUpDown_ValueChanged(sender, EventArgs.Empty); + } + + private void txtInputLo_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + txtInputLo_ValueChanged(sender, EventArgs.Empty); + } + + private void outputLowUpDown_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + outputLowUpDown_ValueChanged(sender, EventArgs.Empty); + } + + private void outputGammaUpDown_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + txtInputHi_ValueChanged(sender, EventArgs.Empty); + } + } +} \ No newline at end of file diff --git a/src/Effects/LevelsEffectConfigToken.cs b/src/Effects/LevelsEffectConfigToken.cs new file mode 100644 index 0000000..1a095d0 --- /dev/null +++ b/src/Effects/LevelsEffectConfigToken.cs @@ -0,0 +1,44 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + public class LevelsEffectConfigToken + : EffectConfigToken + { + private UnaryPixelOps.Level levels = null; + + public UnaryPixelOps.Level Levels + { + get + { + return levels; + } + + set + { + levels = value; + } + } + + public LevelsEffectConfigToken() + { + levels = new UnaryPixelOps.Level(); + } + + public override object Clone() + { + LevelsEffectConfigToken cpy = new LevelsEffectConfigToken(); + cpy.levels = (UnaryPixelOps.Level)this.levels.Clone(); + return cpy; + } + } +} diff --git a/src/Effects/LocalHistogramEffect.cs b/src/Effects/LocalHistogramEffect.cs new file mode 100644 index 0000000..da30cae --- /dev/null +++ b/src/Effects/LocalHistogramEffect.cs @@ -0,0 +1,539 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Effects; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.Effects +{ + public abstract class LocalHistogramEffect + : InternalPropertyBasedEffect + { + internal LocalHistogramEffect(string name, Image image, string subMenuName, EffectFlags flags) + : base(name, image, subMenuName, flags) + { + } + + protected static int GetMaxAreaForRadius(int radius) + { + int area = 0; + int cutoff = ((radius * 2 + 1) * (radius * 2 + 1) + 2) / 4; + + for (int v = -radius; v <= radius; ++v) + { + for (int u = -radius; u <= radius; ++u) + { + if (u * u + v * v <= cutoff) + { + ++area; + } + } + } + + return area; + } + + public virtual unsafe ColorBgra Apply(ColorBgra src, int area, int* hb, int* hg, int* hr, int* ha) + { + return src; + } + + //same as Aply, except the histogram is alpha-weighted instead of keeping a separate alpha channel histogram. + public virtual unsafe ColorBgra ApplyWithAlpha(ColorBgra src, int area, int sum, int* hb, int* hg, int* hr) + { + return src; + } + + public static unsafe ColorBgra GetPercentile(int percentile, int area, int* hb, int* hg, int* hr, int* ha) + { + int minCount = area * percentile / 100; + + int b = 0; + int bCount = 0; + + while (b < 255 && hb[b] == 0) + { + ++b; + } + + while (b < 255 && bCount < minCount) + { + bCount += hb[b]; + ++b; + } + + int g = 0; + int gCount = 0; + + while (g < 255 && hg[g] == 0) + { + ++g; + } + + while (g < 255 && gCount < minCount) + { + gCount += hg[g]; + ++g; + } + + int r = 0; + int rCount = 0; + + while (r < 255 && hr[r] == 0) + { + ++r; + } + + while (r < 255 && rCount < minCount) + { + rCount += hr[r]; + ++r; + } + + int a = 0; + int aCount = 0; + + while (a < 255 && ha[a] == 0) + { + ++a; + } + + while (a < 255 && aCount < minCount) + { + aCount += ha[a]; + ++a; + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a); + } + + public unsafe void RenderRect( + int rad, + Surface src, + Surface dst, + Rectangle rect) + { + int width = src.Width; + int height = src.Height; + + int* leadingEdgeX = stackalloc int[rad + 1]; + int stride = src.Stride / sizeof(ColorBgra); + + // approximately (rad + 0.5)^2 + int cutoff = ((rad * 2 + 1) * (rad * 2 + 1) + 2) / 4; + + for (int v = 0; v <= rad; ++v) + { + for (int u = 0; u <= rad; ++u) + { + if (u * u + v * v <= cutoff) + { + leadingEdgeX[v] = u; + } + } + } + + const int hLength = 256; + int* hb = stackalloc int[hLength]; + int* hg = stackalloc int[hLength]; + int* hr = stackalloc int[hLength]; + int* ha = stackalloc int[hLength]; + uint hSize = (uint)(sizeof(int) * hLength); + + for (int y = rect.Top; y < rect.Bottom; y++) + { + Memory.SetToZero(hb, hSize); + Memory.SetToZero(hg, hSize); + Memory.SetToZero(hr, hSize); + Memory.SetToZero(ha, hSize); + + int area = 0; + + ColorBgra* ps = src.GetPointAddressUnchecked(rect.Left, y); + ColorBgra* pd = dst.GetPointAddressUnchecked(rect.Left, y); + + // assert: v + y >= 0 + int top = -Math.Min(rad, y); + + // assert: v + y <= height - 1 + int bottom = Math.Min(rad, height - 1 - y); + + // assert: u + x >= 0 + int left = -Math.Min(rad, rect.Left); + + // assert: u + x <= width - 1 + int right = Math.Min(rad, width - 1 - rect.Left); + + for (int v = top; v <= bottom; ++v) + { + ColorBgra* psamp = src.GetPointAddressUnchecked(rect.Left + left, y + v); + + for (int u = left; u <= right; ++u) + { + if ((u * u + v * v) <= cutoff) + { + ++area; + ++hb[psamp->B]; + ++hg[psamp->G]; + ++hr[psamp->R]; + ++ha[psamp->A]; + } + + ++psamp; + } + } + + for (int x = rect.Left; x < rect.Right; x++) + { + *pd = Apply(*ps, area, hb, hg, hr, ha); + + // assert: u + x >= 0 + left = -Math.Min(rad, x); + + // assert: u + x <= width - 1 + right = Math.Min(rad + 1, width - 1 - x); + + // Subtract trailing edge top half + int v = -1; + + while (v >= top) + { + int u = leadingEdgeX[-v]; + + if (-u >= left) + { + break; + } + + --v; + } + + while (v >= top) + { + int u = leadingEdgeX[-v]; + ColorBgra* p = unchecked(ps + (v * stride)) - u; + + --hb[p->B]; + --hg[p->G]; + --hr[p->R]; + --ha[p->A]; + --area; + + --v; + } + + // add leading edge top half + v = -1; + while (v >= top) + { + int u = leadingEdgeX[-v]; + + if (u + 1 <= right) + { + break; + } + + --v; + } + + while (v >= top) + { + int u = leadingEdgeX[-v]; + ColorBgra* p = unchecked(ps + (v * stride)) + u + 1; + + ++hb[p->B]; + ++hg[p->G]; + ++hr[p->R]; + ++ha[p->A]; + ++area; + + --v; + } + + // Subtract trailing edge bottom half + v = 0; + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + + if (-u >= left) + { + break; + } + + ++v; + } + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + ColorBgra* p = ps + v * stride - u; + + --hb[p->B]; + --hg[p->G]; + --hr[p->R]; + --ha[p->A]; + --area; + + ++v; + } + + // add leading edge bottom half + v = 0; + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + + if (u + 1 <= right) + { + break; + } + + ++v; + } + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + ColorBgra* p = ps + v * stride + u + 1; + + ++hb[p->B]; + ++hg[p->G]; + ++hr[p->R]; + ++ha[p->A]; + ++area; + + ++v; + } + + ++ps; + ++pd; + } + } + } + + //same as RenderRect, except the histogram is alpha-weighted instead of keeping a separate alpha channel histogram. + public unsafe void RenderRectWithAlpha( + int rad, + Surface src, + Surface dst, + Rectangle rect) + { + int width = src.Width; + int height = src.Height; + + int* leadingEdgeX = stackalloc int[rad + 1]; + int stride = src.Stride / sizeof(ColorBgra); + + // approximately (rad + 0.5)^2 + int cutoff = ((rad * 2 + 1) * (rad * 2 + 1) + 2) / 4; + + for (int v = 0; v <= rad; ++v) + { + for (int u = 0; u <= rad; ++u) + { + if (u * u + v * v <= cutoff) + { + leadingEdgeX[v] = u; + } + } + } + + const int hLength = 256; + int* hb = stackalloc int[hLength]; + int* hg = stackalloc int[hLength]; + int* hr = stackalloc int[hLength]; + uint hSize = (uint)(sizeof(int) * hLength); + + for (int y = rect.Top; y < rect.Bottom; y++) + { + Memory.SetToZero(hb, hSize); + Memory.SetToZero(hg, hSize); + Memory.SetToZero(hr, hSize); + + int area = 0; + int sum = 0; + + ColorBgra* ps = src.GetPointAddressUnchecked(rect.Left, y); + ColorBgra* pd = dst.GetPointAddressUnchecked(rect.Left, y); + + // assert: v + y >= 0 + int top = -Math.Min(rad, y); + + // assert: v + y <= height - 1 + int bottom = Math.Min(rad, height - 1 - y); + + // assert: u + x >= 0 + int left = -Math.Min(rad, rect.Left); + + // assert: u + x <= width - 1 + int right = Math.Min(rad, width - 1 - rect.Left); + + for (int v = top; v <= bottom; ++v) + { + ColorBgra* psamp = src.GetPointAddressUnchecked(rect.Left + left, y + v); + + for (int u = left; u <= right; ++u) + { + byte w = psamp->A; + if ((u * u + v * v) <= cutoff) + { + ++area; + sum += w; + hb[psamp->B] += w; + hg[psamp->G] += w; + hr[psamp->R] += w; + } + + ++psamp; + } + } + + for (int x = rect.Left; x < rect.Right; x++) + { + *pd = ApplyWithAlpha(*ps, area, sum, hb, hg, hr); + + // assert: u + x >= 0 + left = -Math.Min(rad, x); + + // assert: u + x <= width - 1 + right = Math.Min(rad + 1, width - 1 - x); + + // Subtract trailing edge top half + int v = -1; + + while (v >= top) + { + int u = leadingEdgeX[-v]; + + if (-u >= left) + { + break; + } + + --v; + } + + while (v >= top) + { + int u = leadingEdgeX[-v]; + ColorBgra* p = unchecked(ps + (v * stride)) - u; + byte w = p->A; + + hb[p->B] -= w; + hg[p->G] -= w; + hr[p->R] -= w; + sum -= w; + --area; + + --v; + } + + // add leading edge top half + v = -1; + while (v >= top) + { + int u = leadingEdgeX[-v]; + + if (u + 1 <= right) + { + break; + } + + --v; + } + + while (v >= top) + { + int u = leadingEdgeX[-v]; + ColorBgra* p = unchecked(ps + (v * stride)) + u + 1; + byte w = p->A; + + hb[p->B] += w; + hg[p->G] += w; + hr[p->R] += w; + sum += w; + ++area; + + --v; + } + + // Subtract trailing edge bottom half + v = 0; + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + + if (-u >= left) + { + break; + } + + ++v; + } + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + ColorBgra* p = ps + v * stride - u; + byte w = p->A; + + hb[p->B] -= w; + hg[p->G] -= w; + hr[p->R] -= w; + sum -= w; + --area; + + ++v; + } + + // add leading edge bottom half + v = 0; + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + + if (u + 1 <= right) + { + break; + } + + ++v; + } + + while (v <= bottom) + { + int u = leadingEdgeX[v]; + ColorBgra* p = ps + v * stride + u + 1; + byte w = p->A; + + hb[p->B] += w; + hg[p->G] += w; + hr[p->R] += w; + sum += w; + ++area; + + ++v; + } + + ++ps; + ++pd; + } + } + } + } +} diff --git a/src/Effects/MandelbrotFractalEffect.cs b/src/Effects/MandelbrotFractalEffect.cs new file mode 100644 index 0000000..d715ed0 --- /dev/null +++ b/src/Effects/MandelbrotFractalEffect.cs @@ -0,0 +1,225 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace PaintDotNet.Effects +{ + public sealed class MandelbrotFractalEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("MandelbrotFractalEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MandelbrotFractalEffectIcon.png").Reference; + } + } + + public enum PropertyNames + { + Factor = 0, + Quality = 1, + Zoom = 2, + Angle = 3, + InvertColors = 4 + } + + public MandelbrotFractalEffect() + : base(StaticName, + StaticImage, + SubmenuNames.Render, + EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Factor, 1, 1, 10)); + props.Add(new DoubleProperty(PropertyNames.Zoom, 10, 0, 100)); + props.Add(new DoubleProperty(PropertyNames.Angle, 0.0, -180.0, +180.0)); + props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + props.Add(new BooleanProperty(PropertyNames.InvertColors)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Factor, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MandelbrotFractalEffect.ConfigDialog.Factor.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Zoom, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MandelbrotFractalEffect.ConfigDialog.Zoom.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MandelbrotFractalEffect.ConfigDialog.Angle.DisplayName")); + configUI.SetPropertyControlType(PropertyNames.Angle, PropertyControlType.AngleChooser); + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MandelbrotFractalEffect.ConfigDialog.Quality.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.InvertColors, ControlInfoPropertyNames.DisplayName, string.Empty); + configUI.SetPropertyControlValue(PropertyNames.InvertColors, ControlInfoPropertyNames.Description, PdnResources.GetString("MandelbrotFractalEffect.ConfigDialog.InvertColors.Description")); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.zoom = 1 + zoomFactor * newToken.GetProperty(PropertyNames.Zoom).Value; + this.factor = newToken.GetProperty(PropertyNames.Factor).Value; + this.quality = newToken.GetProperty(PropertyNames.Quality).Value; + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + this.invertColors = newToken.GetProperty(PropertyNames.InvertColors).Value; + this.angleTheta = (this.angle * 2 * Math.PI) / 360; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + private static double zoomFactor = 20.0; + private double zoom; + + private int factor; + private int quality = 1; + private double angle = 0; + + private double angleTheta; + + private const double xOffsetBasis = -0.7; + private double xOffset = xOffsetBasis; + + private const double yOffsetBasis = -0.29; + private double yOffset = yOffsetBasis; + + private bool invertColors; + + private const double max = 100000; + private static readonly double invLogMax = 1.0 / Math.Log(max); + + private static double Mandelbrot(double r, double i, int factor) + { + int c = 0; + double x = 0; + double y = 0; + + while ((c * factor) < 1024 && + ((x * x) + (y * y)) < max) + { + double t = x; + + x = x * x - y * y + r; + y = 2 * t * y + i; + + ++c; + } + + return c - Math.Log(y * y + x * x) * invLogMax; + } + + protected override unsafe void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + int w = DstArgs.Width; + int h = DstArgs.Height; + + double wDiv2 = (double)w / 2; + double hDiv2 = (double)h / 2; + + double invH = 1.0 / h; + double invZoom = 1.0 / this.zoom; + + double invQuality = 1.0 / (double)this.quality; + + int count = this.quality * this.quality + 1; + double invCount = 1.0 / (double)count; + + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle rect = renderRects[ri]; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; x++) + { + int r = 0; + int g = 0; + int b = 0; + int a = 0; + + for (double i = 0; i < count; i++) + { + double u = (2.0 * x - w + (i * invCount)) * invH; + double v = (2.0 * y - h + ((i * invQuality) % 1)) * invH; + + double radius = Math.Sqrt((u * u) + (v * v)); + double radiusP = radius; + double theta = Math.Atan2(v, u); + double thetaP = theta + this.angleTheta; + + double uP = radiusP * Math.Cos(thetaP); + double vP = radiusP * Math.Sin(thetaP); + + double m = Mandelbrot( + (uP * invZoom) + this.xOffset, + (vP * invZoom) + this.yOffset, + this.factor); + + double c = 64 + this.factor * m; + + r += Utility.ClampToByte(c - 768); + g += Utility.ClampToByte(c - 512); + b += Utility.ClampToByte(c - 256); + a += Utility.ClampToByte(c - 0); + } + + *dstPtr = ColorBgra.FromBgra( + Utility.ClampToByte(b / count), + Utility.ClampToByte(g / count), + Utility.ClampToByte(r / count), + Utility.ClampToByte(a / count)); + + ++dstPtr; + } + } + + if (this.invertColors) + { + for (int y = rect.Top; y < rect.Bottom; y++) + { + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + ColorBgra c = *dstPtr; + + c.B = (byte)(255 - c.B); + c.G = (byte)(255 - c.G); + c.R = (byte)(255 - c.R); + + *dstPtr = c; + ++dstPtr; + } + } + } + } + } + } +} diff --git a/src/Effects/MedianEffect.cs b/src/Effects/MedianEffect.cs new file mode 100644 index 0000000..cd60b18 --- /dev/null +++ b/src/Effects/MedianEffect.cs @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Drawing; +using System.Collections.Generic; + +namespace PaintDotNet.Effects +{ + public sealed class MedianEffect + : LocalHistogramEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("MedianEffect.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MedianEffectIcon.png"); + } + } + + private int radius; + private int percentile; + + public MedianEffect() + : base(StaticName, + StaticImage.Reference, + SubmenuNames.Noise, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Radius = 0, + Percentile = 1 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Radius, 10, 1, 200)); + props.Add(new Int32Property(PropertyNames.Percentile, 50, 0, 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MedianEffect.ConfigDialog.RadiusLabel")); + configUI.SetPropertyControlValue(PropertyNames.Percentile, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MedianEffect.ConfigDialog.PercentileLabel")); + + return configUI; + } + + public unsafe override ColorBgra Apply(ColorBgra src, int area, int* hb, int* hg, int* hr, int* ha) + { + ColorBgra c = GetPercentile(this.percentile, area, hb, hg, hr, ha); + return c; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.radius = newToken.GetProperty(PropertyNames.Radius).Value; + this.percentile = newToken.GetProperty(PropertyNames.Percentile).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + foreach (Rectangle rect in rois) + { + RenderRect(this.radius, SrcArgs.Surface, DstArgs.Surface, rect); + } + } + } +} diff --git a/src/Effects/MotionBlurEffect.cs b/src/Effects/MotionBlurEffect.cs new file mode 100644 index 0000000..9425dd1 --- /dev/null +++ b/src/Effects/MotionBlurEffect.cs @@ -0,0 +1,147 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class MotionBlurEffect + : InternalPropertyBasedEffect + { + public MotionBlurEffect() + : base(PdnResources.GetString("MotionBlurEffect.Name"), + PdnResources.GetImageResource("Icons.MotionBlurEffect.png").Reference, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Angle = 0, + Distance = 1, + Centered = 2 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Angle, 25, -180, +180)); + props.Add(new BooleanProperty(PropertyNames.Centered, true)); + props.Add(new Int32Property(PropertyNames.Distance, 10, 1, 200)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MotionBlurEffectConfigDialog.AngleHeader.Text")); + configUI.SetPropertyControlType(PropertyNames.Angle, PropertyControlType.AngleChooser); + configUI.SetPropertyControlValue(PropertyNames.Centered, ControlInfoPropertyNames.DisplayName, string.Empty); + configUI.SetPropertyControlValue(PropertyNames.Centered, ControlInfoPropertyNames.Description, PdnResources.GetString("MotionBlurEffectConfigDialog.CenteredCheckBox.Text")); + configUI.SetPropertyControlValue(PropertyNames.Distance, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("MotionBlurEffectConfigDialog.DistanceHeader.Text")); + + return configUI; + } + + private double angle; + private int distance; + private bool centered; + private PointF[] points; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + this.distance = newToken.GetProperty(PropertyNames.Distance).Value; + this.centered = newToken.GetProperty(PropertyNames.Centered).Value; + + PointF start = new PointF(0, 0); + double theta = ((double)(this.angle + 180) * 2 * Math.PI) / 360.0; + double alpha = (double)distance; + double x = alpha * Math.Cos(theta); + double y = alpha * Math.Sin(theta); + PointF end = new PointF((float)x, (float)(-y)); + + if (this.centered) + { + start.X = -end.X / 2.0f; + start.Y = -end.Y / 2.0f; + + end.X /= 2.0f; + end.Y /= 2.0f; + } + + this.points = new PointF[((1 + this.distance) * 3) / 2]; + + if (this.points.Length == 1) + { + this.points[0] = new PointF(0, 0); + } + else + { + for (int i = 0; i < this.points.Length; ++i) + { + float frac = (float)i / (float)(this.points.Length - 1); + this.points[i] = Utility.Lerp(start, end, frac); + } + } + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + ColorBgra* samples = stackalloc ColorBgra[this.points.Length]; + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle rect = rois[i]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra *dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + int sampleCount = 0; + + PointF a = new PointF((float)x + points[0].X, (float)y + points[0].Y); + PointF b = new PointF((float)x + points[points.Length - 1].X, (float)y + points[points.Length - 1].Y); + + for (int j = 0; j < this.points.Length; ++j) + { + PointF pt = new PointF(this.points[j].X + (float)x, this.points[j].Y + (float)y); + + if (pt.X >= 0 && pt.Y >= 0 && pt.X <= (src.Width - 1) && pt.Y <= (src.Height - 1)) + { + samples[sampleCount] = src.GetBilinearSample(pt.X, pt.Y); + ++sampleCount; + } + } + + *dstPtr = ColorBgra.Blend(samples, sampleCount); + ++dstPtr; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Effects/OilPaintingEffect.cs b/src/Effects/OilPaintingEffect.cs new file mode 100644 index 0000000..51bd971 --- /dev/null +++ b/src/Effects/OilPaintingEffect.cs @@ -0,0 +1,201 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Original C++ implementation by Jason Waltman as part of "Filter Explorer," http://www.jasonwaltman.com/thesis/index.html + +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class OilPaintingEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("OilPaintingEffect.Name"); + } + } + + public OilPaintingEffect() + : base(StaticName, + PdnResources.GetImageResource("Icons.OilPaintingEffect.png").Reference, + SubmenuNames.Artistic, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + BrushSize = 0, + Coarseness = 1 + } + + private int brushSize; + private byte coarseness; + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.BrushSize, 3, 1, 8)); + props.Add(new Int32Property(PropertyNames.Coarseness, 50, 3, 255)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.BrushSize, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("OilPaintingEffect.ConfigDialog.Amount1Label")); + configUI.SetPropertyControlValue(PropertyNames.Coarseness, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("OilPaintingEffect.ConfigDialog.Amount2Label")); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.brushSize = newToken.GetProperty(PropertyNames.BrushSize).Value; + this.coarseness = (byte)newToken.GetProperty(PropertyNames.Coarseness).Value; + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface src = SrcArgs.Surface; + Surface dst = DstArgs.Surface; + int width = src.Width; + int height = src.Height; + + int arrayLens = 1 + this.coarseness; + + int localStoreSize = arrayLens * 5 * sizeof(int); + + byte* localStore = stackalloc byte[localStoreSize]; + byte* p = localStore; + + int* intensityCount = (int*)p; + p += arrayLens * sizeof(int); + + uint* avgRed = (uint*)p; + p += arrayLens * sizeof(uint); + + uint* avgGreen = (uint*)p; + p += arrayLens * sizeof(uint); + + uint* avgBlue = (uint*)p; + p += arrayLens * sizeof(uint); + + uint* avgAlpha = (uint*)p; + p += arrayLens * sizeof(uint); + + byte maxIntensity = this.coarseness; + + for (int r = startIndex; r < startIndex + length; ++r) + { + Rectangle rect = rois[r]; + + int rectTop = rect.Top; + int rectBottom = rect.Bottom; + int rectLeft = rect.Left; + int rectRight = rect.Right; + + for (int y = rectTop; y < rectBottom; ++y) + { + ColorBgra *dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + int top = y - brushSize; + int bottom = y + brushSize + 1; + + if (top < 0) + { + top = 0; + } + + if (bottom > height) + { + bottom = height; + } + + for (int x = rectLeft; x < rectRight; ++x) + { + SystemLayer.Memory.SetToZero(localStore, (ulong)localStoreSize); + + int left = x - brushSize; + int right = x + brushSize + 1; + + if (left < 0) + { + left = 0; + } + + if (right > width) + { + right = width; + } + + int numInt = 0; + + for (int j = top; j < bottom; ++j) + { + ColorBgra *srcPtr = src.GetPointAddressUnchecked(left, j); + + for (int i = left; i < right; ++i) + { + byte intensity = Utility.FastScaleByteByByte(srcPtr->GetIntensityByte(), maxIntensity); + + ++intensityCount[intensity]; + ++numInt; + + avgRed[intensity] += srcPtr->R; + avgGreen[intensity] += srcPtr->G; + avgBlue[intensity] += srcPtr->B; + avgAlpha[intensity] += srcPtr->A; + + ++srcPtr; + } + } + + byte chosenIntensity = 0; + int maxInstance = 0; + + for (int i = 0; i <= maxIntensity; ++i) + { + if (intensityCount[i] > maxInstance) + { + chosenIntensity = (byte)i; + maxInstance = intensityCount[i]; + } + } + + // TODO: correct handling of alpha values? + + byte R = (byte)(avgRed[chosenIntensity] / maxInstance); + byte G = (byte)(avgGreen[chosenIntensity] / maxInstance); + byte B = (byte)(avgBlue[chosenIntensity] / maxInstance); + byte A = (byte)(avgAlpha[chosenIntensity] / maxInstance); + + *dstPtr = ColorBgra.FromBgra(B, G, R, A); + ++dstPtr; + } + } + } + } + + } +} diff --git a/src/Effects/OutlineEffect.cs b/src/Effects/OutlineEffect.cs new file mode 100644 index 0000000..251b16e --- /dev/null +++ b/src/Effects/OutlineEffect.cs @@ -0,0 +1,179 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Drawing; +using System.Collections.Generic; + +namespace PaintDotNet.Effects +{ + public sealed class OutlineEffect + : LocalHistogramEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("OutlineEffect.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.OutlineEffectIcon.png"); + } + } + + public enum PropertyNames + { + Thickness = 0, + Intensity = 1 + } + + private int thickness; + private int intensity; + + public OutlineEffect() + : base(StaticName, StaticImage.Reference, SubmenuNames.Stylize, EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Thickness, 3, 1, 200)); + props.Add(new Int32Property(PropertyNames.Intensity, 50, 0, 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Thickness, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("OutlineEffect.ConfigDialog.ThicknessLabel")); + configUI.SetPropertyControlValue(PropertyNames.Intensity, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("OutlineEffect.ConfigDialog.IntensityLabel")); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.thickness = newToken.GetProperty(PropertyNames.Thickness).Value; + this.intensity = newToken.GetProperty(PropertyNames.Intensity).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + public unsafe override ColorBgra Apply(ColorBgra src, int area, int* hb, int* hg, int* hr, int* ha) + { + int minCount1 = area * (100 - this.intensity) / 200; + int minCount2 = area * (100 + this.intensity) / 200; + + int bCount = 0; + int b1 = 0; + while (b1 < 255 && hb[b1] == 0) + { + ++b1; + } + + while (b1 < 255 && bCount < minCount1) + { + bCount += hb[b1]; + ++b1; + } + + int b2 = b1; + while (b2 < 255 && bCount < minCount2) + { + bCount += hb[b2]; + ++b2; + } + + int gCount = 0; + int g1 = 0; + while (g1 < 255 && hg[g1] == 0) + { + ++g1; + } + + while (g1 < 255 && gCount < minCount1) + { + gCount += hg[g1]; + ++g1; + } + + int g2 = g1; + while (g2 < 255 && gCount < minCount2) + { + gCount += hg[g2]; + ++g2; + } + + int rCount = 0; + int r1 = 0; + while (r1 < 255 && hr[r1] == 0) + { + ++r1; + } + + while (r1 < 255 && rCount < minCount1) + { + rCount += hr[r1]; + ++r1; + } + + int r2 = r1; + while (r2 < 255 && rCount < minCount2) + { + rCount += hr[r2]; + ++r2; + } + + int aCount = 0; + int a1 = 0; + while (a1 < 255 && hb[a1] == 0) + { + ++a1; + } + + while (a1 < 255 && aCount < minCount1) + { + aCount += ha[a1]; + ++a1; + } + + int a2 = a1; + while (a2 < 255 && aCount < minCount2) + { + aCount += ha[a2]; + ++a2; + } + + return ColorBgra.FromBgra( + (byte)(255 - (b2 - b1)), + (byte)(255 - (g2 - g1)), + (byte)(255 - (r2 - r1)), + (byte)(a2)); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + foreach (Rectangle rect in rois) + { + RenderRect(this.thickness, SrcArgs.Surface, DstArgs.Surface, rect); + } + } + } +} diff --git a/src/Effects/PanControl.cs b/src/Effects/PanControl.cs new file mode 100644 index 0000000..e204b98 --- /dev/null +++ b/src/Effects/PanControl.cs @@ -0,0 +1,253 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public class PanControl + : UserControl + { + private bool mouseDown = false; + private PointF startPosition = new PointF(0, 0); + private Point startMouse = new Point(0, 0); + private Bitmap renderSurface = null; // used for double-buffering + private Cursor handCursor; + private Cursor handMouseDownCursor; + + public PanControl() + { + if (!this.DesignMode) + { + handCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur")); + handMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur")); + this.Cursor = handCursor; + } + + InitializeComponent(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (handCursor != null) + { + handCursor.Dispose(); + handCursor = null; + } + + if (handMouseDownCursor != null) + { + handMouseDownCursor.Dispose(); + handMouseDownCursor = null; + } + } + + base.Dispose(disposing); + } + + private void InitializeComponent() + { + // + // PanControl + // + this.Name = "PanControl"; + this.Size = new System.Drawing.Size(184, 168); + } + + private PointF position = new PointF(0, 0); + + [Browsable(false)] + public PointF Position + { + get + { + return position; + } + + set + { + if (position != value) + { + position = value; + + this.Invalidate(); + OnPositionChanged(); + this.Update(); + } + } + } + + public event EventHandler PositionChanged; + protected void OnPositionChanged() + { + if (PositionChanged != null) + { + PositionChanged(this, EventArgs.Empty); + } + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (mouseDown) + { + return; + } + + if (e.Button == MouseButtons.Left) + { + mouseDown = true; + startPosition = position; + startMouse = new Point(e.X, e.Y); + + Cursor = handMouseDownCursor; + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove (e); + + if (mouseDown && e.Button == MouseButtons.Left) + { + Position = new PointF(2.0f * e.X / this.Width - 1, 2.0f * e.Y / this.Height - 1); + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp (e); + + if (mouseDown) + { + Cursor = handCursor; + mouseDown = false; + } + } + + private void CheckRenderSurface() + { + if (renderSurface != null && renderSurface.Size != Size) + { + renderSurface.Dispose(); + renderSurface = null; + } + + if (renderSurface == null) + { + renderSurface = new Bitmap(Width, Height); + + using (Graphics g = Graphics.FromImage(renderSurface)) + { + DrawToGraphics(g); + } + } + } + + private void DoPaint(Graphics g) + { + CheckRenderSurface(); + g.DrawImage(renderSurface, ClientRectangle, ClientRectangle, GraphicsUnit.Pixel); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint (e); + renderSurface = null; + DoPaint(e.Graphics); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + DoPaint(pevent.Graphics); + } + + protected void DrawToGraphics(Graphics g) + { + PointF ptCenter = new PointF(Width / 2.0f, Height / 2.0f); + PointF ptDot = new PointF((1 + position.X) * Width / 2.0f, (1 + position.Y) * Height / 2.0f); + PointF ptArrow; + + if (-1 <= position.X && position.X <= 1 && + -1 <= position.Y && position.Y <= 1) + { + ptArrow = new PointF((1 + position.X) * Width / 2, (1 + position.Y) * Height / 2); + } + else + { + ptArrow = new PointF((1 + position.X) * Width / 2, (1 + position.Y) * Height / 2); + + if (Math.Abs(Position.X) > Math.Abs(Position.Y)) + { + if (position.X > 0) + { + ptArrow.X = this.Width - 1; + ptArrow.Y = (1 + position.Y / position.X) * Height / 2; + } + else + { + ptArrow.X = 0; + ptArrow.Y = (1 - position.Y / position.X) * Height / 2; + } + } + else + { + if (position.Y > 0) + { + ptArrow.X = (1 + position.X / position.Y) * Width / 2; + ptArrow.Y = this.Height - 1; + } + else + { + ptArrow.X = (1 - position.X / position.Y) * Width / 2; + ptArrow.Y = 0; + } + } + } + + g.Clear(this.BackColor); + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.HighQuality; + + using (Pen pen = (Pen)Pens.Black.Clone()) + { + pen.SetLineCap(LineCap.Round, LineCap.DiamondAnchor, DashCap.Flat); + pen.EndCap = LineCap.ArrowAnchor; + pen.Width = 4.0f; + pen.Color = SystemColors.ControlDark; + + g.DrawLine(pen, ptCenter, ptArrow); + } + + using (Pen pen = (Pen)Pens.Black.Clone()) + { + pen.SetLineCap(LineCap.DiamondAnchor, LineCap.DiamondAnchor, DashCap.Flat); + pen.Width = 3.0f; + pen.Color = SystemColors.ControlText; + + g.DrawLine(pen, ptDot.X - 6, ptDot.Y, ptDot.X + 6, ptDot.Y); + g.DrawLine(pen, ptDot.X, ptDot.Y - 6, ptDot.X, ptDot.Y + 6); + } + + g.SmoothingMode = oldSM; + } + } +} diff --git a/src/Effects/PanControl.resx b/src/Effects/PanControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/PencilSketchEffect.cs b/src/Effects/PencilSketchEffect.cs new file mode 100644 index 0000000..9ca1734 --- /dev/null +++ b/src/Effects/PencilSketchEffect.cs @@ -0,0 +1,139 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class PencilSketchEffect + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + PencilTipSize = 0, + ColorRange = 1 + } + + private static string StaticName + { + get + { + return PdnResources.GetString("PencilSketchEffect.Name"); + } + } + + private static ImageResource StaticIcon + { + get + { + return PdnResources.GetImageResource("Icons.PencilSketchEffectIcon.png"); + } + } + + private GaussianBlurEffect blurEffect; + private PropertyCollection blurProps; + + private UnaryPixelOps.Desaturate desaturateOp = new UnaryPixelOps.Desaturate(); + + private DesaturateEffect desaturateEffect = new DesaturateEffect(); + private InvertColorsEffect invertEffect = new InvertColorsEffect(); + + private BrightnessAndContrastAdjustment bacAdjustment = new BrightnessAndContrastAdjustment(); + private PropertyCollection bacProps; + + private UserBlendOps.ColorDodgeBlendOp colorDodgeOp = new UserBlendOps.ColorDodgeBlendOp(); + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.PencilTipSize, 2, 1, 20)); + props.Add(new Int32Property(PropertyNames.ColorRange, 0, -20, +20)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.PencilTipSize, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PencilSketchEffect.ConfigDialog.PencilTipSizeLabel")); + configUI.SetPropertyControlValue(PropertyNames.ColorRange, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PencilSketchEffect.ConfigDialog.RangeLabel")); + + return configUI; + } + + private int pencilTipSize; + private int colorRange; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.pencilTipSize = newToken.GetProperty(PropertyNames.PencilTipSize).Value; + this.colorRange = newToken.GetProperty(PropertyNames.ColorRange).Value; + + PropertyBasedEffectConfigToken blurToken = new PropertyBasedEffectConfigToken(this.blurProps); + blurToken.SetPropertyValue(GaussianBlurEffect.PropertyNames.Radius, this.pencilTipSize); + this.blurEffect.SetRenderInfo(blurToken, dstArgs, srcArgs); + + PropertyBasedEffectConfigToken bacToken = new PropertyBasedEffectConfigToken(this.bacProps); + bacToken.SetPropertyValue(BrightnessAndContrastAdjustment.PropertyNames.Brightness, this.colorRange); + bacToken.SetPropertyValue(BrightnessAndContrastAdjustment.PropertyNames.Contrast, -this.colorRange); + this.bacAdjustment.SetRenderInfo(bacToken, dstArgs, dstArgs); + + this.desaturateEffect.SetRenderInfo(null, dstArgs, dstArgs); + + this.invertEffect.SetRenderInfo(null, dstArgs, dstArgs); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + this.blurEffect.Render(rois, startIndex, length); + this.bacAdjustment.Render(rois, startIndex, length); + this.invertEffect.Render(rois, startIndex, length); + this.desaturateEffect.Render(rois, startIndex, length); + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle roi = rois[i]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra* srcPtr = SrcArgs.Surface.GetPointAddressUnchecked(roi.X, y); + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddressUnchecked(roi.X, y); + + for (int x = roi.Left; x < roi.Right; ++x) + { + ColorBgra srcGrey = this.desaturateOp.Apply(*srcPtr); + ColorBgra sketched = this.colorDodgeOp.Apply(srcGrey, *dstPtr); + *dstPtr = sketched; + + ++srcPtr; + ++dstPtr; + } + } + } + } + + public PencilSketchEffect() + : base(StaticName, StaticIcon.Reference, SubmenuNames.Artistic, EffectFlags.Configurable) + { + this.blurEffect = new GaussianBlurEffect(); + this.blurProps = this.blurEffect.CreatePropertyCollection(); + + this.bacAdjustment = new BrightnessAndContrastAdjustment(); + this.bacProps = this.bacAdjustment.CreatePropertyCollection(); + } + } +} diff --git a/src/Effects/PerlinNoise2D.cs b/src/Effects/PerlinNoise2D.cs new file mode 100644 index 0000000..1438f36 --- /dev/null +++ b/src/Effects/PerlinNoise2D.cs @@ -0,0 +1,202 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2006-2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// Parts adapted to 2D from perlin's reference 3D implementation +// http://mrl.nyu.edu/~perlin/noise/ + + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.Effects +{ + internal static class PerlinNoise2D + { + // precalculated rotation matrix coefficients + private static readonly double rot_11; + private static readonly double rot_12; + private static readonly double rot_21; + private static readonly double rot_22; + + private static readonly int[] permuteLookup; + + private static readonly int[] permutationTable = + new int[] + { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, + 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, + 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, + 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, + 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, + 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, + 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, + 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, + 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, + 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, + 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, + 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, + 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, + 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, + 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, + 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, + 195, 78, 66, 215, 61, 156, 180 + }; + + static PerlinNoise2D() + { + permuteLookup = new int[512]; + + for (int i = 0; i < 256; i++) + { + permuteLookup[256 + i] = permutationTable[i]; + permuteLookup[i] = permutationTable[i]; + } + + // precalculate a rotation matrix - arbitary angle... + double angle = 137.2 / 180.0 * Math.PI; + + rot_11 = Math.Cos(angle); + rot_12 = -Math.Sin(angle); + rot_21 = Math.Sin(angle); + rot_22 = Math.Cos(angle); + } + + public static double Noise(double x, double y, double detail, double roughness, byte seed) + { + double total = 0.0; + double frequency = 1; + double amplitude = 1; + + double partialOctaveFactor = detail; + int octaves = (int)Math.Ceiling(detail); + + for (int i = 0; i < octaves; i++) + { + // rotate the coordinates. + // reduces correlation between octaves. + double xr = ((x * rot_11) + (y * rot_12)); + double yr = ((x * rot_21) + (y * rot_22)); + + double noise = Noise(xr * frequency, yr * frequency, seed); + + noise *= amplitude; + + // if this is the last 'partial' octave, + // reduce its contribution accordingly. + if (partialOctaveFactor < 1) + { + noise *= partialOctaveFactor; + } + + total += noise; + + // scale amplitude for next octave. + amplitude = amplitude * roughness; + + // if the contribution is going to be negligable, + // don't bother with higher octaves. + if (amplitude < 0.001) + { + break; + } + + // setup for next octave + frequency += frequency; + partialOctaveFactor -= 1.0; + + // offset the coordinates by prime numbers, with prime difference. + // reduces correlation between octaves. + x = xr + 499; + y = yr + 506; + } + + return total; + } + + private static double Fade(double t) + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private static double Grad(int hash, double x, double y) + { + int h = hash & 15; + double u = (h < 8) ? x : y; + double v = (h < 4) ? y : ((h == 12 || h == 14) ? x : 0); + + return (((h & 1) == 0) ? u : -u) + (((h & 2) == 0) ? v : -v); + } + + private static double Lerp(double a, double b, double t) + { + return a + t * (b - a); + } + + private static double Noise(double x, double y, byte seed) + { + double xf = Math.Floor(x); + double yf = Math.Floor(y); + + int ix = (int)xf & 255; + int iy = (int)yf & 255; + + x -= xf; + y -= yf; + + double u = Fade(x); + double v = Fade(y); + + int a = permuteLookup[ix + seed] + iy; + int aa = permuteLookup[a]; + int ab = permuteLookup[a + 1]; + int b = permuteLookup[ix + 1 + seed] + iy; + int ba = permuteLookup[b]; + int bb = permuteLookup[b + 1]; + + double gradAA = Grad(permuteLookup[aa], x, y); + double gradBA = Grad(permuteLookup[ba], x - 1, y); + + double edge1 = Lerp(gradAA, gradBA, u); + + double gradAB = Grad(permuteLookup[ab], x, y - 1); + double gradBB = Grad(permuteLookup[bb], x - 1, y - 1); + + double edge2 = Lerp(gradAB, gradBB, u); + + double lerped = Lerp(edge1, edge2, v); + + return lerped; + } + } +} diff --git a/src/Effects/PixelateEffect.cs b/src/Effects/PixelateEffect.cs new file mode 100644 index 0000000..ec5aa0e --- /dev/null +++ b/src/Effects/PixelateEffect.cs @@ -0,0 +1,137 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.Effects +{ + public sealed class PixelateEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("PixelateEffect.Name"); + } + } + + public PixelateEffect() + : base(StaticName, + PdnResources.GetImageResource("Icons.PixelateEffect.png").Reference, + SubmenuNames.Distort, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + CellSize = 0 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.CellSize, 2, 1, 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.CellSize, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PixelateEffect.ConfigDialog.SliderLabel")); + // TODO: units label? + //aecg.SliderUnitsName = PdnResources.GetString("PixelateEffect.ConfigDialog.SliderUnitsName"); + + return configUI; + } + + private ColorBgra ComputeCellColor(int x, int y, RenderArgs src, int cellSize) + { + Rectangle cell = GetCellBox(x, y, cellSize); + cell.Intersect(src.Bounds); + + int left = cell.Left; + int right = cell.Right - 1; + int bottom = cell.Bottom - 1; + int top = cell.Top; + + ColorBgra colorTopLeft = src.Surface[left, top]; + ColorBgra colorTopRight = src.Surface[right, top]; + ColorBgra colorBottomLeft = src.Surface[left, bottom]; + ColorBgra colorBottomRight = src.Surface[right, bottom]; + + ColorBgra c = ColorBgra.BlendColors4W16IP(colorTopLeft, 16384, colorTopRight, 16384, colorBottomLeft, 16384, colorBottomRight, 16384); + + return c; + } + + private Rectangle GetCellBox(int x, int y, int cellSize) + { + int widthBoxNum = x % cellSize; + int heightBoxNum = y % cellSize; + Point leftUpper = new Point(x - widthBoxNum, y - heightBoxNum); + Rectangle returnMe = new Rectangle(leftUpper, new Size(cellSize, cellSize)); + return returnMe; + } + + private int cellSize; + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.cellSize = newToken.GetProperty(PropertyNames.CellSize).Value; + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle rect = rois[i]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + int yEnd = y + 1; + + for (int x = rect.Left; x < rect.Right; ++x) + { + Rectangle cellRect = GetCellBox(x, y, this.cellSize); + cellRect.Intersect(DstArgs.Bounds); + ColorBgra color = ComputeCellColor(x, y, SrcArgs, this.cellSize); + + int xEnd = Math.Min(rect.Right, cellRect.Right); + yEnd = Math.Min(rect.Bottom, cellRect.Bottom); + + for (int y2 = y; y2 < yEnd; ++y2) + { + ColorBgra *ptr = DstArgs.Surface.GetPointAddressUnchecked(x, y2); + + for (int x2 = x; x2 < xEnd; ++x2) + { + ptr->Bgra = color.Bgra; + ++ptr; + } + } + + x = xEnd - 1; + } + + y = yEnd - 1; + } + } + } + } +} diff --git a/src/Effects/PolarInversionEffect.cs b/src/Effects/PolarInversionEffect.cs new file mode 100644 index 0000000..dad5a97 --- /dev/null +++ b/src/Effects/PolarInversionEffect.cs @@ -0,0 +1,127 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2006-2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Drawing; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; + +namespace PaintDotNet.Effects +{ + public sealed class PolarInversionEffect + : WarpEffectBase + { + public PolarInversionEffect() + : base(PdnResources.GetString("PolarInversion.Name"), + PdnResources.GetImageResource("Icons.PolarInversionEffect.png").Reference, + SubmenuNames.Distort, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Amount = 0, + Offset = 1, + EdgeBehavior = 2, + Quality = 3 + } + + private double amount; + + protected override PropertyCollection OnCreatePropertyCollection() + { + List properties = new List(); + + properties.Add(new DoubleProperty(PropertyNames.Amount, 1, -4, 4)); + properties.Add(new DoubleVectorProperty(PropertyNames.Offset, Pair.Create(0, 0), Pair.Create(-2, -2), Pair.Create(2, 2))); + properties.Add(new StaticListChoiceProperty(PropertyNames.EdgeBehavior, new object[] { WarpEdgeBehavior.Clamp, WarpEdgeBehavior.Reflect, WarpEdgeBehavior.Wrap }, 2)); + properties.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + + return new PropertyCollection(properties); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = base.OnCreateConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PolarInversion.ConfigUI.Amount.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.UseExponentialScale, true); + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.SliderLargeChange, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.SliderSmallChange, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.UpDownIncrement, 0.01); + + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PolarInversion.ConfigUI.Offset.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeX, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeX, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementX, 0.01); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeY, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeY, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementY, 0.01); + + Rectangle selection = this.EnvironmentParameters.GetSelection(base.EnvironmentParameters.SourceSurface.Bounds).GetBoundsInt(); + ImageResource propertyValue = ImageResource.FromImage(base.EnvironmentParameters.SourceSurface.CreateAliasedBitmap(selection)); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.StaticImageUnderlay, propertyValue); + + configUI.SetPropertyControlValue(PropertyNames.EdgeBehavior, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PolarInversion.ConfigUI.EdgeBehavior.DisplayName")); + + PropertyControlInfo edgeBehaviorPCI = configUI.FindControlForPropertyName(PropertyNames.EdgeBehavior); + edgeBehaviorPCI.SetValueDisplayName(WarpEdgeBehavior.Clamp, PdnResources.GetString("PolarInversion.ConfigUI.EdgeBehavior.Clamp.DisplayName")); + edgeBehaviorPCI.SetValueDisplayName(WarpEdgeBehavior.Reflect, PdnResources.GetString("PolarInversion.ConfigUI.EdgeBehavior.Reflect.DisplayName")); + edgeBehaviorPCI.SetValueDisplayName(WarpEdgeBehavior.Wrap, PdnResources.GetString("PolarInversion.ConfigUI.EdgeBehavior.Wrap.DisplayName")); + + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("PolarInversion.ConfigUI.Quality.DisplayName")); + + return configUI; + } + + protected override void OnSetRenderInfo2(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.amount = newToken.GetProperty(PropertyNames.Amount).Value; + base.Offset = newToken.GetProperty(PropertyNames.Offset).Value; + base.EdgeBehavior = (WarpEdgeBehavior)newToken.GetProperty(PropertyNames.EdgeBehavior).Value; + base.Quality = newToken.GetProperty(PropertyNames.Quality).Value; + } + + protected override void InverseTransform(ref TransformData data) + { + double x = data.X; + double y = data.Y; + + // NOTE: when x and y are zero, this will divide by zero and return NaN + double invertDistance = Utility.Lerp(1d, DefaultRadius2 / ((x * x) + (y * y)), amount); + + data.X = x * invertDistance; + data.Y = y * invertDistance; + } + } +} diff --git a/src/Effects/PosterizeAdjustment.cs b/src/Effects/PosterizeAdjustment.cs new file mode 100644 index 0000000..40ee46e --- /dev/null +++ b/src/Effects/PosterizeAdjustment.cs @@ -0,0 +1,207 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2007,2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System.Collections.Generic; +using System.Drawing; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class PosterizeAdjustment + : InternalPropertyBasedEffect + { + private class PosterizePixelOp + : UnaryPixelOp + { + private byte[] redLevels; + private byte[] greenLevels; + private byte[] blueLevels; + + public PosterizePixelOp(int red, int green, int blue) + { + this.redLevels = CalcLevels(red); + this.greenLevels = CalcLevels(green); + this.blueLevels = CalcLevels(blue); + } + + private static byte[] CalcLevels(int levelCount) + { + byte[] t1 = new byte[levelCount]; + + for (int i = 1; i < levelCount; i++) + { + t1[i] = (byte)((255 * i) / (levelCount - 1)); + } + + byte[] levels = new byte[256]; + + int j = 0; + int k = 0; + + for (int i = 0; i < 256; i++) + { + levels[i] = t1[j]; + + k += levelCount; + + if (k > 255) + { + k -= 255; + j++; + } + } + + return levels; + } + + public override ColorBgra Apply(ColorBgra color) + { + return ColorBgra.FromBgra(blueLevels[color.B], greenLevels[color.G], redLevels[color.R], color.A); + } + + public unsafe override void Apply(ColorBgra* ptr, int length) + { + while (length > 0) + { + ptr->B = this.blueLevels[ptr->B]; + ptr->G = this.greenLevels[ptr->G]; + ptr->R = this.redLevels[ptr->R]; + + ++ptr; + --length; + } + } + + public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) + { + while (length > 0) + { + dst->B = this.blueLevels[src->B]; + dst->G = this.greenLevels[src->G]; + dst->R = this.redLevels[src->R]; + dst->A = src->A; + + ++dst; + ++src; + --length; + } + } + } + + private UnaryPixelOp op; + + public PosterizeAdjustment() + : base(PdnResources.GetString("PosterizeAdjustment.Name"), + PdnResources.GetImageResource("Icons.PosterizeEffectIcon.png").Reference, + null, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + RedLevels = 0, + GreenLevels = 1, + BlueLevels = 2, + LinkLevels = 3 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.RedLevels, 16, 2, 64)); + props.Add(new Int32Property(PropertyNames.GreenLevels, 16, 2, 64)); + props.Add(new Int32Property(PropertyNames.BlueLevels, 16, 2, 64)); + props.Add(new BooleanProperty(PropertyNames.LinkLevels, true)); + + List rules = new List(); + + rules.Add(new LinkValuesBasedOnBooleanRule( + new object[] { PropertyNames.RedLevels, PropertyNames.GreenLevels, PropertyNames.BlueLevels }, + PropertyNames.LinkLevels, + false)); + + return new PropertyCollection(props, rules); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo info = PropertyBasedEffect.CreateDefaultConfigUI(props); + + info.SetPropertyControlValue( + PropertyNames.RedLevels, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("PosterizeAdjustment.ConfigDialog.RedLevels.DisplayName")); + + info.SetPropertyControlValue( + PropertyNames.GreenLevels, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("PosterizeAdjustment.ConfigDialog.GreenLevels.DisplayName")); + + info.SetPropertyControlValue( + PropertyNames.BlueLevels, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("PosterizeAdjustment.ConfigDialog.BlueLevels.DisplayName")); + + info.SetPropertyControlValue( + PropertyNames.LinkLevels, + ControlInfoPropertyNames.DisplayName, + string.Empty); + + info.SetPropertyControlValue( + PropertyNames.LinkLevels, + ControlInfoPropertyNames.Description, + PdnResources.GetString("PosterizeAdjustment.ConfigDialog.LinkLevels.Description")); + + return info; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + int red = newToken.GetProperty(PropertyNames.RedLevels).Value; + int green = newToken.GetProperty(PropertyNames.GreenLevels).Value; + int blue = newToken.GetProperty(PropertyNames.BlueLevels).Value; + + this.op = new PosterizePixelOp(red, green, blue); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + this.op.Apply(DstArgs.Surface, SrcArgs.Surface, renderRects, startIndex, length); + } + } +} diff --git a/src/Effects/PropertyBasedEffect.cs b/src/Effects/PropertyBasedEffect.cs new file mode 100644 index 0000000..e2c3b68 --- /dev/null +++ b/src/Effects/PropertyBasedEffect.cs @@ -0,0 +1,110 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public abstract class PropertyBasedEffect + : Effect + { + protected abstract PropertyCollection OnCreatePropertyCollection(); + + public PropertyCollection CreatePropertyCollection() + { + return OnCreatePropertyCollection(); + } + + protected virtual ControlInfo OnCreateConfigUI(PropertyCollection props) + { + return CreateDefaultConfigUI(props); + } + + public ControlInfo CreateConfigUI(PropertyCollection props) + { + PropertyCollection props2 = props.Clone(); + + using (props2.__Internal_BeginEventAddMoratorium()) + { + ControlInfo configUI1 = OnCreateConfigUI(props2); + ControlInfo configUI2 = configUI1.Clone(); + return configUI2; + } + } + + public static ControlInfo CreateDefaultConfigUI(IEnumerable props) + { + PanelControlInfo configUI = new PanelControlInfo(); + + foreach (Property property in props) + { + PropertyControlInfo propertyControlInfo = PropertyControlInfo.CreateFor(property); + propertyControlInfo.ControlProperties[ControlInfoPropertyNames.DisplayName].Value = property.Name; + configUI.AddChildControl(propertyControlInfo); + } + + return configUI; + } + + private string GetConfigDialogTitle() + { + return this.Name; + } + + private Icon GetConfigDialogIcon() + { + Image image = this.Image; + + Icon icon = null; + + if (image != null) + { + icon = Utility.ImageToIcon(image); + } + + return icon; + } + + protected virtual void OnCustomizeConfigUIWindowProperties(PropertyCollection props) + { + return; + } + + public override sealed EffectConfigDialog CreateConfigDialog() + { + PropertyCollection props1 = CreatePropertyCollection(); + PropertyCollection props2 = props1.Clone(); + PropertyCollection props3 = props1.Clone(); + + ControlInfo configUI1 = CreateConfigUI(props2); + ControlInfo configUI2 = configUI1.Clone(); + + PropertyCollection windowProps = PropertyBasedEffectConfigDialog.CreateWindowProperties(); + windowProps[ControlInfoPropertyNames.WindowTitle].Value = this.Name; + OnCustomizeConfigUIWindowProperties(windowProps); + PropertyCollection windowProps2 = windowProps.Clone(); + + PropertyBasedEffectConfigDialog pbecd = new PropertyBasedEffectConfigDialog(props3, configUI2, windowProps2); + + pbecd.Icon = GetConfigDialogIcon(); + + return pbecd; + } + + protected PropertyBasedEffect(string name, Image image, string subMenuName, EffectFlags flags) + : base(name, image, subMenuName, flags) + { + } + } +} diff --git a/src/Effects/PropertyBasedEffectConfigDialog.cs b/src/Effects/PropertyBasedEffectConfigDialog.cs new file mode 100644 index 0000000..a547046 --- /dev/null +++ b/src/Effects/PropertyBasedEffectConfigDialog.cs @@ -0,0 +1,449 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Core; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + internal sealed class PropertyBasedEffectConfigDialog + : EffectConfigDialog + { + private const int defaultClientWidth96Dpi = 345; + private const int defaultClientHeight96Dpi = 125; + + private Button okButton; + private Button cancelButton; + private EtchedLine etchedLine; + private ControlInfo configUI; + private Panel configUIPanel; + private Control configUIControl; + private PropertyCollection properties; + private PropertyCollection windowProperties; + + internal static PropertyCollection CreateWindowProperties() + { + List props = new List(); + + // Title + props.Add(new StringProperty( + ControlInfoPropertyNames.WindowTitle, + string.Empty, + Math.Min(1024, StringProperty.MaxMaxLength))); + + // Sizability -- defaults to false + props.Add(new BooleanProperty(ControlInfoPropertyNames.WindowIsSizable, false)); + + // Window width, as a scale of the default width (1.0 is obviously the default) + props.Add(new DoubleProperty(ControlInfoPropertyNames.WindowWidthScale, 1.0, 1.0, 2.0)); + + return new PropertyCollection(props); + } + + protected override PropertyBasedEffectConfigToken CreateInitialToken() + { + PropertyBasedEffectConfigToken token = new PropertyBasedEffectConfigToken(this.properties); + return token; + } + + protected override void InitDialogFromToken(PropertyBasedEffectConfigToken effectTokenCopy) + { + // We run this twice so that rules don't execute on stale values + // See bug #2719 + InitDialogFromTokenImpl(effectTokenCopy); + InitDialogFromTokenImpl(effectTokenCopy); + } + + private void InitDialogFromTokenImpl(PropertyBasedEffectConfigToken effectTokenCopy) + { + foreach (string propertyName in effectTokenCopy.PropertyNames) + { + Property srcProperty = effectTokenCopy.GetProperty(propertyName); + PropertyControlInfo dstPropertyControlInfo = this.configUI.FindControlForPropertyName(propertyName); + + if (dstPropertyControlInfo != null) + { + Property dstProperty = dstPropertyControlInfo.Property; + + if (dstProperty.ReadOnly) + { + dstProperty.ReadOnly = false; + dstProperty.Value = srcProperty.Value; + dstProperty.ReadOnly = true; + } + else + { + dstProperty.Value = srcProperty.Value; + } + } + } + } + + protected override void LoadIntoTokenFromDialog(PropertyBasedEffectConfigToken writeValuesHere) + { + foreach (string propertyName in this.EffectToken.PropertyNames) + { + PropertyControlInfo srcPropertyControlInfo = this.configUI.FindControlForPropertyName(propertyName); + + if (srcPropertyControlInfo != null) + { + Property srcProperty = srcPropertyControlInfo.Property; + Property dstProperty = writeValuesHere.GetProperty(srcProperty.Name); + + if (dstProperty.ReadOnly) + { + dstProperty.ReadOnly = false; + dstProperty.Value = srcProperty.Value; + dstProperty.ReadOnly = true; + } + else + { + dstProperty.Value = srcProperty.Value; + } + } + } + } + + protected override void OnShown(EventArgs e) + { + if (this.configUIControl is IFirstSelection) + { + ((IFirstSelection)this.configUIControl).FirstSelect(); + } + + base.OnShown(e); + } + + protected override void OnLoad(EventArgs e) + { + Size idealClientSize = OnLayoutImpl(); + Size idealWindowSize = ClientSizeToWindowSize(idealClientSize); + + Rectangle workingArea = Screen.FromControl(this).WorkingArea; + + Size targetWindowSize = new Size( + Math.Min(workingArea.Width, idealWindowSize.Width), + Math.Min(workingArea.Height, idealWindowSize.Height)); + + Size targetClientSize = WindowSizeToClientSize(targetWindowSize); + + if (ClientSize != targetClientSize) + { + ClientSize = targetClientSize; + OnLayoutImpl(); + } + + Size newMinimumSize = new Size(idealWindowSize.Width, Math.Min(workingArea.Height, Math.Min(idealWindowSize.Height, UI.ScaleHeight(500)))); + + Size minimumClientSize = WindowSizeToClientSize(newMinimumSize); + + MinimumSize = newMinimumSize; + + ClientSize = new Size( + (int)(ClientSize.Width * (double)this.windowProperties[ControlInfoPropertyNames.WindowWidthScale].Value), + Math.Min(ClientSize.Height, (workingArea.Height * 9) / 10)); // max height should ever be 90% of working screen area height + + base.OnLoad(e); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + Size idealClientSize = OnLayoutImpl(); + base.OnLayout(levent); + } + + private Size OnLayoutImpl() + { + int leftMargin = UI.ScaleWidth(8); + int rightMargin = UI.ScaleHeight(8); + int insetWidth = ClientSize.Width - leftMargin - rightMargin; + + int topMargin = UI.ScaleHeight(8); + int bottomMargin = UI.ScaleHeight(8); + int insetHeight = ClientSize.Height - topMargin - bottomMargin; + + int hMargin = UI.ScaleWidth(6); + int vMargin = UI.ScaleHeight(6); + + int minButtonWidth = UI.ScaleWidth(80); + + this.cancelButton.Width = minButtonWidth; + this.cancelButton.PerformLayout(); + this.cancelButton.Location = new Point( + ClientSize.Width - rightMargin - this.cancelButton.Width, + ClientSize.Height - bottomMargin - this.cancelButton.Height); + + this.okButton.Width = minButtonWidth; + this.okButton.PerformLayout(); + this.okButton.Location = new Point( + this.cancelButton.Left - hMargin - this.okButton.Width, + ClientSize.Height - bottomMargin - this.okButton.Height); + + this.etchedLine.Size = this.etchedLine.GetPreferredSize(new Size(insetWidth, 0)); + this.etchedLine.Location = new Point( + leftMargin, + Math.Min(this.okButton.Top, this.cancelButton.Top) - vMargin - this.etchedLine.Height); + + // Commenting out this line of code, along with the others marked //2 and //3, fixes the + // problem whereby the trackbar was always drawing a focus rectangle. However, if we enable + // a resizable dialog, commenting out these lines results in some fidgety looking drawing. + bool paintingSuspended = false; + if (IsHandleCreated) + { + UI.SuspendControlPainting(this.configUIPanel); //1 + paintingSuspended = true; + } + + this.configUIPanel.SuspendLayout(); + + int configUIWidth = insetWidth; + this.configUIPanel.Bounds = new Rectangle( + leftMargin, + topMargin, + insetWidth, + ClientSize.Height - topMargin - (ClientSize.Height - this.etchedLine.Top) - vMargin); + + Point autoScrollPos = this.configUIPanel.AutoScrollPosition; + this.configUIPanel.AutoScroll = false; + + int propertyWidth1 = configUIWidth; + int propertyWidth2 = configUIWidth - SystemInformation.VerticalScrollBarWidth - hMargin; + + int propertyWidth; + + if (this.configUIControl.Width == propertyWidth1) + { + propertyWidth = propertyWidth1; + } + else if (this.configUIControl.Height <= this.configUIPanel.ClientRectangle.Height) + { + propertyWidth = propertyWidth1; + } + else + { + propertyWidth = propertyWidth2; + } + + int tries = 3; + + while (tries > 0) + { + this.configUIControl.SuspendLayout(); + this.configUIControl.Location = this.configUIPanel.AutoScrollPosition; + this.configUIControl.Width = propertyWidth; + this.configUIControl.ResumeLayout(false); + this.configUIControl.PerformLayout(); + + if (this.configUIControl.Height > this.configUIPanel.ClientRectangle.Height) + { + propertyWidth = propertyWidth2; + this.configUIPanel.AutoScroll = true; + --tries; + } + else if (this.configUIControl.Height <= this.configUIPanel.ClientRectangle.Height) + { + propertyWidth = propertyWidth1; + this.configUIPanel.AutoScroll = false; + --tries; + } + else + { + break; + } + } + + this.configUIPanel.ResumeLayout(false); + this.configUIPanel.PerformLayout(); + + SendOrPostCallback finishPainting = new SendOrPostCallback((s) => + { + try + { + if (IsHandleCreated && !IsDisposed) + { + this.configUIControl.Location = new Point(0, 0); + this.configUIPanel.AutoScrollPosition = new Point(-autoScrollPos.X, -autoScrollPos.Y); + + if (paintingSuspended) + { + UI.ResumeControlPainting(this.configUIPanel); //2 + } + + Refresh(); + } + } + + catch (Exception) + { + } + }); + + SynchronizationContext.Current.Post(finishPainting, null); + + Size idealClientSize = new Size( + ClientSize.Width, + ClientSize.Height + this.configUIControl.Height - this.configUIPanel.Height); + + return idealClientSize; + } + + internal override void OnBeforeConstructor(object context) + { + this.properties = ((PropertyCollection)context).Clone(); + } + + public PropertyBasedEffectConfigDialog(PropertyCollection propertyCollection, ControlInfo configUI, PropertyCollection windowProperties) + : base(propertyCollection) + { + this.windowProperties = windowProperties.Clone(); + this.configUI = (ControlInfo)configUI.Clone(); + + // Make sure that the properties in props and configUI are not the same objects + foreach (Property property in propertyCollection) + { + PropertyControlInfo pci = this.configUI.FindControlForPropertyName(property.Name); + + if (pci != null && object.ReferenceEquals(property, pci.Property)) + { + throw new ArgumentException("Property references in propertyCollection must not be the same as those in configUI"); + } + } + + SuspendLayout(); + + this.okButton = new Button(); + this.cancelButton = new Button(); + this.cancelButton.Name = "cancelButton"; + this.configUIPanel = new Panel(); + this.configUIControl = (Control)this.configUI.CreateConcreteControl(this); + this.configUIControl.Location = new Point(0, 0); + + this.configUIPanel.SuspendLayout(); + this.configUIControl.SuspendLayout(); + + this.okButton.Name = "okButton"; + this.okButton.AutoSize = true; + this.okButton.Click += OkButton_Click; + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.okButton.FlatStyle = FlatStyle.System; + + this.cancelButton.AutoSize = true; + this.cancelButton.Click += CancelButton_Click; + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.cancelButton.FlatStyle = FlatStyle.System; + + this.configUIPanel.Name = "configUIPanel"; + this.configUIPanel.TabStop = false; + this.configUIPanel.Controls.Add(this.configUIControl); + + this.configUIControl.Name = "configUIControl"; + + this.etchedLine = new EtchedLine(); + this.etchedLine.Name = "etchedLine"; + + Controls.AddRange( + new Control[] + { + this.okButton, + this.cancelButton, + this.etchedLine, + this.configUIPanel + }); + + int tabIndex = 0; + + this.configUIControl.TabIndex = tabIndex; + ++tabIndex; + + // Set up data binding + foreach (Property property in this.properties) + { + PropertyControlInfo pci = this.configUI.FindControlForPropertyName(property.Name); + + if (pci == null) + { + throw new InvalidOperationException("Every property must have a control associated with it"); + } + else + { + Property controlsProperty = pci.Property; + + // ASSUMPTION: We assume that the concrete WinForms Control holds a reference to + // the same Property instance as the ControlInfo it was created from. + + controlsProperty.ValueChanged += ControlsProperty_ValueChanged; + } + } + + this.okButton.TabIndex = tabIndex; + ++tabIndex; + + this.cancelButton.TabIndex = tabIndex; + ++tabIndex; + + AcceptButton = this.okButton; + CancelButton = this.cancelButton; + + bool isSizable = (bool)this.windowProperties[ControlInfoPropertyNames.WindowIsSizable].Value; + FormBorderStyle = isSizable ? FormBorderStyle.Sizable : FormBorderStyle.FixedDialog; + + Text = (string)this.windowProperties[ControlInfoPropertyNames.WindowTitle].Value; + + ClientSize = new Size(UI.ScaleWidth(defaultClientWidth96Dpi), UI.ScaleHeight(defaultClientHeight96Dpi)); + + this.configUIControl.ResumeLayout(false); + this.configUIPanel.ResumeLayout(false); + + ResumeLayout(false); + PerformLayout(); + } + + private void ControlsProperty_ValueChanged(object sender, EventArgs e) + { + Property controlsProperty = (Property)sender; + Property property = this.properties[controlsProperty.Name]; + + if (!property.Value.Equals(controlsProperty.Value)) + { + if (property.ReadOnly) + { + property.ReadOnly = false; + property.Value = controlsProperty.Value; + property.ReadOnly = true; + } + else + { + property.Value = controlsProperty.Value; + } + } + + FinishTokenUpdate(); + } + + private void CancelButton_Click(object sender, EventArgs e) + { + Close(); + } + + private void OkButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.OK; + Close(); + } + } +} diff --git a/src/Effects/PropertyBasedEffectConfigToken.cs b/src/Effects/PropertyBasedEffectConfigToken.cs new file mode 100644 index 0000000..0e6668b --- /dev/null +++ b/src/Effects/PropertyBasedEffectConfigToken.cs @@ -0,0 +1,96 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace PaintDotNet.Effects +{ + public sealed class PropertyBasedEffectConfigToken + : EffectConfigToken + { + private PropertyCollection properties; + + public PropertyCollection Properties + { + get + { + return this.properties; + } + } + + public IEnumerable PropertyNames + { + get + { + return this.properties.PropertyNames; + } + } + + public Property GetProperty(object propertyName) + { + return this.properties[propertyName]; + } + + public T GetProperty(object propertyName) + where T : Property + { + return (T)this.properties[propertyName]; + } + + public bool SetPropertyValue(object propertyName, object newValue) + { + try + { + Property property = this.properties[propertyName]; + property.Value = newValue; + } + + catch (Exception ex) + { + if (ex is KeyNotFoundException || + ex is ReadOnlyException) + { + return false; + } + else + { + throw; + } + } + + return true; + } + + public PropertyBasedEffectConfigToken(PropertyCollection propertyCollection) + { + Initialize(propertyCollection); + } + + private PropertyBasedEffectConfigToken(PropertyBasedEffectConfigToken cloneMe) + : base(cloneMe) + { + Initialize(cloneMe.properties); + } + + private void Initialize(PropertyCollection propertyCollection) + { + this.properties = propertyCollection.Clone(); + } + + public override object Clone() + { + return new PropertyBasedEffectConfigToken(this); + } + } +} diff --git a/src/Effects/RadialBlurEffect.cs b/src/Effects/RadialBlurEffect.cs new file mode 100644 index 0000000..6855c44 --- /dev/null +++ b/src/Effects/RadialBlurEffect.cs @@ -0,0 +1,215 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class RadialBlurEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("RadialBlurEffect.Name"); + } + } + + public RadialBlurEffect() + : base(StaticName, + PdnResources.GetImageResource("Icons.RadialBlurEffect.png").Reference, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Angle = 0, + Offset = 1, + Quality = 2 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Angle, 2, 0, 360)); + + props.Add(new DoubleVectorProperty( + PropertyNames.Offset, + Pair.Create(0.0, 0.0), + Pair.Create(-2.0, -2.0), + Pair.Create(+2.0, +2.0))); + + props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("RadialBlurEffect.ConfigDialog.RadialLabel")); + configUI.FindControlForPropertyName(PropertyNames.Angle).ControlType.Value = PropertyControlType.AngleChooser; + + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("RadialBlurEffect.ConfigDialog.OffsetLabel")); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeX, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeX, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementX, 0.01); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeY, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeY, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementY, 0.01); + + Surface sourceSurface = this.EnvironmentParameters.SourceSurface; + Bitmap bitmap = sourceSurface.CreateAliasedBitmap(); + ImageResource imageResource = ImageResource.FromImage(bitmap); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.StaticImageUnderlay, imageResource); + + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("RadialBlurEffect.ConfigDialog.QualityLabel")); + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.Description, PdnResources.GetString("RadialBlurEffect.ConfigDialog.QualityDescription")); + + return configUI; + } + + private static void Rotate(ref int fx, ref int fy, int fr) + { + int cx = fx; + int cy = fy; + + //sin(x) ~~ x + //cos(x)~~ 1 - x^2/2 + fx = cx - ((cy >> 8) * fr >> 8) - ((cx >> 14) * (fr * fr >> 11) >> 8); + fy = cy + ((cx >> 8) * fr >> 8) - ((cy >> 14) * (fr * fr >> 11) >> 8); + } + + private double angle; + private double offsetX; + private double offsetY; + private int quality; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + this.offsetX = newToken.GetProperty(PropertyNames.Offset).ValueX; + this.offsetY = newToken.GetProperty(PropertyNames.Offset).ValueY; + + this.quality = newToken.GetProperty(PropertyNames.Quality).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface src = SrcArgs.Surface; + Surface dst = DstArgs.Surface; + int w = dst.Width; + int h = dst.Height; + int fcx = (w << 15) + (int)(this.offsetX * (w << 15)); + int fcy = (h << 15) + (int)(this.offsetY * (h << 15)); + + int n = (this.quality * this.quality) * (30 + this.quality * this.quality); + + int fr = (int)(this.angle * Math.PI * 65536.0 / 181.0); + + for (int r = startIndex; r < startIndex + length; ++r) + { + Rectangle rect = rois[r]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + ColorBgra* srcPtr = src.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + int fx = (x << 16) - fcx; + int fy = (y << 16) - fcy; + + int fsr = fr / n; + + int sr = 0; + int sg = 0; + int sb = 0; + int sa = 0; + int sc = 0; + + sr += srcPtr->R * srcPtr->A; + sg += srcPtr->G * srcPtr->A; + sb += srcPtr->B * srcPtr->A; + sa += srcPtr->A; + ++sc; + + int ox1 = fx; + int ox2 = fx; + int oy1 = fy; + int oy2 = fy; + + for (int i = 0; i < n; ++i) + { + Rotate(ref ox1, ref oy1, fsr); + Rotate(ref ox2, ref oy2, -fsr); + + int u1 = ox1 + fcx + 32768 >> 16; + int v1 = oy1 + fcy + 32768 >> 16; + + if (u1 > 0 && v1 > 0 && u1 < w && v1 < h) + { + ColorBgra *sample = src.GetPointAddressUnchecked(u1, v1); + + sr += sample->R * sample->A; + sg += sample->G * sample->A; + sb += sample->B * sample->A; + sa += sample->A; + ++sc; + } + + int u2 = ox2 + fcx + 32768 >> 16; + int v2 = oy2 + fcy + 32768 >> 16; + + if (u2 > 0 && v2 > 0 && u2 < w && v2 < h) + { + ColorBgra* sample = src.GetPointAddressUnchecked(u2, v2); + + sr += sample->R * sample->A; + sg += sample->G * sample->A; + sb += sample->B * sample->A; + sa += sample->A; + ++sc; + } + } + + if (sa > 0) + { + *dstPtr = ColorBgra.FromBgra( + Utility.ClampToByte(sb / sa), + Utility.ClampToByte(sg / sa), + Utility.ClampToByte(sr / sa), + Utility.ClampToByte(sa / sc)); + } + else + { + dstPtr->Bgra = 0; + } + + ++dstPtr; + ++srcPtr; + } + } + } + } + } +} diff --git a/src/Effects/RedEyeRemoveEffect.cs b/src/Effects/RedEyeRemoveEffect.cs new file mode 100644 index 0000000..e9d59ce --- /dev/null +++ b/src/Effects/RedEyeRemoveEffect.cs @@ -0,0 +1,76 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class RedEyeRemoveEffect + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + Tolerance = 0, + Saturation = 1 + } + + private int tolerance; + private int saturation; + private PixelOp redEyeOp; + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Tolerance, 70, 0, 100)); + props.Add(new Int32Property(PropertyNames.Saturation, 90, 0, 100)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Tolerance, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("RedEyeRemoveEffect.ConfigDialog.Amount1Label")); + configUI.SetPropertyControlValue(PropertyNames.Saturation, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("RedEyeRemoveEffect.ConfigDialog.Amount2Label")); + configUI.SetPropertyControlValue(PropertyNames.Saturation, ControlInfoPropertyNames.Description, PdnResources.GetString("RedEyeRemoveEffectDialog.UsageHintLabel.Text")); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.tolerance = newToken.GetProperty(PropertyNames.Tolerance).Value; + this.saturation = newToken.GetProperty(PropertyNames.Saturation).Value; + + this.redEyeOp = new UnaryPixelOps.RedEyeRemove(this.tolerance, this.saturation); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + this.redEyeOp.Apply(DstArgs.Surface, SrcArgs.Surface, rois, startIndex, length); + } + + public RedEyeRemoveEffect() + : base(PdnResources.GetString("RedEyeRemoveEffect.Name"), + PdnResources.GetImageResource("Icons.RedEyeRemoveEffect.png").Reference, + SubmenuNames.Photo, + EffectFlags.Configurable) + { + } + } +} \ No newline at end of file diff --git a/src/Effects/ReduceNoiseEffect.cs b/src/Effects/ReduceNoiseEffect.cs new file mode 100644 index 0000000..06e4486 --- /dev/null +++ b/src/Effects/ReduceNoiseEffect.cs @@ -0,0 +1,129 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.PropertySystem; +using PaintDotNet.IndirectUI; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class ReduceNoiseEffect + : LocalHistogramEffect + { + private int radius; + private double strength; + + public ReduceNoiseEffect() + : base(StaticName, StaticImage, SubmenuNames.Noise, EffectFlags.Configurable) + { + } + + public static string StaticName + { + get + { + return PdnResources.GetString("ReduceNoiseEffect.Name"); + } + } + + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.ReduceNoiseEffectIcon.png").Reference; + } + } + + public enum PropertyNames + { + Radius = 0, + Strength = 1 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Radius, 10, 0, 200)); + props.Add(new DoubleProperty(PropertyNames.Strength, 0.4, 0, 1)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("ReduceNoise.Radius.DisplayName")); + configUI.SetPropertyControlValue(PropertyNames.Strength, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("ReduceNoise.Strength.DisplayName")); + + PropertyControlInfo strengthControlInfo = configUI.FindControlForPropertyName(PropertyNames.Strength); + configUI.SetPropertyControlValue(PropertyNames.Strength, ControlInfoPropertyNames.UpDownIncrement, 0.01); + configUI.SetPropertyControlValue(PropertyNames.Strength, ControlInfoPropertyNames.SliderSmallChange, 0.01); + configUI.SetPropertyControlValue(PropertyNames.Strength, ControlInfoPropertyNames.SliderLargeChange, 0.1); + + return configUI; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.radius = newToken.GetProperty(PropertyNames.Radius).Value; + this.strength = -0.2 * newToken.GetProperty(PropertyNames.Strength).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + for (int i = startIndex; i < startIndex + length; ++i) + { + RenderRect(radius, this.SrcArgs.Surface, this.DstArgs.Surface, renderRects[i]); + } + } + + public override unsafe ColorBgra Apply(ColorBgra color, int area, int* hb, int* hg, int* hr, int* ha) + { + ColorBgra normalized = GetPercentileOfColor(color, area, hb, hg, hr, ha); + double lerp = strength * (1 - 0.75 * color.GetIntensity()); + + return ColorBgra.Lerp(color, normalized, lerp); + } + + private static unsafe ColorBgra GetPercentileOfColor(ColorBgra color, int area, int* hb, int* hg, int* hr, int* ha) + { + int rc = 0; + int gc = 0; + int bc = 0; + + for (int i = 0; i < color.R; ++i) + { + rc += hr[i]; + } + + for (int i = 0; i < color.G; ++i) + { + gc += hg[i]; + } + + for (int i = 0; i < color.B; ++i) + { + bc += hb[i]; + } + + rc = (rc * 255) / area; + gc = (gc * 255) / area; + bc = (bc * 255) / area; + + return ColorBgra.FromBgr((byte)bc, (byte)gc, (byte)rc); + } + } +} diff --git a/src/Effects/ReliefEffect.cs b/src/Effects/ReliefEffect.cs new file mode 100644 index 0000000..7a03943 --- /dev/null +++ b/src/Effects/ReliefEffect.cs @@ -0,0 +1,96 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using PaintDotNet.Effects; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class ReliefEffect + : ColorDifferenceEffect + { + public ReliefEffect() + : base(PdnResources.GetString("ReliefEffect.Name"), + PdnResources.GetImageResource("Icons.ReliefEffect.png").Reference, + SubmenuNames.Stylize, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Angle = 0 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Angle, 45.0, -180.0, +180.0)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Angle, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("AngleChooserConfigDialog.AngleHeader.Text")); + configUI.SetPropertyControlType(PropertyNames.Angle, PropertyControlType.AngleChooser); + + return configUI; + } + + private double angle; + private double[][] weights; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.angle = newToken.GetProperty(PropertyNames.Angle).Value; + + this.weights = new double[3][]; + for (int i = 0; i < this.weights.Length; ++i) + { + this.weights[i] = new double[3]; + } + + // adjust and convert angle to radians + double r = (double)this.angle * 2.0 * Math.PI / 360.0; + + // angle delta for each weight + double dr = Math.PI / 4.0; + + // for r = 0 this builds an Relief filter pointing straight left + this.weights[0][0] = Math.Cos(r + dr); + this.weights[0][1] = Math.Cos(r + 2.0 * dr); + this.weights[0][2] = Math.Cos(r + 3.0 * dr); + + this.weights[1][0] = Math.Cos(r); + this.weights[1][1] = 1; + this.weights[1][2] = Math.Cos(r + 4.0 * dr); + + this.weights[2][0] = Math.Cos(r - dr); + this.weights[2][1] = Math.Cos(r - 2.0 * dr); + this.weights[2][2] = Math.Cos(r - 3.0 * dr); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + base.RenderColorDifferenceEffect(this.weights, DstArgs, SrcArgs, rois, startIndex, length); + } + + } +} diff --git a/src/Effects/RollControl.cs b/src/Effects/RollControl.cs new file mode 100644 index 0000000..a30f703 --- /dev/null +++ b/src/Effects/RollControl.cs @@ -0,0 +1,540 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public class RollControl + : System.Windows.Forms.UserControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + private bool tracking = false; + private Point lastMouseXY; + private Bitmap renderSurface = null; // used for double-buffering + + public event EventHandler ValueChanged; + protected virtual void OnValueChanged() + { + if (ValueChanged != null) + { + ValueChanged(this, EventArgs.Empty); + } + } + + public double angle; + public double Angle + { + get + { + return angle; + } + + set + { + double v = Math.IEEERemainder(value, 360); + + if (angle != v) + { + angle = v; + OnValueChanged(); + Invalidate(); + } + } + } + + + //Direction at which to roll the image, in degrees + protected double rollDirection; + public double RollDirection + { + get + { + return rollDirection; + } + + set + { + double v = Math.IEEERemainder(value, 360); + + if (rollDirection != v) + { + rollDirection = v; + OnValueChanged(); + Invalidate(); + } + } + } + + //Amount to roll the image, in degrees + protected double rollAmount; + public double RollAmount + { + get + { + return rollAmount; + } + + set + { + double v = Math.IEEERemainder(value, 360); + + if (v >= 90) + { + return; + } + if (rollAmount != v) + { + rollAmount = v; + OnValueChanged(); + Invalidate(); + } + } + } + + private void DrawToGraphics(Graphics g) + { + g.Clear(this.BackColor); + +#if DEBUG + string debug = ""; + + try + { +#endif + g.SmoothingMode = SmoothingMode.AntiAlias; + + // Calculations + Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2); + int diameter = Math.Min(ourRect.Width, ourRect.Height); + Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2)); + float radius1 = ((float)diameter / 3); + float radius2 = ((float)diameter / 2); + double theta = -((double)angle * 2 * Math.PI) / 360.0; + float cos = (float)Math.Cos(theta); + float sin = (float)Math.Sin(theta); + float rx = (float)(rollAmount * Math.Cos(rollDirection * Math.PI / 180) / 90); + float ry = (float)(rollAmount * Math.Sin(rollDirection * Math.PI / 180) / 90); + + float phi = (float)(rx / (ry * ry < 0.99 ? Math.Sqrt(1 - ry * ry) : 1)); + float rho = (float)(ry / (rx * rx < 0.99 ? Math.Sqrt(1 - rx * rx) : 1)); + + // Globe + g.TranslateTransform(center.X, center.Y); + + int divs = 4; + double angleRadians = angle * Math.PI / 180; + + Pen darkPen; + darkPen = onSphere ? Pens.Blue : Pens.Black; + + Pen lightPen; + lightPen = Pens.Gray; + + for (int i = -divs; i < divs; i++) + { + double u = -angleRadians + i * Math.PI / divs; + double v = -Math.PI / 2; + double ox = Math.Cos(u) * Math.Cos(v); + double oy = Math.Sin(u) * Math.Cos(v); + double oz = Math.Sin(v); + double x; + double y; + double z; + + for (int j = -divs * 4; j <= 0; j++) + { + v = j * Math.PI / (divs * 8); + Pen p = (i % 2 == 0) ? lightPen : darkPen; + x = Math.Cos(u) * Math.Cos(v); + y = Math.Sin(u) * Math.Cos(v); + z = Math.Sin(v); + Draw3DLine(g, p, rx, -ry, radius1, ox, oy, oz, x, y, z); + ox = x; + oy = y; + oz = z; + } + } + + for (int j = -divs / 2; j <= 0; j++) + { + double v = j * Math.PI / divs; + double u = -angleRadians + -Math.PI; + double ox = Math.Cos(u) * Math.Cos(v); + double oy = Math.Sin(u) * Math.Cos(v); + double oz = Math.Sin(v); + double x; + double y; + double z; + + for (int i = -divs * 6; i <= divs * 6; i++) + { + u = -angleRadians + i * Math.PI / (divs * 6); + double cosv = Math.Cos(v); + double sinv = Math.Sin(v); + Pen p = (j == 0) ? lightPen : darkPen; + + x = Math.Cos(u) * Math.Cos(v); + y = Math.Sin(u) * Math.Cos(v); + z = Math.Sin(v); + Draw3DLine(g, p, rx, -ry, radius1, ox, oy, oz, x, y, z); + ox = x; + oy = y; + oz = z; + } + } + + g.ResetTransform(); + + // Ring + + // Reference Theta line + g.DrawLine(SystemPens.ControlDark, + center.X + radius1, center.Y, + center.X + radius2, center.Y); + + // Draw Theta-chooser ring + Pen outerRingDark = (Pen)SystemPens.ControlDarkDark.Clone(); + outerRingDark.Width = 2.0f; + + Pen outerRingLight = (Pen)SystemPens.ControlLightLight.Clone(); + outerRingLight.Width = 2.0f; + + g.DrawEllipse(outerRingDark, Utility.RectangleFromCenter(new Point(center.X - 1, center.Y - 1), radius1)); + g.DrawEllipse(outerRingDark, Utility.RectangleFromCenter(new Point(center.X - 1, center.Y - 1), radius2)); + g.DrawEllipse(outerRingLight, Utility.RectangleFromCenter(center, radius2)); + g.DrawEllipse(outerRingLight, Utility.RectangleFromCenter(center, radius1)); + + outerRingDark.Dispose(); + outerRingLight.Dispose(); + + // Draw actual theta line + Pen thetaLinePen; + + if (mouseEntered && !onSphere) + { + thetaLinePen = Pens.Blue; + } + else + { + thetaLinePen = Pens.Black; + } + + Pen useMePen = (Pen)thetaLinePen.Clone(); + useMePen.Width = 3.0f; + + g.DrawLine(useMePen, + center.X + radius1 * cos, center.Y + radius1 * sin, + center.X + radius2 * cos, center.Y + radius2 * sin); + + useMePen.Dispose(); +#if DEBUG + } + + catch + { + g.DrawString(debug, new Font("Courier New", 10), SystemBrushes.WindowText, 0, 0); + } +#endif + } + + void Draw3DLine( + Graphics g, + Pen p, + double rx, + double ry, + double scale, + double xs, + double ys, + double zs, + double xe, + double ye, + double ze) + { + double dist = Math.Sqrt(rx * rx + ry * ry); + + if (dist != 0) + { + double rAngle = Math.Atan2(ry, rx); + double sinAngle = Math.Sin(rAngle); + double cosAngle = Math.Cos(rAngle); + + Transform(sinAngle, cosAngle, dist, Math.Cos(Math.Asin(dist)), ref xs, ref ys, ref zs); + Transform(sinAngle, cosAngle, dist, Math.Cos(Math.Asin(dist)), ref xe, ref ye, ref ze); + } + + xs *= scale; + xe *= scale; + ys *= scale; + ye *= scale; + + if (ze < 0.03 && zs < 0.03) + { + g.DrawLine(p, (float)xs, (float)ys, (float)xe, (float)ye); + } + } + + void Transform(double sinangle, double cosangle, double sinamt, double cosamt, ref double x, ref double y, ref double z) + { + double ox = x; + double oy = y; + double oz = z; + + x = cosangle * ox - sinangle * oy; + y = sinangle * ox + cosangle * oy; + + ox = x; + oy = y; + + x = cosamt * ox - sinamt * oz; + z = sinamt * ox + cosamt * oz; + + ox = x; + + x = cosangle * ox + sinangle * oy; + y = -sinangle * ox + cosangle * oy; + } + + private void CheckRenderSurface() + { + if (renderSurface != null && renderSurface.Size != Size) + { + renderSurface.Dispose(); + renderSurface = null; + } + + if (renderSurface == null) + { + renderSurface = new Bitmap(Width, Height); + + using (Graphics g = Graphics.FromImage(renderSurface)) + { + DrawToGraphics(g); + } + } + } + + private void DoPaint(Graphics g) + { + CheckRenderSurface(); + g.DrawImage(renderSurface, ClientRectangle, ClientRectangle, GraphicsUnit.Pixel); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint (e); + renderSurface = null; + DoPaint(e.Graphics); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + DoPaint(pevent.Graphics); + } + + private bool onSphere = false; + private double startAngle; + private double startTheta; + private PointF startRoll; + private Point startPt; + private bool mouseEntered = false; + + protected override void OnMouseEnter(EventArgs e) + { + mouseEntered = true; + onSphere = IsMouseOnSphere(Control.MousePosition.X, Control.MousePosition.Y); + Invalidate(); + base.OnMouseEnter(e); + } + + protected override void OnMouseLeave(EventArgs e) + { + mouseEntered = false; + onSphere = IsMouseOnSphere(Control.MousePosition.X, Control.MousePosition.Y); + Invalidate(); + base.OnMouseLeave(e); + } + + private bool IsMouseOnSphere(int x, int y) + { + Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2); + int diameter = Math.Min(ourRect.Width, ourRect.Height); + float radius1 = ((float)diameter / 3); + Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2)); + Point dist = new Point(x - center.X, y - center.Y); + bool returnVal = (Math.Sqrt(dist.X * dist.X + dist.Y * dist.Y) <= radius1); + return returnVal; + } + + protected override void OnMouseDown(MouseEventArgs e) + { + startPt = new Point(e.X, e.Y); + base.OnMouseDown (e); + + tracking = true; + + onSphere = IsMouseOnSphere(e.X, e.Y); + startAngle = angle; + startTheta = rollDirection; + startRoll = new PointF( + (float)(rollAmount * Math.Cos(rollDirection * Math.PI / 180)), + (float)(rollAmount * Math.Sin(rollDirection * Math.PI / 180))); + + OnMouseMove(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + tracking = false; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (!tracking) + { + onSphere = IsMouseOnSphere(e.X, e.Y); + Invalidate(); + } + + Point preLastMouseXY = new Point(e.X, e.Y); + bool moved = (preLastMouseXY != lastMouseXY); + + lastMouseXY = preLastMouseXY; + + if (tracking && moved) + { + Rectangle ourRect = Rectangle.Inflate(ClientRectangle, -2, -2); + int diameter = Math.Min(ourRect.Width, ourRect.Height); + Point center = new Point(ourRect.X + (diameter / 2), ourRect.Y + (diameter / 2)); + + if (onSphere) + { + int dx = e.X - startPt.X; + int dy = e.Y - startPt.Y; + float mx = startRoll.X / 89.9f + 3.0f * dx / (diameter - 4); + float my = startRoll.Y / 89.9f + 3.0f * dy / (diameter - 4); + float dist = (float)Math.Sqrt(mx * mx + my * my); + float rad = (float)((dist > 1) ? 1 : dist); + + if (dist == 0.0f) + { + mx = 0; + my = 0; + } + else + { + mx = mx * rad / dist; + my = my * rad / dist; + } + + if (0 != (ModifierKeys & Keys.Shift)) + { + if (mx * mx > my * my) + { + my = 0; + } + else + { + mx = 0; + } + } + + this.rollDirection = 180 * Math.Atan2(my, mx) / Math.PI; + this.rollAmount = 89.94 * Math.Sqrt(mx * mx + my * my); + OnValueChanged(); + Update(); + } + else + { + int dx = e.X - center.X; + int dy = e.Y - center.Y; + double theta = Math.Atan2(-dy, dx); + + if (0 != (ModifierKeys & Keys.Shift)) + { + this.Angle = Math.Round(4 * theta / Math.PI) * 45; + } + else + { + this.Angle = (theta * 360) / (2 * Math.PI); + } + + Update(); + } + } + } + + protected override void OnClick(EventArgs e) + { + base.OnClick (e); + tracking = true; + OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, lastMouseXY.X, lastMouseXY.Y, 0)); + tracking = false; + } + + protected override void OnDoubleClick(EventArgs e) + { + base.OnDoubleClick (e); + tracking = true; + OnMouseMove(new MouseEventArgs(MouseButtons.Left, 1, lastMouseXY.X, lastMouseXY.Y, 0)); + tracking = false; + rollAmount = 0; + rollDirection = 0; + } + + public RollControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + this.ResizeRedraw = true; + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // RollControl + // + this.Name = "RollControl"; + this.Size = new System.Drawing.Size(168, 144); + + } + #endregion + } +} diff --git a/src/Effects/RollControl.resx b/src/Effects/RollControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/RotateZoomEffect.cs b/src/Effects/RotateZoomEffect.cs new file mode 100644 index 0000000..07d5f4d --- /dev/null +++ b/src/Effects/RotateZoomEffect.cs @@ -0,0 +1,270 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.DoNotDisplay)] // we have a menu item that manually places this in the Layers menu + public sealed class RotateZoomEffect + : Effect + { + private static readonly ColorBgra seeThroughColor = ColorBgra.FromBgra(255, 255, 255, 0); + + public static string StaticName + { + get + { + return PdnResources.GetString("RotateZoomEffect.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.RotateZoomIcon.png"); + } + } + + public static Keys StaticShortcutKeys + { + get + { + return Keys.Control | Keys.Shift | Keys.Z; + } + } + + public override EffectConfigDialog CreateConfigDialog() + { + return new RotateZoomEffectConfigDialog(); + } + + public unsafe override void Render(EffectConfigToken parameters, RenderArgs dstArgs, RenderArgs srcArgs, Rectangle[] rois, int startIndex, int length) + { + RotateZoomEffectConfigToken token = (RotateZoomEffectConfigToken)parameters; + RotateZoomEffectConfigToken.RzInfo rzInfo = token.ComputedOnce; + Rectangle bounds = this.EnvironmentParameters.GetSelection(dstArgs.Bounds).GetBoundsInt(); + bounds.Intersect(dstArgs.Bounds); + Surface src = srcArgs.Surface; + Surface dst = dstArgs.Surface; + PdnRegion selection = this.EnvironmentParameters.GetSelection(src.Bounds); + Rectangle srcBounds = src.Bounds; + int srcMaxX = srcBounds.Width - 1; + int srcMaxY = srcBounds.Height - 1; + + float dsxdx = rzInfo.dsxdx; + float dsydx = rzInfo.dsydx; + float dszdx = rzInfo.dszdx; + float dsxdy = rzInfo.dsxdy; + float dsydy = rzInfo.dsydy; + float dszdy = rzInfo.dszdy; + float zoom = token.Zoom; + uint srcMask = token.SourceAsBackground ? 0xffffffff : 0; + + bool tile = token.Tile; + float divZ = 0.5f * (float)Math.Sqrt(dst.Width * dst.Width + dst.Height * dst.Height); + float centerX = (float)dst.Width / 2.0f; + float centerY = (float)dst.Height / 2.0f; + float tx = (token.Offset.X) * dst.Width / 2.0f; + float ty = (token.Offset.Y) * dst.Height / 2.0f; + + uint tilingMask = tile ? 0xffffffff : 0; + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle rect = rois[i]; + + float cx = rzInfo.startX; + float cy = rzInfo.startY; + float cz = rzInfo.startZ; + + float mcl = ((rect.Left - tx) - dst.Width / 2.0f); + cx += dsxdx * mcl; + cy += dsydx * mcl; + cz += dszdx * mcl; + + float mct = ((rect.Top - ty) - dst.Height / 2.0f); + cx += dsxdy * mct; + cy += dsydy * mct; + cz += dszdy * mct; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra *dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + ColorBgra *srcPtr = src.GetPointAddressUnchecked(rect.Left, y); + + float rx = cx; + float ry = cy; + float rz = cz; + + for (int x = rect.Left; x < rect.Right; ++x) + { + if (rz > -divZ) + { + float div = divZ / (zoom * (divZ + rz)); + float u = (rx * div) + centerX; + float v = (ry * div) + centerY; + + if (tile || (u >= -1 && v >= -1 && u <= srcBounds.Width && v <= srcBounds.Height)) + { + unchecked + { + int iu = (int)Math.Floor(u); + uint sxfrac = (uint)(256 * (u - (float)iu)); + uint sxfracinv = 256 - sxfrac; + + int iv = (int)Math.Floor(v); + uint syfrac = (uint)(256 * (v - (float)iv)); + uint syfracinv = 256 - syfrac; + + uint wul = (uint)(sxfracinv * syfracinv); + uint wur = (uint)(sxfrac * syfracinv); + uint wll = (uint)(sxfracinv * syfrac); + uint wlr = (uint)(sxfrac * syfrac); + + uint inBoundsMaskLeft = tilingMask; + uint inBoundsMaskTop = tilingMask; + uint inBoundsMaskRight = tilingMask; + uint inBoundsMaskBottom = tilingMask; + + int sx = iu; + if (sx < 0) + { + sx = srcMaxX + ((sx + 1) % srcBounds.Width); + } + else if (sx > srcMaxX) + { + sx = sx % srcBounds.Width; + } + else + { + inBoundsMaskLeft = 0xffffffff; + } + + int sy = iv; + if (sy < 0) + { + sy = srcMaxY + ((sy + 1) % srcBounds.Height); + } + else if (sy > srcMaxY) + { + sy = sy % srcBounds.Height; + } + else + { + inBoundsMaskTop = 0xffffffff; + } + + int sleft = sx; + int sright; + + if (sleft == srcMaxX) + { + sright = 0; + inBoundsMaskRight = (iu == -1) ? 0xffffffff : tilingMask; + } + else + { + sright = sleft + 1; + inBoundsMaskRight = inBoundsMaskLeft & 0xffffffff; + } + + int stop = sy; + int sbottom; + + if (stop == srcMaxY) + { + sbottom = 0; + inBoundsMaskBottom = (iv == -1) ? 0xffffffff : tilingMask; + } + else + { + sbottom = stop + 1; + inBoundsMaskBottom = inBoundsMaskTop & 0xffffffff; + } + + uint maskUL = inBoundsMaskLeft & inBoundsMaskTop; + ColorBgra cul = ColorBgra.FromUInt32(src.GetPointUnchecked(sleft, stop).Bgra & maskUL); + + uint maskUR = inBoundsMaskRight & inBoundsMaskTop; + ColorBgra cur = ColorBgra.FromUInt32(src.GetPointUnchecked(sright, stop).Bgra & maskUR); + + uint maskLL = inBoundsMaskLeft & inBoundsMaskBottom; + ColorBgra cll = ColorBgra.FromUInt32(src.GetPointUnchecked(sleft, sbottom).Bgra & maskLL); + + uint maskLR = inBoundsMaskRight & inBoundsMaskBottom; + ColorBgra clr = ColorBgra.FromUInt32(src.GetPointUnchecked(sright, sbottom).Bgra & maskLR); + + ColorBgra c = ColorBgra.BlendColors4W16IP(cul, wul, cur, wur, cll, wll, clr, wlr); + + if (c.A == 255 || !token.SourceAsBackground) + { + dstPtr->Bgra = c.Bgra; + } + else + { + *dstPtr = PaintDotNet.UserBlendOps.NormalBlendOp.ApplyStatic(*srcPtr, c); + } + } + } + else + { + if (srcMask != 0) + { + dstPtr->Bgra = srcPtr->Bgra; + } + else + { + dstPtr->Bgra = 0; + } + } + } + else + { + if (srcMask != 0) + { + dstPtr->Bgra = srcPtr->Bgra; + } + else + { + dstPtr->Bgra = 0; + } + } + + rx += dsxdx; + ry += dsydx; + rz += dszdx; + + ++dstPtr; + ++srcPtr; + } + + cx += dsxdy; + cy += dsydy; + cz += dszdy; + } + } + } + + public RotateZoomEffect() + : base(StaticName, + StaticImage.GetCopy(), + EffectFlags.Configurable) + { + } + } +} diff --git a/src/Effects/RotateZoomEffectConfigDialog.cs b/src/Effects/RotateZoomEffectConfigDialog.cs new file mode 100644 index 0000000..607edd5 --- /dev/null +++ b/src/Effects/RotateZoomEffectConfigDialog.cs @@ -0,0 +1,760 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class RotateZoomEffectConfigDialog + : EffectConfigDialog + { + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.CheckBox keepBackgroundCheckBox; + private System.Windows.Forms.CheckBox tileSourceCheckBox; + private PaintDotNet.Effects.RollControl rollControl; + private PaintDotNet.HeaderLabel headerRoll; + private PaintDotNet.HeaderLabel headerPan; + private System.Windows.Forms.Panel panelPan; + private System.Windows.Forms.TrackBar trackBarZoom; + private PaintDotNet.Effects.PanControl panControl; + private PaintDotNet.HeaderLabel headerZoom; + private System.Windows.Forms.Label zoomLabel; + private System.Windows.Forms.Label panXLabel; + private System.Windows.Forms.Label panYLabel; + private System.Windows.Forms.Button panResetButton; + private System.Windows.Forms.NumericUpDown panXUpDown; + private System.Windows.Forms.NumericUpDown panYUpDown; + private System.Windows.Forms.Label angleLabel; + private System.Windows.Forms.NumericUpDown angleUpDown; + private System.Windows.Forms.Button zoomResetButton; + private System.Windows.Forms.Label twistAngleLabel; + private System.Windows.Forms.Label twistRadiusLabel; + private System.Windows.Forms.NumericUpDown twistAngleUpDown; + private System.Windows.Forms.NumericUpDown twistRadiusUpDown; + private System.Windows.Forms.Button rollResetButton; + private System.Windows.Forms.Button resetAllButton; + private PaintDotNet.HeaderLabel fineTuningHeader; + private PaintDotNet.HeaderLabel headerLabel1; + + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + public RotateZoomEffectConfigDialog() + { + InitializeComponent(); + + this.Icon = Utility.ImageToIcon(RotateZoomEffect.StaticImage.Reference); + this.Text = RotateZoomEffect.StaticName; + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.keepBackgroundCheckBox.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.KeepBackgroundCheckBox.Text"); + this.tileSourceCheckBox.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.TileSourceCheckBox.Text"); + this.headerPan.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.HeaderPan.Text"); + this.panXLabel.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.PanXLabel.Text"); + this.panYLabel.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.PanYLabel.Text"); + this.twistAngleLabel.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.TwistAngleLabel.Text"); + this.twistRadiusLabel.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.TwistRadiusLabel.Text"); + this.panResetButton.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.PanResetButton.Text"); + this.zoomResetButton.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.ZoomResetButton.Text"); + this.rollResetButton.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.RollResetButton.Text"); + this.resetAllButton.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.ResetAllButton.Text"); + this.headerRoll.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.HeaderRoll.Text"); + this.angleLabel.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.AngleLabel.Text"); + this.headerZoom.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.HeaderZoom.Text"); + this.fineTuningHeader.Text = PdnResources.GetString("RotateZoomEffectConfigDialog.FineTuningHeader.Text"); + } + + protected override void OnLoad(EventArgs e) + { + this.angleUpDown.Select(); + base.OnLoad(e); + } + + protected override void InitialInitToken() + { + theEffectToken = new RotateZoomEffectConfigToken(true, 0, 0, 0, 1.0f, PointF.Empty, false, false); + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + RotateZoomEffectConfigToken token = (RotateZoomEffectConfigToken)effectToken; + double r = Math.Sin(token.Tilt) * 90; + double t = -token.PreRotateZ; + + panControl.Position = token.Offset; + rollControl.Angle = (token.PostRotateZ - t) * 180 / Math.PI; + rollControl.RollDirection = 180 * t / Math.PI; + rollControl.RollAmount = r; + keepBackgroundCheckBox.Checked = token.SourceAsBackground; + tileSourceCheckBox.Checked = token.Tile; + trackBarZoom.Value = (int)Math.Round(512 + 128 * Math.Log(token.Zoom, 2.0)); + + TrackBarZoom_ValueChanged(this, EventArgs.Empty); + } + + protected override void InitTokenFromDialog() + { + RotateZoomEffectConfigToken token = (RotateZoomEffectConfigToken)theEffectToken; + double angle = rollControl.RollDirection * Math.PI / 180; + double dist = rollControl.RollAmount; + + if (double.IsNaN(angle)) + { + angle = 0; + dist = 0; + } + + token.Offset = panControl.Position; + token.PreRotateZ = (float)(angle); + token.PostRotateZ = (float)(-angle - rollControl.Angle * Math.PI / 180); + token.Tilt = (float)Math.Asin(dist / 90); + token.SourceAsBackground = keepBackgroundCheckBox.Checked; + token.Tile = tileSourceCheckBox.Checked; + token.Zoom = (float)Math.Pow(2.0, (trackBarZoom.Value - 512) / 128.0); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.keepBackgroundCheckBox = new System.Windows.Forms.CheckBox(); + this.tileSourceCheckBox = new System.Windows.Forms.CheckBox(); + this.rollControl = new PaintDotNet.Effects.RollControl(); + this.headerRoll = new PaintDotNet.HeaderLabel(); + this.headerPan = new PaintDotNet.HeaderLabel(); + this.panelPan = new System.Windows.Forms.Panel(); + this.panControl = new PaintDotNet.Effects.PanControl(); + this.headerZoom = new PaintDotNet.HeaderLabel(); + this.trackBarZoom = new System.Windows.Forms.TrackBar(); + this.zoomLabel = new System.Windows.Forms.Label(); + this.panXLabel = new System.Windows.Forms.Label(); + this.panYLabel = new System.Windows.Forms.Label(); + this.panXUpDown = new System.Windows.Forms.NumericUpDown(); + this.panYUpDown = new System.Windows.Forms.NumericUpDown(); + this.panResetButton = new System.Windows.Forms.Button(); + this.angleLabel = new System.Windows.Forms.Label(); + this.angleUpDown = new System.Windows.Forms.NumericUpDown(); + this.zoomResetButton = new System.Windows.Forms.Button(); + this.twistAngleLabel = new System.Windows.Forms.Label(); + this.twistRadiusLabel = new System.Windows.Forms.Label(); + this.twistAngleUpDown = new System.Windows.Forms.NumericUpDown(); + this.twistRadiusUpDown = new System.Windows.Forms.NumericUpDown(); + this.rollResetButton = new System.Windows.Forms.Button(); + this.resetAllButton = new System.Windows.Forms.Button(); + this.fineTuningHeader = new PaintDotNet.HeaderLabel(); + this.headerLabel1 = new PaintDotNet.HeaderLabel(); + this.panelPan.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.panXUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.panYUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.angleUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.twistAngleUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.twistRadiusUpDown)).BeginInit(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(312, 312); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(72, 23); + this.okButton.TabIndex = 26; + this.okButton.Click += new System.EventHandler(this.OkButton_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(392, 312); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(72, 23); + this.cancelButton.TabIndex = 27; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // keepBackgroundCheckBox + // + this.keepBackgroundCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.keepBackgroundCheckBox.Location = new System.Drawing.Point(9, 316); + this.keepBackgroundCheckBox.Name = "keepBackgroundCheckBox"; + this.keepBackgroundCheckBox.Width = 175; + this.keepBackgroundCheckBox.TabIndex = 24; + this.keepBackgroundCheckBox.FlatStyle = FlatStyle.System; + this.keepBackgroundCheckBox.CheckedChanged += new System.EventHandler(this.KeepBackgroundCheckBox_CheckedChanged); + // + // tileSourceCheckBox + // + this.tileSourceCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.tileSourceCheckBox.Location = new System.Drawing.Point(9, 298); + this.tileSourceCheckBox.Name = "tileSourceCheckBox"; + this.tileSourceCheckBox.Width = 175; + this.tileSourceCheckBox.TabIndex = 23; + this.tileSourceCheckBox.FlatStyle = FlatStyle.System; + this.tileSourceCheckBox.CheckedChanged += new System.EventHandler(this.TileSource_CheckedChanged); + // + // rollControl + // + this.rollControl.Angle = -70; + this.rollControl.Location = new System.Drawing.Point(16, 32); + this.rollControl.Name = "rollControl"; + this.rollControl.RollAmount = 0; + this.rollControl.RollDirection = 0; + this.rollControl.Size = new System.Drawing.Size(112, 120); + this.rollControl.TabIndex = 3; + this.rollControl.TabStop = false; + this.rollControl.ValueChanged += new System.EventHandler(this.RollControl_ValueChanged); + // + // headerRoll + // + this.headerRoll.Location = new System.Drawing.Point(8, 8); + this.headerRoll.Name = "headerRoll"; + this.headerRoll.RightMargin = 0; + this.headerRoll.Size = new System.Drawing.Size(168, 14); + this.headerRoll.TabIndex = 2; + this.headerRoll.TabStop = false; + // + // headerPan + // + this.headerPan.Location = new System.Drawing.Point(199, 8); + this.headerPan.Name = "headerPan"; + this.headerPan.RightMargin = 0; + this.headerPan.Size = new System.Drawing.Size(129, 14); + this.headerPan.TabIndex = 5; + this.headerPan.TabStop = false; + // + // panelPan + // + this.panelPan.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.panelPan.Controls.Add(this.panControl); + this.panelPan.Location = new System.Drawing.Point(200, 29); + this.panelPan.Name = "panelPan"; + this.panelPan.Size = new System.Drawing.Size(128, 120); + this.panelPan.TabIndex = 6; + // + // panControl + // + this.panControl.Dock = System.Windows.Forms.DockStyle.Fill; + this.panControl.Location = new System.Drawing.Point(0, 0); + this.panControl.Name = "panControl"; + this.panControl.Size = new System.Drawing.Size(124, 116); + this.panControl.TabIndex = 0; + this.panControl.TabStop = false; + this.panControl.PositionChanged += new System.EventHandler(this.PanControl_PositionChanged); + // + // headerZoom + // + this.headerZoom.Location = new System.Drawing.Point(352, 8); + this.headerZoom.Name = "headerZoom"; + this.headerZoom.RightMargin = 0; + this.headerZoom.Size = new System.Drawing.Size(112, 14); + this.headerZoom.TabIndex = 7; + this.headerZoom.TabStop = false; + // + // trackBarZoom + // + this.trackBarZoom.Location = new System.Drawing.Point(352, 24); + this.trackBarZoom.Maximum = 1024; + this.trackBarZoom.Name = "trackBarZoom"; + this.trackBarZoom.Orientation = System.Windows.Forms.Orientation.Vertical; + this.trackBarZoom.Size = new System.Drawing.Size(42, 131); + this.trackBarZoom.TabIndex = 8; + this.trackBarZoom.TickFrequency = 64; + this.trackBarZoom.Value = 512; + this.trackBarZoom.ValueChanged += new System.EventHandler(this.TrackBarZoom_ValueChanged); + // + // zoomLabel + // + this.zoomLabel.Location = new System.Drawing.Point(400, 32); + this.zoomLabel.Name = "zoomLabel"; + this.zoomLabel.AutoSize = true; + this.zoomLabel.Width = 48; + this.zoomLabel.TabIndex = 9; + // + // panXLabel + // + this.panXLabel.Location = new System.Drawing.Point(200, 208); + this.panXLabel.Name = "panXLabel"; + this.panXLabel.AutoSize = true; + this.panXLabel.Width = 56; + this.panXLabel.TabIndex = 18; + // + // panYLabel + // + this.panYLabel.Location = new System.Drawing.Point(200, 232); + this.panYLabel.Name = "panYLabel"; + this.panYLabel.AutoSize = true; + this.panYLabel.Width = 56; + this.panYLabel.TabIndex = 19; + // + // panXUpDown + // + this.panXUpDown.DecimalPlaces = 3; + this.panXUpDown.Increment = new System.Decimal(new int[] { + 1, + 0, + 0, + 131072}); + this.panXUpDown.Location = new System.Drawing.Point(260, 204); + this.panXUpDown.Maximum = new System.Decimal(new int[] { + 1000000000, + 0, + 0, + 0}); + this.panXUpDown.Minimum = new System.Decimal(new int[] { + 100000000, + 0, + 0, + -2147483648}); + this.panXUpDown.Name = "panXUpDown"; + this.panXUpDown.Size = new System.Drawing.Size(68, 20); + this.panXUpDown.TabIndex = 20; + this.panXUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.panXUpDown.Enter += new System.EventHandler(this.NumericUpDown_Enter); + this.panXUpDown.ValueChanged += new System.EventHandler(this.PanXUpDown_ValueChanged); + this.panXUpDown.Leave += new System.EventHandler(this.NumericUpDown_Leave); + // + // panYUpDown + // + this.panYUpDown.DecimalPlaces = 3; + this.panYUpDown.Increment = new System.Decimal(new int[] { + 1, + 0, + 0, + 131072}); + this.panYUpDown.Location = new System.Drawing.Point(260, 228); + this.panYUpDown.Maximum = new System.Decimal(new int[] { + 1000000000, + 0, + 0, + 0}); + this.panYUpDown.Minimum = new System.Decimal(new int[] { + 1000000000, + 0, + 0, + -2147483648}); + this.panYUpDown.Name = "panYUpDown"; + this.panYUpDown.Size = new System.Drawing.Size(68, 20); + this.panYUpDown.TabIndex = 21; + this.panYUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.panYUpDown.Enter += new System.EventHandler(this.NumericUpDown_Enter); + this.panYUpDown.ValueChanged += new System.EventHandler(this.PanYUpDown_ValueChanged); + this.panYUpDown.Leave += new System.EventHandler(this.NumericUpDown_Leave); + // + // panResetButton + // + this.panResetButton.Location = new System.Drawing.Point(248, 160); + this.panResetButton.Name = "panResetButton"; + this.panResetButton.Size = new System.Drawing.Size(80, 23); + this.panResetButton.TabIndex = 6; + this.panResetButton.FlatStyle = FlatStyle.System; + this.panResetButton.Click += new System.EventHandler(this.PanResetButton_Click); + // + // angleLabel + // + this.angleLabel.Location = new System.Drawing.Point(8, 208); + this.angleLabel.Name = "angleLabel"; + this.angleLabel.AutoSize = true; + this.angleLabel.Width = 88; + this.angleLabel.TabIndex = 12; + // + // angleUpDown + // + this.angleUpDown.DecimalPlaces = 2; + this.angleUpDown.Location = new System.Drawing.Point(108, 204); + this.angleUpDown.Maximum = new System.Decimal(new int[] { + 360, + 0, + 0, + 0}); + this.angleUpDown.Minimum = new System.Decimal(new int[] { + 360, + 0, + 0, + -2147483648}); + this.angleUpDown.Name = "angleUpDown"; + this.angleUpDown.Size = new System.Drawing.Size(68, 20); + this.angleUpDown.TabIndex = 13; + this.angleUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.angleUpDown.Enter += new System.EventHandler(this.NumericUpDown_Enter); + this.angleUpDown.ValueChanged += new System.EventHandler(this.AngleUpDown_ValueChanged); + this.angleUpDown.Leave += new System.EventHandler(this.NumericUpDown_Leave); + // + // zoomResetButton + // + this.zoomResetButton.Location = new System.Drawing.Point(384, 160); + this.zoomResetButton.Name = "zoomResetButton"; + this.zoomResetButton.Size = new System.Drawing.Size(80, 23); + this.zoomResetButton.TabIndex = 10; + this.zoomResetButton.FlatStyle = FlatStyle.System; + this.zoomResetButton.Click += new System.EventHandler(this.ZoomResetButton_Click); + // + // twistAngleLabel + // + this.twistAngleLabel.Location = new System.Drawing.Point(8, 232); + this.twistAngleLabel.Name = "twistAngleLabel"; + this.twistAngleLabel.AutoSize = true; + this.twistAngleLabel.Width = 88; + this.twistAngleLabel.TabIndex = 14; + // + // twistRadiusLabel + // + this.twistRadiusLabel.Location = new System.Drawing.Point(8, 256); + this.twistRadiusLabel.Name = "twistRadiusLabel"; + this.twistRadiusLabel.AutoSize = true; + this.twistRadiusLabel.Width = 88; + this.twistRadiusLabel.TabIndex = 16; + // + // twistAngleUpDown + // + this.twistAngleUpDown.DecimalPlaces = 2; + this.twistAngleUpDown.Location = new System.Drawing.Point(108, 228); + this.twistAngleUpDown.Maximum = new System.Decimal(new int[] { + 360, + 0, + 0, + 0}); + this.twistAngleUpDown.Minimum = new System.Decimal(new int[] { + 360, + 0, + 0, + -2147483648}); + this.twistAngleUpDown.Name = "twistAngleUpDown"; + this.twistAngleUpDown.Size = new System.Drawing.Size(68, 20); + this.twistAngleUpDown.TabIndex = 15; + this.twistAngleUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.twistAngleUpDown.Enter += new System.EventHandler(this.NumericUpDown_Enter); + this.twistAngleUpDown.ValueChanged += new System.EventHandler(this.TwistAngleUpDown_ValueChanged); + this.twistAngleUpDown.Leave += new System.EventHandler(this.NumericUpDown_Leave); + // + // twistRadiusUpDown + // + this.twistRadiusUpDown.DecimalPlaces = 2; + this.twistRadiusUpDown.Location = new System.Drawing.Point(108, 252); + this.twistRadiusUpDown.Maximum = new System.Decimal(new int[] { + 8995, + 0, + 0, + 131072}); + this.twistRadiusUpDown.Name = "twistRadiusUpDown"; + this.twistRadiusUpDown.Size = new System.Drawing.Size(68, 20); + this.twistRadiusUpDown.TabIndex = 17; + this.twistRadiusUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.twistRadiusUpDown.Enter += new System.EventHandler(this.NumericUpDown_Enter); + this.twistRadiusUpDown.ValueChanged += new System.EventHandler(this.TwistRadiusUpDown_ValueChanged); + this.twistRadiusUpDown.Leave += new System.EventHandler(this.NumericUpDown_Leave); + // + // rollResetButton + // + this.rollResetButton.Location = new System.Drawing.Point(96, 160); + this.rollResetButton.Name = "rollResetButton"; + this.rollResetButton.Size = new System.Drawing.Size(80, 23); + this.rollResetButton.TabIndex = 4; + this.rollResetButton.FlatStyle = FlatStyle.System; + this.rollResetButton.Click += new System.EventHandler(this.RollResetButton_Click); + // + // resetAllButton + // + this.resetAllButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.resetAllButton.Location = new System.Drawing.Point(200, 312); + this.resetAllButton.Name = "resetAllButton"; + this.resetAllButton.Size = new System.Drawing.Size(104, 23); + this.resetAllButton.TabIndex = 25; + this.resetAllButton.FlatStyle = FlatStyle.System; + this.resetAllButton.Click += new System.EventHandler(this.ResetAllButton_Click); + // + // fineTuningHeader + // + this.fineTuningHeader.Location = new System.Drawing.Point(8, 184); + this.fineTuningHeader.Name = "fineTuningHeader"; + this.fineTuningHeader.Size = new System.Drawing.Size(464, 14); + this.fineTuningHeader.TabIndex = 11; + this.fineTuningHeader.TabStop = false; + // + // headerLabel1 + // + this.headerLabel1.Location = new System.Drawing.Point(8, 280); + this.headerLabel1.Name = "headerLabel1"; + this.headerLabel1.Size = new System.Drawing.Size(464, 14); + this.headerLabel1.TabIndex = 22; + this.headerLabel1.TabStop = false; + // + // RotateZoomEffectConfigDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(474, 343); + this.Controls.Add(this.headerLabel1); + this.Controls.Add(this.fineTuningHeader); + this.Controls.Add(this.resetAllButton); + this.Controls.Add(this.rollResetButton); + this.Controls.Add(this.twistRadiusUpDown); + this.Controls.Add(this.twistAngleUpDown); + this.Controls.Add(this.twistRadiusLabel); + this.Controls.Add(this.twistAngleLabel); + this.Controls.Add(this.zoomResetButton); + this.Controls.Add(this.angleUpDown); + this.Controls.Add(this.angleLabel); + this.Controls.Add(this.panResetButton); + this.Controls.Add(this.panYUpDown); + this.Controls.Add(this.panXUpDown); + this.Controls.Add(this.panYLabel); + this.Controls.Add(this.panXLabel); + this.Controls.Add(this.zoomLabel); + this.Controls.Add(this.trackBarZoom); + this.Controls.Add(this.panelPan); + this.Controls.Add(this.headerPan); + this.Controls.Add(this.headerRoll); + this.Controls.Add(this.keepBackgroundCheckBox); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.tileSourceCheckBox); + this.Controls.Add(this.rollControl); + this.Controls.Add(this.headerZoom); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "RotateZoomEffectConfigDialog"; + this.Controls.SetChildIndex(this.headerZoom, 0); + this.Controls.SetChildIndex(this.rollControl, 0); + this.Controls.SetChildIndex(this.tileSourceCheckBox, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.keepBackgroundCheckBox, 0); + this.Controls.SetChildIndex(this.headerRoll, 0); + this.Controls.SetChildIndex(this.headerPan, 0); + this.Controls.SetChildIndex(this.panelPan, 0); + this.Controls.SetChildIndex(this.trackBarZoom, 0); + this.Controls.SetChildIndex(this.zoomLabel, 0); + this.Controls.SetChildIndex(this.panXLabel, 0); + this.Controls.SetChildIndex(this.panYLabel, 0); + this.Controls.SetChildIndex(this.panXUpDown, 0); + this.Controls.SetChildIndex(this.panYUpDown, 0); + this.Controls.SetChildIndex(this.panResetButton, 0); + this.Controls.SetChildIndex(this.angleLabel, 0); + this.Controls.SetChildIndex(this.angleUpDown, 0); + this.Controls.SetChildIndex(this.zoomResetButton, 0); + this.Controls.SetChildIndex(this.twistAngleLabel, 0); + this.Controls.SetChildIndex(this.twistRadiusLabel, 0); + this.Controls.SetChildIndex(this.twistAngleUpDown, 0); + this.Controls.SetChildIndex(this.twistRadiusUpDown, 0); + this.Controls.SetChildIndex(this.rollResetButton, 0); + this.Controls.SetChildIndex(this.resetAllButton, 0); + this.Controls.SetChildIndex(this.fineTuningHeader, 0); + this.Controls.SetChildIndex(this.headerLabel1, 0); + this.panelPan.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.trackBarZoom)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.panXUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.panYUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.angleUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.twistAngleUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.twistRadiusUpDown)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + + private void OkButton_Click(object sender, System.EventArgs e) + { + DialogResult = DialogResult.OK; + Close(); + } + + private void CancelButton_Click(object sender, System.EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void KeepBackgroundCheckBox_CheckedChanged(object sender, System.EventArgs e) + { + FinishTokenUpdate(); + } + + private void RollControl_ValueChanged(object sender, System.EventArgs e) + { + if (this.angleUpDown.Value != (decimal)this.rollControl.Angle) + { + this.angleUpDown.Value = (decimal)this.rollControl.Angle; + } + + if (this.twistAngleUpDown.Value != -(decimal)this.rollControl.RollDirection) + { + this.twistAngleUpDown.Value = -(decimal)this.rollControl.RollDirection; + } + + if (this.twistRadiusUpDown.Value != (decimal)this.rollControl.RollAmount) + { + this.twistRadiusUpDown.Value = (decimal)this.rollControl.RollAmount; + } + + UpdateUpDowns(); + FinishTokenUpdate(); + } + + private void PanControl_PositionChanged(object sender, System.EventArgs e) + { + if (panXUpDown.Value != (decimal)panControl.Position.X) + { + panXUpDown.Value = (decimal)panControl.Position.X; + } + + if (panYUpDown.Value != (decimal)panControl.Position.Y) + { + panYUpDown.Value = (decimal)panControl.Position.Y; + } + + UpdateUpDowns(); + FinishTokenUpdate(); + } + + private void TrackBarZoom_ValueChanged(object sender, System.EventArgs e) + { + FinishTokenUpdate(); + string zoomTextFormat = PdnResources.GetString("RotateZoomEffectConfigDialog.ZoomLabel.Text.Format"); + string zoomText = string.Format(zoomTextFormat, ((RotateZoomEffectConfigToken)theEffectToken).Zoom.ToString("F2")); + this.zoomLabel.Text = zoomText; + UpdateUpDowns(); + } + + private void TileSource_CheckedChanged(object sender, System.EventArgs e) + { + FinishTokenUpdate(); + } + + private void NumericUpDown_Enter(object sender, System.EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + nud.Select(0, nud.Text.Length); + } + + private void NumericUpDown_Leave(object sender, System.EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + Utility.ClipNumericUpDown(nud); + + if (Utility.CheckNumericUpDown(nud)) + { + nud.Value = decimal.Parse(nud.Text); + } + } + + private void PanXUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (this.panControl.Position.X != (float)panXUpDown.Value) + { + this.panControl.Position = new PointF((float)panXUpDown.Value, this.panControl.Position.Y); + FinishTokenUpdate(); + } + } + + private void PanYUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (this.panControl.Position.Y != (float)panYUpDown.Value) + { + this.panControl.Position = new PointF(this.panControl.Position.X, (float)panYUpDown.Value); + FinishTokenUpdate(); + } + } + + private void PanResetButton_Click(object sender, System.EventArgs e) + { + panXUpDown.Value = 0; + panYUpDown.Value = 0; + } + + private void AngleUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (this.rollControl.Angle != (double)angleUpDown.Value) + { + this.rollControl.Angle = (double)angleUpDown.Value; + UpdateUpDowns(); + FinishTokenUpdate(); + } + } + + private void ZoomResetButton_Click(object sender, System.EventArgs e) + { + this.trackBarZoom.Value = 512; // 1.00 + } + + private void TwistAngleUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (this.rollControl.RollDirection != -(float)this.twistAngleUpDown.Value) + { + this.rollControl.RollDirection = -(float)this.twistAngleUpDown.Value; + FinishTokenUpdate(); + } + } + + private void TwistRadiusUpDown_ValueChanged(object sender, System.EventArgs e) + { + if (this.rollControl.RollAmount != (float)this.twistRadiusUpDown.Value) + { + this.rollControl.RollAmount = (float)this.twistRadiusUpDown.Value; + FinishTokenUpdate(); + } + } + + private void RollResetButton_Click(object sender, System.EventArgs e) + { + this.rollControl.Angle = 0.0; + this.rollControl.RollAmount = 0; + this.rollControl.RollDirection = 0; + } + + private void UpdateUpDowns() + { + this.twistAngleUpDown.Update(); + this.twistRadiusUpDown.Update(); + this.angleUpDown.Update(); + this.panXUpDown.Update(); + this.panYUpDown.Update(); + } + + private void ResetAllButton_Click(object sender, System.EventArgs e) + { + this.panResetButton.PerformClick(); + this.zoomResetButton.PerformClick(); + this.rollResetButton.PerformClick(); + } + } +} diff --git a/src/Effects/RotateZoomEffectConfigDialog.resx b/src/Effects/RotateZoomEffectConfigDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/RotateZoomEffectConfigToken.cs b/src/Effects/RotateZoomEffectConfigToken.cs new file mode 100644 index 0000000..1169b9b --- /dev/null +++ b/src/Effects/RotateZoomEffectConfigToken.cs @@ -0,0 +1,237 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.Effects +{ + public class RotateZoomEffectConfigToken + : EffectConfigToken + { + internal class RzInfo + { + // gradients + public float startX; + public float startY; + public float startZ; + public float dsxdx; + public float dsydx; + public float dszdx; + public float dsxdy; + public float dsydy; + public float dszdy; + + private void Transform(RotateZoomEffectConfigToken token, int x, int y, out float sx, out float sy, out float sz) + { + float rb = token.preRotateZ; + float ra = token.postRotateZ; + float r = -token.tilt; + float crb = (float)Math.Cos(rb); + float cr = (float)Math.Cos(r); + float cra = (float)Math.Cos(ra); + float srb = (float)Math.Sin(rb); + float sr = (float)Math.Sin(r); + float sra = (float)Math.Sin(ra); + float ox = x, oy = y, oz = 0; + + sx = (ox * crb + oy * srb) / cr; + sy = -ox * srb + oy * crb; + + sz = sx * sr; + sx = sx / cr; + ox = sx; oy = sy; oz = sz; + + sx = ox * cra + oy * sra; + sy = -ox * sra + oy * cra; + } + + public void Update(RotateZoomEffectConfigToken token) + { + Transform(token, 0, 0, out startX, out startY, out startZ); + Transform(token, 1, 0, out dsxdx, out dsydx, out dszdx); + Transform(token, 0, 1, out dsxdy, out dsydy, out dszdy); + + dsxdx -= startX; dsydx -= startY; dszdx -= startZ; + dsxdy -= startX; dsydy -= startY; dszdy -= startZ; + } + } + + private void UpdateRzInfo() + { + lock (this) + { + computedOnce = new RzInfo(); + computedOnce.Update(this); + } + } + + private bool highQuality; + public bool HighQuality + { + get + { + return highQuality; + } + + set + { + this.highQuality = value; + } + } + + private RzInfo computedOnce; + internal RzInfo ComputedOnce + { + get + { + return computedOnce; + } + } + + private float preRotateZ; + public float PreRotateZ + { + get + { + return preRotateZ; + } + + set + { + preRotateZ = value; + UpdateRzInfo(); + } + } + + private float postRotateZ; + public float PostRotateZ + { + get + { + return postRotateZ; + } + + set + { + postRotateZ = value; + UpdateRzInfo(); + } + } + + private float tilt; + public float Tilt + { + get + { + return tilt; + } + + set + { + tilt = value; + UpdateRzInfo(); + } + } + + private float zoom; + public float Zoom + { + get + { + return zoom; + } + + set + { + zoom = value; + UpdateRzInfo(); + } + } + + private bool sourceAsBackground; + public bool SourceAsBackground + { + get + { + return sourceAsBackground; + } + + set + { + sourceAsBackground = value; + UpdateRzInfo(); + } + } + + private bool tile; + public bool Tile + { + get + { + return tile; + } + + set + { + tile = value; + UpdateRzInfo(); + } + } + + private PointF offset; + public PointF Offset + { + get + { + return offset; + } + + set + { + offset = value; + UpdateRzInfo(); + } + } + + public RotateZoomEffectConfigToken(bool highQuality, float preRotateZ, float postRotateZ, + float tilt, float zoom, PointF offset, bool sourceAsBackground, bool tile) + { + this.highQuality = highQuality; + this.preRotateZ = preRotateZ; + this.postRotateZ = postRotateZ; + this.tilt = tilt; + this.zoom = zoom; + this.offset = offset; + this.sourceAsBackground = sourceAsBackground; + this.tile = tile; + UpdateRzInfo(); + } + + protected RotateZoomEffectConfigToken(RotateZoomEffectConfigToken copyMe) + { + this.highQuality = copyMe.highQuality; + this.preRotateZ = copyMe.preRotateZ; + this.postRotateZ = copyMe.postRotateZ; + this.tilt = copyMe.tilt; + this.zoom = copyMe.zoom; + this.offset = copyMe.offset; + this.sourceAsBackground = copyMe.sourceAsBackground; + this.tile = copyMe.tile; + UpdateRzInfo(); + } + + public override object Clone() + { + return new RotateZoomEffectConfigToken(this); + } + } +} diff --git a/src/Effects/SepiaEffect.cs b/src/Effects/SepiaEffect.cs new file mode 100644 index 0000000..8ad1898 --- /dev/null +++ b/src/Effects/SepiaEffect.cs @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.PropertySystem; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + [EffectCategory(EffectCategory.Adjustment)] + public sealed class SepiaEffect + : InternalPropertyBasedEffect + { + private UnaryPixelOp levels; + private UnaryPixelOp desaturate; + + protected override PropertyCollection OnCreatePropertyCollection() + { + return PropertyCollection.CreateEmpty(); + } + + protected override void OnRender(Rectangle[] rois, int startIndex, int length) + { + this.desaturate.Apply(DstArgs.Surface, SrcArgs.Surface, rois, startIndex, length); + this.levels.Apply(DstArgs.Surface, DstArgs.Surface, rois, startIndex, length); + } + + public SepiaEffect() + : base(PdnResources.GetString("SepiaEffect.Name"), + PdnResources.GetImageResource("Icons.SepiaEffect.png").Reference, + null, + EffectFlags.None) + { + this.desaturate = new UnaryPixelOps.Desaturate(); + + this.levels = new UnaryPixelOps.Level( + ColorBgra.Black, + ColorBgra.White, + new float[] { 1.2f, 1.0f, 0.8f }, + ColorBgra.Black, + ColorBgra.White); + } + } +} diff --git a/src/Effects/SharpenEffect.cs b/src/Effects/SharpenEffect.cs new file mode 100644 index 0000000..53fe5f8 --- /dev/null +++ b/src/Effects/SharpenEffect.cs @@ -0,0 +1,87 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class SharpenEffect + : LocalHistogramEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("SharpenEffect.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.SharpenEffect.png"); + } + } + + public enum PropertyNames + { + Amount = 0 + } + + public SharpenEffect() + : base(StaticName, StaticImage.Reference, SubmenuNames.Photo, EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Amount, 2, 1, 20)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("SharpenEffect.ConfigDialog.SliderLabel")); + + return configUI; + } + + private int amount; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.amount = newToken.GetProperty(PropertyNames.Amount).Value; + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + public unsafe override ColorBgra Apply(ColorBgra src, int area, int* hb, int* hg, int* hr, int* ha) + { + ColorBgra median = GetPercentile(50, area, hb, hg, hr, ha); + return ColorBgra.Lerp(src, median, -0.5f); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + foreach (Rectangle rect in rois) + { + RenderRect(this.amount, SrcArgs.Surface, DstArgs.Surface, rect); + } + } + } +} diff --git a/src/Effects/SoftenPortraitEffect.cs b/src/Effects/SoftenPortraitEffect.cs new file mode 100644 index 0000000..52ef07b --- /dev/null +++ b/src/Effects/SoftenPortraitEffect.cs @@ -0,0 +1,172 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// This effect was graciously provided by David Issel, aka BoltBait. His original +// copyright and license (MIT License) are reproduced below. + +/* +PortraitEffect.cs +Copyright (c) 2007 David Issel +Contact Info: BoltBait@hotmail.com http://www.BoltBait.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +using PaintDotNet; +using PaintDotNet.Effects; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class SoftenPortraitEffect + : InternalPropertyBasedEffect + { + public enum PropertyNames + { + Softness = 0, + Lighting = 1, + Warmth = 2 + } + + public static string StaticName + { + get + { + return PdnResources.GetString("SoftenPortraitEffect.Name"); + } + } + + public static Image StaticIcon + { + get + { + return PdnResources.GetImageResource("Icons.SoftenPortraitEffectIcon.png").Reference; + } + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Softness, 5, 0, 10)); + props.Add(new Int32Property(PropertyNames.Lighting, 0, -20, +20)); + props.Add(new Int32Property(PropertyNames.Warmth, 10, 0, 20)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Softness, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("SoftenPortraitEffect.ConfigDialog.SoftnessLabel")); + configUI.SetPropertyControlValue(PropertyNames.Lighting, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("SoftenPortraitEffect.ConfigDialog.LightingLabel")); + configUI.SetPropertyControlValue(PropertyNames.Warmth, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("SoftenPortraitEffect.ConfigDialog.WarmthLabel")); + + return configUI; + } + + private GaussianBlurEffect blurEffect; + private PropertyCollection blurProps; + private UnaryPixelOps.Desaturate desaturateOp; + private BrightnessAndContrastAdjustment bacAdjustment; + private PropertyCollection bacProps; + private UserBlendOps.OverlayBlendOp overlayOp; + + private int softness; + private int lighting; + private int warmth; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.softness = newToken.GetProperty(PropertyNames.Softness).Value; + this.lighting = newToken.GetProperty(PropertyNames.Lighting).Value; + this.warmth = newToken.GetProperty(PropertyNames.Warmth).Value; + + PropertyBasedEffectConfigToken blurToken = new PropertyBasedEffectConfigToken(this.blurProps); + blurToken.SetPropertyValue(GaussianBlurEffect.PropertyNames.Radius, this.softness * 3); + this.blurEffect.SetRenderInfo(blurToken, dstArgs, srcArgs); + + PropertyBasedEffectConfigToken bacToken = new PropertyBasedEffectConfigToken(this.bacProps); + bacToken.SetPropertyValue(BrightnessAndContrastAdjustment.PropertyNames.Brightness, this.lighting); + bacToken.SetPropertyValue(BrightnessAndContrastAdjustment.PropertyNames.Contrast, -this.lighting / 2); + this.bacAdjustment.SetRenderInfo(bacToken, dstArgs, dstArgs); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected override unsafe void OnRender(Rectangle[] rois, int startIndex, int length) + { + float redAdjust = 1.0f + (this.warmth / 100.0f); + float blueAdjust = 1.0f - (this.warmth / 100.0f); + + this.blurEffect.Render(rois, startIndex, length); + this.bacAdjustment.Render(rois, startIndex, length); + + for (int i = startIndex; i < startIndex + length; ++i) + { + Rectangle roi = rois[i]; + + for (int y = roi.Top; y < roi.Bottom; ++y) + { + ColorBgra* srcPtr = SrcArgs.Surface.GetPointAddress(roi.X, y); + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddress(roi.X, y); + + for (int x = roi.Left; x < roi.Right; ++x) + { + ColorBgra srcGrey = this.desaturateOp.Apply(*srcPtr); + + srcGrey.R = Utility.ClampToByte((int)((float)srcGrey.R * redAdjust)); + srcGrey.B = Utility.ClampToByte((int)((float)srcGrey.B * blueAdjust)); + + ColorBgra mypixel = this.overlayOp.Apply(srcGrey, *dstPtr); + *dstPtr = mypixel; + + ++srcPtr; + ++dstPtr; + } + } + } + } + + public SoftenPortraitEffect() + : base(StaticName, StaticIcon, SubmenuNames.Photo, EffectFlags.Configurable) + { + this.blurEffect = new GaussianBlurEffect(); + this.blurProps = this.blurEffect.CreatePropertyCollection(); + + this.desaturateOp = new UnaryPixelOps.Desaturate(); + + this.bacAdjustment = new BrightnessAndContrastAdjustment(); + this.bacProps = this.bacAdjustment.CreatePropertyCollection(); + + this.overlayOp = new UserBlendOps.OverlayBlendOp(); + } + } +} \ No newline at end of file diff --git a/src/Effects/SrgbUtility.cs b/src/Effects/SrgbUtility.cs new file mode 100644 index 0000000..8f718d0 --- /dev/null +++ b/src/Effects/SrgbUtility.cs @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2007, 2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; + +namespace PaintDotNet.Effects +{ + internal static class SrgbUtility + { + // pre-calculated array of linear intensity for 8bit values + private static double[] linearIntensity; + + static SrgbUtility() + { + linearIntensity = new double[256]; + + for (int i = 0; i <= 255; i++) + { + double x = i / 255d; + linearIntensity[i] = ToLinear(x); + } + } + + public static double ToSrgb(double linearLevel) + { + System.Diagnostics.Debug.Assert((linearLevel >= 0d && linearLevel <= 1d), "level is out of range 0-1"); + const double power = 1d / 2.4d; + if (linearLevel <= 0.0031308d) + { + return 12.92d * linearLevel; + } + else + { + double result = (1.055d * Math.Pow(linearLevel, power)) - 0.055d; + return result; + } + } + + public static double ToSrgbClamped(double linearLevel) + { + double result = (linearLevel < 0d) ? 0d : (linearLevel > 1d) ? 1d : ToSrgb(linearLevel); + return result; + } + + public static double ToLinear(byte srgbLevel) + { + return linearIntensity[srgbLevel]; + } + + public static double ToLinear(double srgbLevel) + { + double Y; + const double factor1 = 1d / 12.92d; + const double factor2 = 1d / 1.055d; + + if (srgbLevel <= 0.04045d) + { + Y = srgbLevel * factor1; + } + else + { + Y = Math.Pow(((srgbLevel + 0.055d) * factor2), 2.4d); + } + + return Y; + } + } +} diff --git a/src/Effects/SubmenuNames.cs b/src/Effects/SubmenuNames.cs new file mode 100644 index 0000000..eb025a8 --- /dev/null +++ b/src/Effects/SubmenuNames.cs @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + public static class SubmenuNames + { + public static string Blurs + { + get + { + return PdnResources.GetString("Effects.Blurring.Submenu.Name"); + } + } + + public static string Distort + { + get + { + return PdnResources.GetString("DistortSubmenu.Name"); + } + } + + public static string Render + { + get + { + return PdnResources.GetString("Effects.Render.Submenu.Name"); + } + } + + public static string Noise + { + get + { + return PdnResources.GetString("Effects.Noise.Submenu.Name"); + } + } + + public static string Photo + { + get + { + return PdnResources.GetString("Effects.Photo.Submenu.Name"); + } + } + + public static string Artistic + { + get + { + return PdnResources.GetString("Effects.Artistic.Submenu.Name"); + } + } + + public static string Stylize + { + get + { + return PdnResources.GetString("Effects.Stylize.Submenu.Name"); + } + } + } +} diff --git a/src/Effects/SurfaceBlurEffect.cs b/src/Effects/SurfaceBlurEffect.cs new file mode 100644 index 0000000..09e3eaf --- /dev/null +++ b/src/Effects/SurfaceBlurEffect.cs @@ -0,0 +1,191 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2007, 2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; + +namespace PaintDotNet.Effects +{ + public sealed class SurfaceBlurEffect + : LocalHistogramEffect + { + public SurfaceBlurEffect() + : base(PdnResources.GetString("SurfaceBlurEffect.Name"), + PdnResources.GetImageResource("Icons.SurfaceBlurEffectIcon.png").Reference, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + private int radius; + private int threshold; + private int[] intensityFunction; + + public enum PropertyName + { + Radius = 0, + Threshold = 1, + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List properties = new List(); + + properties.Add(new Int32Property(PropertyName.Radius, 6, 1, 100)); + properties.Add(new Int32Property(PropertyName.Threshold, 15, 1, 100)); + + return new PropertyCollection(properties); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo info = PropertyBasedEffect.CreateDefaultConfigUI(props); + + info.SetPropertyControlValue( + PropertyName.Radius, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("SurfaceBlurEffect.ConfigDialog.RadiusLabel")); + + info.SetPropertyControlValue( + PropertyName.Threshold, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("SurfaceBlurEffect.ConfigDialog.ThresholdLabel")); + + // don't need to add custom increments - the defaults are fine + + return info; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.radius = newToken.GetProperty(PropertyName.Radius).Value; + this.threshold = newToken.GetProperty(PropertyName.Threshold).Value; + this.intensityFunction = PrecalculateIntensityFunction(this.threshold); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + // rather than a fancy function such as a gaussian, + // currently using a trigular function as this seems to be 'good-enough' + private static int[] PrecalculateIntensityFunction(int threshold) + { + int[] factors = new int[256]; + + double slope = 96d / threshold; + + for (int i = 0; i < 256; i++) + { + int factor = (int)Math.Round(255 - (i * slope), MidpointRounding.AwayFromZero); + + if (factor < 0) + { + factor = 0; + } + + factors[i] = factor; + } + + return factors; + } + + protected override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + foreach (Rectangle rect in renderRects) + { + RenderRect(this.radius, SrcArgs.Surface, DstArgs.Surface, rect); + } + } + + public override unsafe ColorBgra Apply(ColorBgra src, int area, int* hb, int* hg, int* hr, int* ha) + { + int resultB = BlurChannel(src.B, hb); + int resultG = BlurChannel(src.G, hg); + int resultR = BlurChannel(src.R, hr); + + // there is no way we can deal with pre-multiplied alphas; the correlation + // between channels no longer exists by this point in the algorithm... + // so, just use the alpha from the source pixel. + + ColorBgra result = ColorBgra.FromBgra((byte)resultB, (byte)resultG, (byte)resultR, src.A); + return result; + } + + private unsafe int BlurChannel(int current, int* histogram) + { + // note to self: pointers are passed by-value... + // incrementing passed pointer - no effect outside current scope + int sum = 0; + int divisor = 0; + int result = current; + + for (int bin = 0; bin < 256; bin++) + { + if (*histogram > 0) + { + int diff; + + if (bin > current) + { + diff = bin - current; + } + else + { + diff = current - bin; + } + + int intensity = this.intensityFunction[diff]; + + if (intensity > 0) + { + int t = (*histogram) * intensity; + sum += (t * bin); + divisor += t; + } + } + + ++histogram; + } + + if (divisor > 0) + { + // 1/2 LSB for integer rounding + int roundingTerm = divisor >> 1; + result = (sum + roundingTerm) / divisor; + } + + return result; + } + } +} \ No newline at end of file diff --git a/src/Effects/ThreeAmountsConfigDialog.cs b/src/Effects/ThreeAmountsConfigDialog.cs new file mode 100644 index 0000000..c718090 --- /dev/null +++ b/src/Effects/ThreeAmountsConfigDialog.cs @@ -0,0 +1,253 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + public sealed class ThreeAmountsConfigDialog + : ThreeAmountsConfigDialogBase + { + public ThreeAmountsConfigDialog() + { + } + } + + public abstract class ThreeAmountsConfigDialogBase + : TwoAmountsConfigDialogBase + { + private System.Windows.Forms.Button amount3Reset; + private System.Windows.Forms.NumericUpDown amount3UpDown; + private System.Windows.Forms.TrackBar amount3Slider; + private PaintDotNet.HeaderLabel amount3Header; + + private int amount3Default = 0; + + public int Amount3Default + { + get + { + return amount3Default; + } + + set + { + amount3Default = value; + amount3Slider.Value = value; + InitTokenFromDialog(); + } + } + + public int Amount3Minimum + { + get + { + return amount3Slider.Minimum; + } + + set + { + amount3Slider.Minimum = value; + amount3UpDown.Minimum = (decimal)value; + InitTokenFromDialog(); + } + } + + public int Amount3Maximum + { + get + { + return amount3Slider.Maximum; + } + + set + { + amount3Slider.Maximum = value; + amount3UpDown.Maximum = (decimal)value; + InitTokenFromDialog(); + } + } + + public string Amount3Label + { + get + { + return amount3Header.Text; + } + + set + { + amount3Header.Text = value; + } + } + + protected override void InitialInitToken() + { + this.theEffectToken = new ThreeAmountsConfigToken(Amount1Default, Amount2Default, Amount3Default); + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + base.InitDialogFromToken (effectToken); + amount3Slider.Value = ((ThreeAmountsConfigToken)effectToken).Amount3; + } + + protected override void InitTokenFromDialog() + { + base.InitTokenFromDialog(); + ((ThreeAmountsConfigToken)theEffectToken).Amount3 = amount3Slider.Value; + } + + private void InitializeComponent() + { + this.amount3Reset = new System.Windows.Forms.Button(); + this.amount3UpDown = new System.Windows.Forms.NumericUpDown(); + this.amount3Slider = new System.Windows.Forms.TrackBar(); + this.amount3Header = new PaintDotNet.HeaderLabel(); + ((System.ComponentModel.ISupportInitialize)(this.amount3UpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount3Slider)).BeginInit(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.Location = new System.Drawing.Point(101, 219); + this.okButton.Size = new System.Drawing.Size(81, 23); + this.okButton.Name = "okButton"; + this.okButton.TabIndex = 9; + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + // + // cancelButton + // + this.cancelButton.Location = new System.Drawing.Point(188, 219); + this.cancelButton.Size = new System.Drawing.Size(81, 23); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 10; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + // + // amount3Reset + // + this.amount3Reset.Location = new System.Drawing.Point(188, 188); + this.amount3Reset.Name = "amount3Reset"; + this.amount3Reset.Size = new System.Drawing.Size(81, 20); + this.amount3Reset.TabIndex = 8; + this.amount3Reset.Click += new System.EventHandler(this.amount3Reset_Click); + this.amount3Reset.FlatStyle = System.Windows.Forms.FlatStyle.System; + // + // amount3UpDown + // + this.amount3UpDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.amount3UpDown.Location = new System.Drawing.Point(188, 164); + this.amount3UpDown.Minimum = new System.Decimal(new int[] { + 100, + 0, + 0, + -2147483648}); + this.amount3UpDown.Name = "amount3UpDown"; + this.amount3UpDown.Size = new System.Drawing.Size(81, 20); + this.amount3UpDown.TabIndex = 7; + this.amount3UpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.amount3UpDown.Enter += new System.EventHandler(this.amount3UpDown_Enter); + this.amount3UpDown.ValueChanged += new System.EventHandler(this.amount3UpDown_ValueChanged); + this.amount3UpDown.Leave += new System.EventHandler(this.amount3UpDown_Leave); + // + // amount3Slider + // + this.amount3Slider.LargeChange = 20; + this.amount3Slider.Location = new System.Drawing.Point(1, 164); + this.amount3Slider.Maximum = 100; + this.amount3Slider.Minimum = -100; + this.amount3Slider.Name = "amount3Slider"; + this.amount3Slider.Size = new System.Drawing.Size(175, 42); + this.amount3Slider.TabIndex = 6; + this.amount3Slider.TickFrequency = 10; + this.amount3Slider.ValueChanged += new System.EventHandler(this.amount3Slider_ValueChanged); + // + // amount3Header + // + this.amount3Header.Location = new System.Drawing.Point(6, 148); + this.amount3Header.Name = "amount3Header"; + this.amount3Header.Size = new System.Drawing.Size(271, 14); + this.amount3Header.TabIndex = 11; + this.amount3Header.TabStop = false; + this.amount3Header.Text = "Header 3"; + // + // ThreeAmountsConfigDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(275, 248); + this.Controls.Add(this.amount3Header); + this.Controls.Add(this.amount3Slider); + this.Controls.Add(this.amount3Reset); + this.Controls.Add(this.amount3UpDown); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "ThreeAmountsConfigDialog"; + this.Controls.SetChildIndex(this.amount3UpDown, 0); + this.Controls.SetChildIndex(this.amount3Reset, 0); + this.Controls.SetChildIndex(this.amount3Slider, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.amount3Header, 0); + ((System.ComponentModel.ISupportInitialize)(this.amount3UpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount3Slider)).EndInit(); + this.ResumeLayout(false); + + } + + internal ThreeAmountsConfigDialogBase() + { + InitializeComponent(); + this.amount3Reset.Text = PdnResources.GetString("TwoAmountsConfigDialog.Reset.Text"); + } + + private void amount3Slider_ValueChanged(object sender, System.EventArgs e) + { + if (amount3UpDown.Value != (decimal)amount3Slider.Value) + { + amount3UpDown.Value = (decimal)amount3Slider.Value; + FinishTokenUpdate(); + } + } + + private void amount3UpDown_ValueChanged(object sender, System.EventArgs e) + { + if (amount3Slider.Value != (int)amount3UpDown.Value) + { + amount3Slider.Value = (int)amount3UpDown.Value; + FinishTokenUpdate(); + } + } + + private void amount3UpDown_Enter(object sender, System.EventArgs e) + { + amount3UpDown.Select(0, amount3UpDown.Text.Length); + } + + private void amount3UpDown_Leave(object sender, System.EventArgs e) + { + Utility.ClipNumericUpDown(amount3UpDown); + + if (Utility.CheckNumericUpDown(amount3UpDown)) + { + amount3UpDown.Value = decimal.Parse(amount3UpDown.Text); + } + } + + private void amount3Reset_Click(object sender, System.EventArgs e) + { + this.amount3Slider.Value = amount3Default; + } + + protected override void OnOkButtonClicked(object sender, EventArgs e) + { + amount3UpDown_Leave(sender, e); + base.OnOkButtonClicked(sender, e); + } + } +} diff --git a/src/Effects/ThreeAmountsConfigDialog.resx b/src/Effects/ThreeAmountsConfigDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/ThreeAmountsConfigToken.cs b/src/Effects/ThreeAmountsConfigToken.cs new file mode 100644 index 0000000..a705db6 --- /dev/null +++ b/src/Effects/ThreeAmountsConfigToken.cs @@ -0,0 +1,49 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + public class ThreeAmountsConfigToken + : TwoAmountsConfigToken + { + private int amount3; + + public int Amount3 + { + get + { + return amount3; + } + + set + { + amount3 = value; + } + } + + public override object Clone() + { + return new ThreeAmountsConfigToken(this); + } + + public ThreeAmountsConfigToken(int amount1, int amount2, int amount3) + : base(amount1, amount2) + { + this.amount3 = amount3; + } + + private ThreeAmountsConfigToken(ThreeAmountsConfigToken copyMe) + : base(copyMe) + { + this.amount3 = copyMe.amount3; + } + } +} diff --git a/src/Effects/TileEffect.cs b/src/Effects/TileEffect.cs new file mode 100644 index 0000000..d889846 --- /dev/null +++ b/src/Effects/TileEffect.cs @@ -0,0 +1,196 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace PaintDotNet.Effects +{ + public sealed class TileEffect + : InternalPropertyBasedEffect + { + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.TileEffect.png").Reference; + } + } + + public static string StaticName + { + get + { + return PdnResources.GetString("TileEffect.Name"); + } + } + + public static string StaticSubMenuName + { + get + { + return SubmenuNames.Distort; + } + } + + public enum PropertyNames + { + Rotation = 0, + SquareSize = 1, + Curvature = 2, + Quality = 3 + } + + public TileEffect() + : base(StaticName, StaticImage, StaticSubMenuName, EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Rotation, 30, -180, +180)); + props.Add(new DoubleProperty(PropertyNames.SquareSize, 40, 1, 800)); + props.Add(new DoubleProperty(PropertyNames.Curvature, 8, -100, 100)); + props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Rotation, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TileEffect.Rotation.Text")); + configUI.SetPropertyControlType(PropertyNames.Rotation, PropertyControlType.AngleChooser); + + configUI.SetPropertyControlValue(PropertyNames.SquareSize, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TileEffect.SquareSize.Text")); + configUI.SetPropertyControlValue(PropertyNames.SquareSize, ControlInfoPropertyNames.UseExponentialScale, true); + + configUI.SetPropertyControlValue(PropertyNames.Curvature, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TileEffect.Intensity.Text")); + + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TileEffect.Quality.Text")); + + return configUI; + } + + private double rotation; + private double squareSize; + private double curvature; + + private int quality; + private float sin; + private float cos; + private float scale; + private float intensity; + //private PointF[] aaPointsArray; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.rotation = -newToken.GetProperty(PropertyNames.Rotation).Value; + this.squareSize = newToken.GetProperty(PropertyNames.SquareSize).Value; + this.curvature = newToken.GetProperty(PropertyNames.Curvature).Value; + + this.sin = (float)Math.Sin(this.rotation * Math.PI / 180.0); + this.cos = (float)Math.Cos(this.rotation * Math.PI / 180.0); + this.scale = (float)(Math.PI / this.squareSize); + this.intensity = (float)(this.curvature * this.curvature / 10.0 * Math.Sign(this.curvature)); + + this.quality = newToken.GetProperty(PropertyNames.Quality).Value; + + if (this.quality != 1) + { + ++this.quality; + } + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + int width = dst.Width; + int height = dst.Height; + float hw = width / 2.0f; + float hh = height / 2.0f; + + int aaSampleCount = this.quality * this.quality; + PointF* aaPointsArray = stackalloc PointF[aaSampleCount]; + Utility.GetRgssOffsets(aaPointsArray, aaSampleCount, this.quality); + ColorBgra* samples = stackalloc ColorBgra[aaSampleCount]; + + for (int n = startIndex; n < startIndex + length; ++n) + { + Rectangle rect = rois[n]; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + float j = y - hh; + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; x++) + { + float i = x - hw; + + for (int p = 0; p < aaSampleCount; ++p) + { + PointF pt = aaPointsArray[p]; + + float u1 = i + pt.X; + float v1 = j - pt.Y; + + float s1 = cos * u1 + sin * v1; + float t1 = -sin * u1 + cos * v1; + + float s2 = s1 + this.intensity * (float)Math.Tan(s1 * this.scale); + float t2 = t1 + this.intensity * (float)Math.Tan(t1 * this.scale); + + float u2 = cos * s2 - sin * t2; + float v2 = sin * s2 + cos * t2; + + float xSample = hw + u2; + float ySample = hh + v2; + + samples[p] = src.GetBilinearSampleWrapped(xSample, ySample); + + /* + int xiSample = (int)xSample; + int yiSample = (int)ySample; + + xiSample = (xiSample + width) % width; + if (xiSample < 0) // This makes it a little faster + { + xiSample = (xiSample + width) % width; + } + + yiSample = (yiSample + height) % height; + if (yiSample < 0) // This makes it a little faster + { + yiSample = (yiSample + height) % height; + } + + samples[p] = *src.GetPointAddressUnchecked(xiSample, yiSample); + */ + } + + *dstPtr = ColorBgra.Blend(samples, aaSampleCount); + ++dstPtr; + } + } + } + } + } +} diff --git a/src/Effects/TwistEffect.cs b/src/Effects/TwistEffect.cs new file mode 100644 index 0000000..87d6ada --- /dev/null +++ b/src/Effects/TwistEffect.cs @@ -0,0 +1,194 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.IndirectUI; +using PaintDotNet.Effects; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public sealed class TwistEffect + : InternalPropertyBasedEffect + { + public static Image StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.TwistEffect.png").Reference; + } + } + + public static string StaticName + { + get + { + return PdnResources.GetString("TwistEffect.Name"); + } + } + + public static string StaticSubMenuName + { + get + { + return SubmenuNames.Distort; + } + } + + public enum PropertyNames + { + Amount = 0, + Size = 1, + Offset = 2, + Quality = 3 + } + + public TwistEffect() + : base(StaticName, StaticImage, StaticSubMenuName, EffectFlags.Configurable) + { + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new DoubleProperty(PropertyNames.Amount, 30.0, -200.0, 200.0)); + props.Add(new DoubleProperty(PropertyNames.Size, 1.0, 0.01, 2.0)); + + props.Add(new DoubleVectorProperty( + PropertyNames.Offset, + Pair.Create(0.0, 0.0), + Pair.Create(-2.0, -2.0), + Pair.Create(+2.0, +2.0))); + + props.Add(new Int32Property(PropertyNames.Quality, 2, 1, 5)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TwistEffect.TwistAmount.Text")); + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.UseExponentialScale, true); + + configUI.SetPropertyControlValue(PropertyNames.Size, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TwistEffect.TwistSize.Text")); + configUI.SetPropertyControlValue(PropertyNames.Size, ControlInfoPropertyNames.SliderSmallChange, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Size, ControlInfoPropertyNames.SliderLargeChange, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Size, ControlInfoPropertyNames.UpDownIncrement, 0.01); + + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TwistEffect.Offset.Text")); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeX, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeX, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementX, 0.01); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeY, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeY, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementY, 0.01); + + Surface sourceSurface = this.EnvironmentParameters.SourceSurface; + Bitmap bitmap = sourceSurface.CreateAliasedBitmap(); + ImageResource imageResource = ImageResource.FromImage(bitmap); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.StaticImageUnderlay, imageResource); + + configUI.SetPropertyControlValue(PropertyNames.Quality, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("TwistEffect.Antialias.Text")); + + return configUI; + } + + private double inv100 = 1.0 / 100.0; + + private double amount; + private double size; + private int quality; + private Pair offset; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.amount = -newToken.GetProperty(PropertyNames.Amount).Value; + this.size = 1.0 / newToken.GetProperty(PropertyNames.Size).Value; + this.quality = newToken.GetProperty(PropertyNames.Quality).Value; + this.offset = newToken.GetProperty(PropertyNames.Offset).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + double twist = this.amount * this.amount * Math.Sign(this.amount); + + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + float hw = dst.Width / 2.0f; + hw += (float)(hw * this.offset.First); + float hh = dst.Height / 2.0f; + hh += (float)(hh * this.offset.Second); + + //*double maxrad = Math.Min(dst.Width / 2.0, dst.Height / 2.0); + double invmaxrad = 1.0 / Math.Min(dst.Width / 2.0, dst.Height / 2.0); + + int aaLevel = this.quality; + int aaSamples = aaLevel * aaLevel; + PointF* aaPoints = stackalloc PointF[aaSamples]; + Utility.GetRgssOffsets(aaPoints, aaSamples, aaLevel); + + ColorBgra* samples = stackalloc ColorBgra[aaSamples]; + + for (int n = startIndex; n < startIndex + length; ++n) + { + Rectangle rect = rois[n]; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + float j = y - hh; + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + ColorBgra* srcPtr = src.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; x++) + { + float i = x - hw; + + int sampleCount = 0; + + for (int p = 0; p < aaSamples; ++p) + { + float u = i + aaPoints[p].X; + float v = j + aaPoints[p].Y; + + double rad = Math.Sqrt(u * u + v * v); + double theta = Math.Atan2(v, u); + + double t = 1 - ((rad * this.size) * invmaxrad); + + t = (t < 0) ? 0 : (t * t * t); + + theta += (t * twist) * inv100; + + float sampleX = (hw + (float)(rad * Math.Cos(theta))); + float sampleY = (hh + (float)(rad * Math.Sin(theta))); + + samples[sampleCount] = src.GetBilinearSampleClamped(sampleX, sampleY); + ++sampleCount; + } + + *dstPtr = ColorBgra.Blend(samples, sampleCount); + + + ++dstPtr; + ++srcPtr; + } + } + } + } + } +} diff --git a/src/Effects/TwoAmountsConfigDialog.cs b/src/Effects/TwoAmountsConfigDialog.cs new file mode 100644 index 0000000..756f6c9 --- /dev/null +++ b/src/Effects/TwoAmountsConfigDialog.cs @@ -0,0 +1,476 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class TwoAmountsConfigDialog + : TwoAmountsConfigDialogBase + { + public TwoAmountsConfigDialog() + { + } + } + + public abstract class TwoAmountsConfigDialogBase + : EffectConfigDialog + { + private System.Windows.Forms.TrackBar amount1Slider; + private System.Windows.Forms.NumericUpDown amount1UpDown; + protected System.Windows.Forms.Button okButton; + protected System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.NumericUpDown amount2UpDown; + private System.Windows.Forms.TrackBar amount2Slider; + private System.Windows.Forms.Button amount2Reset; + private System.Windows.Forms.Button amount1Reset; + private System.ComponentModel.IContainer components = null; + + private int amount1Default = 0; + private PaintDotNet.HeaderLabel amount1Header; + private PaintDotNet.HeaderLabel amount2Header; + private int amount2Default = 0; + + public int Amount1Default + { + get + { + return amount1Default; + } + + set + { + amount1Default = value; + amount1Slider.Value = value; + InitTokenFromDialog(); + } + } + + public int Amount1Minimum + { + get + { + return amount1Slider.Minimum; + } + + set + { + amount1Slider.Minimum = value; + amount1UpDown.Minimum = (decimal)value; + InitTokenFromDialog(); + } + } + + public int Amount1Maximum + { + get + { + return amount1Slider.Maximum; + } + + set + { + amount1Slider.Maximum = value; + amount1UpDown.Maximum = (decimal)value; + InitTokenFromDialog(); + } + } + + public string Amount1Label + { + get + { + return amount1Header.Text; + } + + set + { + amount1Header.Text = value; + } + } + + public int Amount2Default + { + get + { + return amount2Default; + } + + set + { + amount2Default = value; + amount2Slider.Value = value; + InitTokenFromDialog(); + } + } + + public int Amount2Minimum + { + get + { + return amount2Slider.Minimum; + } + + set + { + amount2Slider.Minimum = value; + amount2UpDown.Minimum = (decimal)value; + InitTokenFromDialog(); + } + } + + public int Amount2Maximum + { + get + { + return amount2Slider.Maximum; + } + + set + { + amount2Slider.Maximum = value; + amount2UpDown.Maximum = (decimal)value; + InitTokenFromDialog(); + } + } + + public string Amount2Label + { + get + { + return amount2Header.Text; + } + + set + { + amount2Header.Text = value; + } + } + + internal TwoAmountsConfigDialogBase() + { + // This call is required by the Windows Form Designer. + InitializeComponent(); + + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.amount1Reset.Text = PdnResources.GetString("TwoAmountsConfigDialog.Reset.Text"); + this.amount2Reset.Text = PdnResources.GetString("TwoAmountsConfigDialog.Reset.Text"); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + protected override void InitialInitToken() + { + theEffectToken = new TwoAmountsConfigToken(Amount1Default, Amount2Default); + } + + protected override void InitDialogFromToken(EffectConfigToken effectToken) + { + amount1Slider.Value = ((TwoAmountsConfigToken)effectToken).Amount1; + amount2Slider.Value = ((TwoAmountsConfigToken)effectToken).Amount2; + } + + protected override void InitTokenFromDialog() + { + ((TwoAmountsConfigToken)theEffectToken).Amount1 = amount1Slider.Value; + ((TwoAmountsConfigToken)theEffectToken).Amount2 = amount2Slider.Value; + } + + #region Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.amount1Slider = new System.Windows.Forms.TrackBar(); + this.amount1UpDown = new System.Windows.Forms.NumericUpDown(); + this.amount1Reset = new System.Windows.Forms.Button(); + this.amount2Reset = new System.Windows.Forms.Button(); + this.amount2UpDown = new System.Windows.Forms.NumericUpDown(); + this.amount2Slider = new System.Windows.Forms.TrackBar(); + this.amount1Header = new PaintDotNet.HeaderLabel(); + this.amount2Header = new PaintDotNet.HeaderLabel(); + ((System.ComponentModel.ISupportInitialize)(this.amount1Slider)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount1UpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount2UpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount2Slider)).BeginInit(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(101, 151); + this.okButton.Size = new System.Drawing.Size(81, 23); + this.okButton.Name = "okButton"; + this.okButton.TabIndex = 6; + this.okButton.Click += new System.EventHandler(this.OnOkButtonClicked); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(188, 151); + this.cancelButton.Size = new System.Drawing.Size(81, 23); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 7; + this.cancelButton.Click += new System.EventHandler(this.OnCancelButtonClicked); + // + // amount1Slider + // + this.amount1Slider.LargeChange = 20; + this.amount1Slider.Location = new System.Drawing.Point(1, 25); + this.amount1Slider.Maximum = 100; + this.amount1Slider.Minimum = -100; + this.amount1Slider.Name = "amount1Slider"; + this.amount1Slider.Size = new System.Drawing.Size(175, 42); + this.amount1Slider.TabIndex = 0; + this.amount1Slider.TickFrequency = 10; + this.amount1Slider.ValueChanged += new System.EventHandler(this.amount1Slider_ValueChanged); + // + // amount1UpDown + // + this.amount1UpDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.amount1UpDown.Location = new System.Drawing.Point(188, 25); + this.amount1UpDown.Minimum = new System.Decimal(new int[] { + 100, + 0, + 0, + -2147483648}); + this.amount1UpDown.Name = "amount1UpDown"; + this.amount1UpDown.Size = new System.Drawing.Size(81, 20); + this.amount1UpDown.TabIndex = 1; + this.amount1UpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.amount1UpDown.Enter += new System.EventHandler(this.amount1UpDown_Enter); + this.amount1UpDown.ValueChanged += new System.EventHandler(this.amount1UpDown_ValueChanged); + this.amount1UpDown.Leave += new System.EventHandler(this.amount1UpDown_Leave); + // + // amount1Reset + // + this.amount1Reset.Location = new System.Drawing.Point(188, 50); + this.amount1Reset.Name = "amount1Reset"; + this.amount1Reset.Size = new System.Drawing.Size(81, 20); + this.amount1Reset.TabIndex = 2; + this.amount1Reset.Click += new System.EventHandler(this.amount1Reset_Click); + // + // amount2Reset + // + this.amount2Reset.Location = new System.Drawing.Point(188, 120); + this.amount2Reset.Name = "amount2Reset"; + this.amount2Reset.Size = new System.Drawing.Size(81, 20); + this.amount2Reset.TabIndex = 5; + this.amount2Reset.FlatStyle = FlatStyle.System; + this.amount2Reset.Click += new System.EventHandler(this.amount2Reset_Click); + // + // amount2UpDown + // + this.amount2UpDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.amount2UpDown.Location = new System.Drawing.Point(188, 95); + this.amount2UpDown.Minimum = new System.Decimal(new int[] { + 100, + 0, + 0, + -2147483648}); + this.amount2UpDown.Name = "amount2UpDown"; + this.amount2UpDown.Size = new System.Drawing.Size(81, 20); + this.amount2UpDown.TabIndex = 4; + this.amount2UpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.amount2UpDown.Enter += new System.EventHandler(this.amount2UpDown_Enter); + this.amount2UpDown.ValueChanged += new System.EventHandler(this.amount2UpDown_ValueChanged); + this.amount2UpDown.Leave += new System.EventHandler(this.amount2UpDown_Leave); + // + // amount2Slider + // + this.amount2Slider.LargeChange = 20; + this.amount2Slider.Location = new System.Drawing.Point(1, 95); + this.amount2Slider.Maximum = 100; + this.amount2Slider.Minimum = -100; + this.amount2Slider.Name = "amount2Slider"; + this.amount2Slider.Size = new System.Drawing.Size(175, 42); + this.amount2Slider.TabIndex = 3; + this.amount2Slider.TickFrequency = 10; + this.amount2Slider.ValueChanged += new System.EventHandler(this.amount2Slider_ValueChanged); + // + // amount1Header + // + this.amount1Header.Location = new System.Drawing.Point(6, 8); + this.amount1Header.Name = "amount1Header"; + this.amount1Header.Size = new System.Drawing.Size(271, 14); + this.amount1Header.TabIndex = 9; + this.amount1Header.TabStop = false; + this.amount1Header.Text = "Header 1"; + // + // amount2Header + // + this.amount2Header.Location = new System.Drawing.Point(6, 78); + this.amount2Header.Name = "amount2Header"; + this.amount2Header.Size = new System.Drawing.Size(271, 14); + this.amount2Header.TabIndex = 10; + this.amount2Header.TabStop = false; + this.amount2Header.Text = "Header 2"; + // + // TwoAmountsConfigDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(275, 180); + this.Controls.Add(this.amount2Header); + this.Controls.Add(this.amount1Header); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.amount2Reset); + this.Controls.Add(this.amount2UpDown); + this.Controls.Add(this.amount2Slider); + this.Controls.Add(this.amount1Reset); + this.Controls.Add(this.amount1UpDown); + this.Controls.Add(this.amount1Slider); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "TwoAmountsConfigDialog"; + this.Controls.SetChildIndex(this.amount1Slider, 0); + this.Controls.SetChildIndex(this.amount1UpDown, 0); + this.Controls.SetChildIndex(this.amount1Reset, 0); + this.Controls.SetChildIndex(this.amount2Slider, 0); + this.Controls.SetChildIndex(this.amount2UpDown, 0); + this.Controls.SetChildIndex(this.amount2Reset, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.amount1Header, 0); + this.Controls.SetChildIndex(this.amount2Header, 0); + ((System.ComponentModel.ISupportInitialize)(this.amount1Slider)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount1UpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount2UpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.amount2Slider)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + protected override void OnLoad(EventArgs e) + { + amount1UpDown.Select(); + amount1UpDown.Select(0, amount1UpDown.Text.Length); + base.OnLoad(e); + } + + protected virtual void OnOkButtonClicked(object sender, System.EventArgs e) + { + amount1UpDown_ValueChanged(sender, e); + amount2UpDown_ValueChanged(sender, e); + this.DialogResult = DialogResult.OK; + this.Close(); + } + + protected virtual void OnCancelButtonClicked(object sender, System.EventArgs e) + { + this.Close(); + } + + private void amount1Slider_ValueChanged(object sender, System.EventArgs e) + { + if (amount1UpDown.Value != (decimal)amount1Slider.Value) + { + amount1UpDown.Value = (decimal)amount1Slider.Value; + FinishTokenUpdate(); + } + } + + private void amount1UpDown_ValueChanged(object sender, System.EventArgs e) + { + if (amount1Slider.Value != (int)amount1UpDown.Value) + { + amount1Slider.Value = (int)amount1UpDown.Value; + FinishTokenUpdate(); + } + } + + private void amount1UpDown_Enter(object sender, System.EventArgs e) + { + amount1UpDown.Select(0, amount1UpDown.Text.Length); + } + + private void amount1UpDown_Leave(object sender, System.EventArgs e) + { + Utility.ClipNumericUpDown(amount1UpDown); + + if (Utility.CheckNumericUpDown(amount1UpDown)) + { + amount1UpDown.Value = decimal.Parse(amount1UpDown.Text); + } + } + + private void amount2Slider_ValueChanged(object sender, System.EventArgs e) + { + if (amount2UpDown.Value != (decimal)amount2Slider.Value) + { + amount2UpDown.Value = (decimal)amount2Slider.Value; + FinishTokenUpdate(); + } + } + + private void amount2UpDown_ValueChanged(object sender, System.EventArgs e) + { + if (amount2Slider.Value != (int)amount2UpDown.Value) + { + amount2Slider.Value = (int)amount2UpDown.Value; + FinishTokenUpdate(); + } + } + + private void amount2UpDown_Enter(object sender, System.EventArgs e) + { + amount2UpDown.Select(0, amount2UpDown.Text.Length); + } + + private void amount2UpDown_Leave(object sender, System.EventArgs e) + { + Utility.ClipNumericUpDown(amount2UpDown); + + if (Utility.CheckNumericUpDown(amount2UpDown)) + { + amount2UpDown.Value = decimal.Parse(amount2UpDown.Text); + } + } + + private void amount2Reset_Click(object sender, System.EventArgs e) + { + this.amount2Slider.Value = amount2Default; + } + + private void amount1Reset_Click(object sender, System.EventArgs e) + { + this.amount1Slider.Value = amount1Default; + } + } +} + diff --git a/src/Effects/TwoAmountsConfigDialog.resx b/src/Effects/TwoAmountsConfigDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Effects/TwoAmountsConfigToken.cs b/src/Effects/TwoAmountsConfigToken.cs new file mode 100644 index 0000000..5c86df1 --- /dev/null +++ b/src/Effects/TwoAmountsConfigToken.cs @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Effects +{ + public class TwoAmountsConfigToken + : EffectConfigToken + { + private int amount1; + public int Amount1 + { + get + { + return amount1; + } + + set + { + amount1 = value; + } + } + + private int amount2; + public int Amount2 + { + get + { + return amount2; + } + + set + { + amount2 = value; + } + } + + public override object Clone() + { + return new TwoAmountsConfigToken(this); + } + + public TwoAmountsConfigToken(int amount1, int amount2) + { + this.amount1 = amount1; + this.amount2 = amount2; + } + + public TwoAmountsConfigToken(TwoAmountsConfigToken copyMe) + : base(copyMe) + { + this.amount1 = copyMe.amount1; + this.amount2 = copyMe.amount2; + } + } +} diff --git a/src/Effects/UnfocusEffect.cs b/src/Effects/UnfocusEffect.cs new file mode 100644 index 0000000..ff5a3d9 --- /dev/null +++ b/src/Effects/UnfocusEffect.cs @@ -0,0 +1,129 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Drawing; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.Effects +{ + public sealed class UnfocusEffect + : LocalHistogramEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("UnfocusEffect.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.UnfocusEffectIcon.png"); + } + } + + public UnfocusEffect() + : base(StaticName, + StaticImage.Reference, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Radius = 0 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Radius, 4, 1, 200)); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("UnfocusEffect.ConfigDialog.AmountLabel")); + + // TODO: units label + //acd.SliderUnitsName = PdnResources.GetString("UnfocusEffect.ConfigDialog.UnitsLabel"); + + return configUI; + } + + private int radius; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.radius = newToken.GetProperty(PropertyNames.Radius).Value; + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + public unsafe override ColorBgra ApplyWithAlpha(ColorBgra src, int area, int sum, int* hb, int* hg, int* hr) + { + //each slot of the histgram can contain up to area * 255. This will overflow an int when area > 32k + if (area < 32768) + { + int b = 0; + int g = 0; + int r = 0; + + for (int i = 1; i < 256; ++i) + { + b += i * hb[i]; + g += i * hg[i]; + r += i * hr[i]; + } + + int alpha = sum / area; + int div = area * 255; + + return ColorBgra.FromBgraClamped(b / div, g / div, r / div, alpha); + } + else //use a long if an int will overflow. + { + long b = 0; + long g = 0; + long r = 0; + + for (long i = 1; i < 256; ++i) + { + b += i * hb[i]; + g += i * hg[i]; + r += i * hr[i]; + } + + int alpha = sum / area; + int div = area * 255; + + return ColorBgra.FromBgraClamped(b / div, g / div, r / div, alpha); + } + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + foreach (Rectangle rect in rois) + { + RenderRectWithAlpha(this.radius, SrcArgs.Surface, DstArgs.Surface, rect); + } + } + } +} diff --git a/src/Effects/VignetteEffect.cs b/src/Effects/VignetteEffect.cs new file mode 100644 index 0000000..4167e84 --- /dev/null +++ b/src/Effects/VignetteEffect.cs @@ -0,0 +1,194 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2007,2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Drawing; +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; + +namespace PaintDotNet.Effects +{ + public sealed class VignetteEffect + : InternalPropertyBasedEffect + { + public VignetteEffect() + : base(PdnResources.GetString("VignetteEffect.Name"), + PdnResources.GetImageResource("Icons.VignetteEffectIcon.png").Reference, + SubmenuNames.Photo, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Offset, + Amount, + Radius, + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List properties = new List(); + + properties.Add(new DoubleVectorProperty(PropertyNames.Offset, + Pair.Create(0.0, 0.0), Pair.Create(-1.0, -1.0), Pair.Create(1.0, 1.0))); + + //somewhat arbitary limits... + properties.Add(new DoubleProperty(PropertyNames.Radius, 0.5, 0.1, 4.0)); + + properties.Add(new DoubleProperty(PropertyNames.Amount, 1.0, 0.0, 1.0)); + + return new PropertyCollection(properties); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo info = PropertyBasedEffect.CreateDefaultConfigUI(props); + + info.SetPropertyControlValue( + PropertyNames.Offset, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("VignetteEffect.ConfigDialog.CenterLabel")); + + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeX, 0.05); + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeX, 0.25); + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementX, 0.01); + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeY, 0.05); + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeY, 0.25); + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementY, 0.01); + + // thumbnail/preview + Rectangle selection = this.EnvironmentParameters.GetSelection(base.EnvironmentParameters.SourceSurface.Bounds).GetBoundsInt(); + ImageResource propertyValue = ImageResource.FromImage(base.EnvironmentParameters.SourceSurface.CreateAliasedBitmap(selection)); + info.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.StaticImageUnderlay, propertyValue); + + info.SetPropertyControlValue( + PropertyNames.Radius, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("VignetteEffect.ConfigDialog.RadiusLabel")); + + info.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.UseExponentialScale, true); + info.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.SliderLargeChange, 0.25); + info.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.SliderSmallChange, 0.05); + info.SetPropertyControlValue(PropertyNames.Radius, ControlInfoPropertyNames.UpDownIncrement, 0.01); + + info.SetPropertyControlValue( + PropertyNames.Amount, + ControlInfoPropertyNames.DisplayName, + PdnResources.GetString("VignetteEffect.ConfigDialog.DensityLabel")); + + info.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.UseExponentialScale, true); + info.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.SliderLargeChange, 0.25); + info.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.SliderSmallChange, 0.05); + info.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.UpDownIncrement, 0.01); + + return info; + } + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.offset = newToken.GetProperty(PropertyNames.Offset).Value; + this.amount = newToken.GetProperty(PropertyNames.Amount).Value; + this.amount1 = 1d - amount; + + Rectangle bounds = this.EnvironmentParameters.GetSelection(srcArgs.Surface.Bounds).GetBoundsInt(); + + double width = bounds.Width; + double height = bounds.Height; + this.xCenterOffset = bounds.Left + (width * (1d + offset.First) * 0.5d); + this.yCenterOffset = bounds.Top + (height * (1d + offset.Second) * 0.5d); + + this.radius = Math.Max(width, height) * 0.5d; + this.radius *= newToken.GetProperty(PropertyNames.Radius).Value; + this.radius *= radius; + this.radiusR = Math.PI / (8 * radius); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + private double amount; + private double amount1; + private double radius; + private double radiusR; + + private double xCenterOffset; + private double yCenterOffset; + + private Pair offset = new Pair(0, 0); + + protected unsafe override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + for (int ri = startIndex; ri < startIndex + length; ++ri) + { + Rectangle rect = renderRects[ri]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + double iy2 = y - this.yCenterOffset; + iy2 *= iy2; + + ColorBgra* srcPtr = SrcArgs.Surface.GetPointAddress(rect.Left, y); + ColorBgra* dstPtr = DstArgs.Surface.GetPointAddress(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + double ix = x - this.xCenterOffset; + double d = (iy2 + (ix * ix)) * radiusR; + double factor = Math.Cos(d); + + if (factor <= 0 || d > Math.PI) + { + dstPtr->R = (byte)(0.5 + (255 * SrgbUtility.ToSrgbClamped(SrgbUtility.ToLinear(srcPtr->R) * amount1))); + dstPtr->G = (byte)(0.5 + (255 * SrgbUtility.ToSrgbClamped(SrgbUtility.ToLinear(srcPtr->G) * amount1))); + dstPtr->B = (byte)(0.5 + (255 * SrgbUtility.ToSrgbClamped(SrgbUtility.ToLinear(srcPtr->B) * amount1))); + dstPtr->A = srcPtr->A; + } + else + { + factor *= factor; + factor *= factor; + factor = amount1 + (amount * factor); + dstPtr->R = (byte)(0.5 + (255 * SrgbUtility.ToSrgbClamped(SrgbUtility.ToLinear(srcPtr->R) * factor))); + dstPtr->G = (byte)(0.5 + (255 * SrgbUtility.ToSrgbClamped(SrgbUtility.ToLinear(srcPtr->G) * factor))); + dstPtr->B = (byte)(0.5 + (255 * SrgbUtility.ToSrgbClamped(SrgbUtility.ToLinear(srcPtr->B) * factor))); + dstPtr->A = srcPtr->A; + } + + ++dstPtr; + ++srcPtr; + } + } + } + } + } +} diff --git a/src/Effects/WarpEdgeBehavior.cs b/src/Effects/WarpEdgeBehavior.cs new file mode 100644 index 0000000..b3b3274 --- /dev/null +++ b/src/Effects/WarpEdgeBehavior.cs @@ -0,0 +1,45 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2006-2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +namespace PaintDotNet.Effects +{ + public enum WarpEdgeBehavior + { + Clamp, + Wrap, + Reflect, + Primary, + Secondary, + Transparent, + Original, + } +} diff --git a/src/Effects/WarpEffectBase.cs b/src/Effects/WarpEffectBase.cs new file mode 100644 index 0000000..80e5ffa --- /dev/null +++ b/src/Effects/WarpEffectBase.cs @@ -0,0 +1,301 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Copyright (c) 2006-2008 Ed Harvey +// +// MIT License: http://www.opensource.org/licenses/mit-license.php +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.Effects +{ + public abstract class WarpEffectBase + : InternalPropertyBasedEffect + { + internal WarpEffectBase(string name, Image image, string submenuName, EffectFlags options) + : base(name, image, submenuName, options) + { + } + + /// + /// The amount to offset the 'center' of the effect + /// + /// + /// The offset is scaled relative to the image size. Each part of the covers the range ±1. + /// The coordinates supplied to are relative to the calcuated logical center. + /// + protected Pair Offset + { + get + { + return this.offset; + } + + set + { + this.offset = value; + } + } + + protected WarpEdgeBehavior EdgeBehavior + { + get + { + return edgeBehavior; + } + + set + { + edgeBehavior = value; + } + } + + protected int Quality + { + get + { + return quality; + } + + set + { + quality = value; + } + } + + /// + /// The radius (in pixels) of the largest circle that can completely fit within the effect selection bounds + /// + protected double DefaultRadius + { + get + { + return this.defaultRadius; + } + } + + /// + /// The square of the DefaultRadius + /// + protected double DefaultRadius2 + { + get + { + return this.defaultRadius2; + } + } + + /// + /// The reciprical of the DefaultRadius + /// + protected double DefaultRadiusR + { + get + { + return this.defaultRadiusR; + } + } + + private Pair offset = new Pair(0, 0); + private WarpEdgeBehavior edgeBehavior = WarpEdgeBehavior.Wrap; + private int quality = 2; + + private double defaultRadius; + private double defaultRadius2; + private double defaultRadiusR; + + private double width; + private double height; + private double xCenterOffset; + private double yCenterOffset; + + protected virtual void OnSetRenderInfo2(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + } + + protected override sealed void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + Rectangle selection = this.EnvironmentParameters.GetSelection(srcArgs.Bounds).GetBoundsInt(); + + this.defaultRadius = Math.Min(selection.Width, selection.Height) * 0.5; + this.defaultRadius2 = this.defaultRadius * this.defaultRadius; + this.defaultRadiusR = 1.0 / this.defaultRadius; + this.width = selection.Width; + this.height = selection.Height; + + OnSetRenderInfo2(newToken, dstArgs, srcArgs); + + this.xCenterOffset = selection.Left + (this.width * (1.0 + this.offset.First) * 0.5); + this.yCenterOffset = selection.Top + (this.height * (1.0 + this.offset.Second) * 0.5); + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] renderRects, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + + ColorBgra colPrimary = EnvironmentParameters.PrimaryColor; + ColorBgra colSecondary = EnvironmentParameters.SecondaryColor; + ColorBgra colTransparent = ColorBgra.Transparent; + + int aaSampleCount = quality * quality; + PointF* aaPoints = stackalloc PointF[aaSampleCount]; + Utility.GetRgssOffsets(aaPoints, aaSampleCount, quality); + ColorBgra* samples = stackalloc ColorBgra[aaSampleCount]; + + TransformData td; + + for (int n = startIndex; n < startIndex + length; ++n) + { + Rectangle rect = renderRects[n]; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + ColorBgra* dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + + double relativeY = y - this.yCenterOffset; + + for (int x = rect.Left; x < rect.Right; x++) + { + double relativeX = x - this.xCenterOffset; + + int sampleCount = 0; + + for (int p = 0; p < aaSampleCount; ++p) + { + td.X = relativeX + aaPoints[p].X; + td.Y = relativeY - aaPoints[p].Y; + + InverseTransform(ref td); + + float sampleX = (float)(td.X + this.xCenterOffset); + float sampleY = (float)(td.Y + this.yCenterOffset); + + ColorBgra sample = colPrimary; + + if (IsOnSurface(src, sampleX, sampleY)) + { + sample = src.GetBilinearSample(sampleX, sampleY); + } + else + { + switch (this.edgeBehavior) + { + case WarpEdgeBehavior.Clamp: + sample = src.GetBilinearSampleClamped(sampleX, sampleY); + break; + + case WarpEdgeBehavior.Wrap: + sample = src.GetBilinearSampleWrapped(sampleX, sampleY); + break; + + case WarpEdgeBehavior.Reflect: + sample = src.GetBilinearSampleClamped( + ReflectCoord(sampleX, src.Width), + ReflectCoord(sampleY, src.Height)); + + break; + + case WarpEdgeBehavior.Primary: + sample = colPrimary; + break; + + case WarpEdgeBehavior.Secondary: + sample = colSecondary; + break; + + case WarpEdgeBehavior.Transparent: + sample = colTransparent; + break; + + case WarpEdgeBehavior.Original: + sample = src[x, y]; + break; + + default: + break; + } + } + + samples[sampleCount] = sample; + ++sampleCount; + } + + *dstPtr = ColorBgra.Blend(samples, sampleCount); + ++dstPtr; + } + } + } + } + + protected abstract void InverseTransform(ref TransformData data); + + protected struct TransformData + { + public double X; + public double Y; + } + + private static bool IsOnSurface(Surface src, float u, float v) + { + return (u >= 0 && u <= (src.Width - 1) && v >= 0 && v <= (src.Height - 1)); + } + + private static float ReflectCoord(float value, int max) + { + bool reflection = false; + + while (value < 0) + { + value += max; + reflection = !reflection; + } + + while (value > max) + { + value -= max; + reflection = !reflection; + } + + if (reflection) + { + value = max - value; + } + + return value; + } + } + +} diff --git a/src/Effects/ZoomBlurEffect.cs b/src/Effects/ZoomBlurEffect.cs new file mode 100644 index 0000000..43b02e1 --- /dev/null +++ b/src/Effects/ZoomBlurEffect.cs @@ -0,0 +1,180 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.IndirectUI; +using PaintDotNet.PropertySystem; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Effects +{ + public sealed class ZoomBlurEffect + : InternalPropertyBasedEffect + { + public static string StaticName + { + get + { + return PdnResources.GetString("ZoomBlurEffect.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.ZoomBlurEffect.png"); + } + } + + public ZoomBlurEffect() + : base(StaticName, + StaticImage.Reference, + SubmenuNames.Blurs, + EffectFlags.Configurable) + { + } + + public enum PropertyNames + { + Amount = 0, + Offset = 1 + } + + protected override PropertyCollection OnCreatePropertyCollection() + { + List props = new List(); + + props.Add(new Int32Property(PropertyNames.Amount, 10, 0, 100)); + + props.Add(new DoubleVectorProperty( + PropertyNames.Offset, + Pair.Create(0.0, 0.0), + Pair.Create(-2.0, -2.0), + Pair.Create(+2.0, +2.0))); + + return new PropertyCollection(props); + } + + protected override ControlInfo OnCreateConfigUI(PropertyCollection props) + { + ControlInfo configUI = CreateDefaultConfigUI(props); + + configUI.SetPropertyControlValue(PropertyNames.Amount, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("ZoomBlurEffect.ConfigDialog.AmountLabel")); + + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.DisplayName, PdnResources.GetString("ZoomBlurEffect.ConfigDialog.Offset")); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeX, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeX, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementX, 0.01); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderSmallChangeY, 0.05); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.SliderLargeChangeY, 0.25); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.UpDownIncrementY, 0.01); + + Surface sourceSurface = this.EnvironmentParameters.SourceSurface; + Bitmap bitmap = sourceSurface.CreateAliasedBitmap(); + ImageResource imageResource = ImageResource.FromImage(bitmap); + configUI.SetPropertyControlValue(PropertyNames.Offset, ControlInfoPropertyNames.StaticImageUnderlay, imageResource); + + return configUI; + } + + private int amount; + private Pair offset; + + protected override void OnSetRenderInfo(PropertyBasedEffectConfigToken newToken, RenderArgs dstArgs, RenderArgs srcArgs) + { + this.amount = newToken.GetProperty(PropertyNames.Amount).Value; + this.offset = newToken.GetProperty(PropertyNames.Offset).Value; + + base.OnSetRenderInfo(newToken, dstArgs, srcArgs); + } + + protected unsafe override void OnRender(Rectangle[] rois, int startIndex, int length) + { + Surface dst = DstArgs.Surface; + Surface src = SrcArgs.Surface; + long w = dst.Width; + long h = dst.Height; + long fox = (long)(dst.Width * this.offset.First * 32768.0); + long foy = (long)(dst.Height * this.offset.Second * 32768.0); + long fcx = fox + (w << 15); + long fcy = foy + (h << 15); + long fz = this.amount; + + const int n = 64; + + for (int r = startIndex; r < startIndex + length; ++r) + { + Rectangle rect = rois[r]; + + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ColorBgra *dstPtr = dst.GetPointAddressUnchecked(rect.Left, y); + ColorBgra *srcPtr = src.GetPointAddressUnchecked(rect.Left, y); + + for (int x = rect.Left; x < rect.Right; ++x) + { + long fx = (x << 16) - fcx; + long fy = (y << 16) - fcy; + + int sr = 0; + int sg = 0; + int sb = 0; + int sa = 0; + int sc = 0; + + sr += srcPtr->R * srcPtr->A; + sg += srcPtr->G * srcPtr->A; + sb += srcPtr->B * srcPtr->A; + sa += srcPtr->A; + ++sc; + + for (int i = 0; i < n; ++i) + { + fx -= ((fx >> 4) * fz) >> 10; + fy -= ((fy >> 4) * fz) >> 10; + + int u = (int)(fx + fcx + 32768 >> 16); + int v = (int)(fy + fcy + 32768 >> 16); + + if (src.IsVisible(u, v)) + { + ColorBgra* srcPtr2 = src.GetPointAddressUnchecked(u, v); + + sr += srcPtr2->R * srcPtr2->A; + sg += srcPtr2->G * srcPtr2->A; + sb += srcPtr2->B * srcPtr2->A; + sa += srcPtr2->A; + ++sc; + } + } + + if (sa != 0) + { + *dstPtr = ColorBgra.FromBgra( + Utility.ClampToByte(sb / sa), + Utility.ClampToByte(sg / sa), + Utility.ClampToByte(sr / sa), + Utility.ClampToByte(sa / sc)); + } + else + { + dstPtr->Bgra = 0; + } + + ++srcPtr; + ++dstPtr; + } + } + } + } + } +} diff --git a/src/ExecutedHistoryMementoEventArgs.cs b/src/ExecutedHistoryMementoEventArgs.cs new file mode 100644 index 0000000..a381a83 --- /dev/null +++ b/src/ExecutedHistoryMementoEventArgs.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal class ExecutedHistoryMementoEventArgs + : EventArgs + { + private HistoryMemento newHistoryMemento; + + public HistoryMemento NewHistoryMemento + { + get + { + return this.newHistoryMemento; + } + } + + public ExecutedHistoryMementoEventArgs(HistoryMemento newHistoryMemento) + { + this.newHistoryMemento = newHistoryMemento; + } + } +} diff --git a/src/ExecutedHistoryMementoEventHandler.cs b/src/ExecutedHistoryMementoEventHandler.cs new file mode 100644 index 0000000..637fe06 --- /dev/null +++ b/src/ExecutedHistoryMementoEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal delegate void ExecutedHistoryMementoEventHandler(object sender, ExecutedHistoryMementoEventArgs e); +} diff --git a/src/ExecutingHistoryMementoEventArgs.cs b/src/ExecutingHistoryMementoEventArgs.cs new file mode 100644 index 0000000..6dc9fa6 --- /dev/null +++ b/src/ExecutingHistoryMementoEventArgs.cs @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal class ExecutingHistoryMementoEventArgs + : EventArgs + { + private HistoryMemento historyMemento; + private bool mayAlterSuspendToolProperty; + private bool suspendTool; + + public HistoryMemento HistoryMemento + { + get + { + return this.historyMemento; + } + } + + public bool MayAlterSuspendTool + { + get + { + return this.mayAlterSuspendToolProperty; + } + } + + public bool SuspendTool + { + get + { + return this.suspendTool; + } + + set + { + if (!this.mayAlterSuspendToolProperty) + { + throw new InvalidOperationException("May not alter the SuspendTool property when MayAlterSuspendToolProperty is false"); + } + + this.suspendTool = value; + } + } + + public ExecutingHistoryMementoEventArgs(HistoryMemento historyMemento, bool mayAlterSuspendToolProperty, bool suspendTool) + { + this.historyMemento = historyMemento; + this.mayAlterSuspendToolProperty = mayAlterSuspendToolProperty; + this.suspendTool = suspendTool; + } + } +} diff --git a/src/ExecutingHistoryMementoEventHandler.cs b/src/ExecutingHistoryMementoEventHandler.cs new file mode 100644 index 0000000..a2fe8db --- /dev/null +++ b/src/ExecutingHistoryMementoEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal delegate void ExecutingHistoryMementoEventHandler(object sender, ExecutingHistoryMementoEventArgs e); +} diff --git a/src/FileTypes.cs b/src/FileTypes.cs new file mode 100644 index 0000000..a25e88b --- /dev/null +++ b/src/FileTypes.cs @@ -0,0 +1,191 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Provides static method and properties for obtaining all the FileType objects + /// responsible for loading and saving Document instances. Loads FileType plugins + /// too. + /// + internal sealed class FileTypes + { + private FileTypes() + { + } + + private static FileTypeCollection collection; + + public static FileTypeCollection GetFileTypes() + { + if (collection == null) + { + collection = LoadFileTypes(); + } + + return collection; + } + + private static bool IsInterfaceImplemented(Type derivedType, Type interfaceType) + { + return -1 != Array.IndexOf(derivedType.GetInterfaces(), interfaceType); + } + + private static Type[] GetFileTypeFactoriesFromAssembly(Assembly assembly) + { + List fileTypeFactories = new List(); + + foreach (Type type in assembly.GetTypes()) + { + if (IsInterfaceImplemented(type, typeof(IFileTypeFactory)) && !type.IsAbstract) + { + fileTypeFactories.Add(type); + } + } + + return fileTypeFactories.ToArray(); + } + + private static Type[] GetFileTypeFactoriesFromAssemblies(ICollection assemblies) + { + List allFactories = new List(); + + foreach (Assembly assembly in assemblies) + { + Type[] factories; + + try + { + factories = GetFileTypeFactoriesFromAssembly(assembly); + } + + catch (Exception) + { + continue; + } + + foreach (Type type in factories) + { + allFactories.Add(type); + } + } + + return allFactories.ToArray(); + } + + private static FileTypeCollection LoadFileTypes() + { + List assemblies = new List(); + + // add the built-in IFileTypeFactory house + assemblies.Add(typeof(FileType).Assembly); + + // enumerate the assemblies inside the FileTypes directory + string homeDir = Path.GetDirectoryName(Application.ExecutablePath); + string fileTypesDir = Path.Combine(homeDir, "FileTypes"); + bool dirExists; + + try + { + DirectoryInfo dirInfo = new DirectoryInfo(fileTypesDir); + dirExists = dirInfo.Exists; + } + + catch (Exception) + { + dirExists = false; + } + + if (dirExists) + { + foreach (string fileName in Directory.GetFiles(fileTypesDir, "*.dll")) + { + bool success; + Assembly pluginAssembly = null; + + try + { + pluginAssembly = Assembly.LoadFrom(fileName); + success = true; + } + + catch (Exception) + { + success = false; + } + + if (success) + { + assemblies.Add(pluginAssembly); + } + } + } + + // Get all the IFileTypeFactory implementations + Type[] fileTypeFactories = GetFileTypeFactoriesFromAssemblies(assemblies); + List allFileTypes = new List(10); + + foreach (Type type in fileTypeFactories) + { + ConstructorInfo ci = type.GetConstructor(System.Type.EmptyTypes); + IFileTypeFactory factory; + + try + { + factory = (IFileTypeFactory)ci.Invoke(null); + } + + catch (Exception) + { +#if DEBUG + throw; +#else + continue; +#endif + } + + FileType[] fileTypes; + + try + { + fileTypes = factory.GetFileTypeInstances(); + } + + catch (Exception) + { +#if DEBUG + throw; +#else + continue; +#endif + } + + if (fileTypes != null) + { + foreach (FileType fileType in fileTypes) + { + allFileTypes.Add(fileType); + } + } + } + + return new FileTypeCollection(allFileTypes); + } + } +} diff --git a/src/FlipType.cs b/src/FlipType.cs new file mode 100644 index 0000000..91ae70b --- /dev/null +++ b/src/FlipType.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum FlipType + { + Horizontal, + Vertical + } +} diff --git a/src/FloatingToolForm.cs b/src/FloatingToolForm.cs new file mode 100644 index 0000000..6932b31 --- /dev/null +++ b/src/FloatingToolForm.cs @@ -0,0 +1,368 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + // TODO: move + internal delegate bool CmdKeysEventHandler(object sender, ref Message msg, Keys keyData); + + internal class FloatingToolForm + : PdnBaseForm, + ISnapObstacleHost + { + private System.ComponentModel.IContainer components = null; + + private ControlEventHandler controlAddedDelegate; + private ControlEventHandler controlRemovedDelegate; + private KeyEventHandler keyUpDelegate; + private SnapObstacleController snapObstacle; + + public SnapObstacle SnapObstacle + { + get + { + if (this.snapObstacle == null) + { + int distancePadding = UI.GetExtendedFrameBounds(this); + int distance = SnapObstacle.DefaultSnapDistance + distancePadding; + + this.snapObstacle = new SnapObstacleController(this.Name, this.Bounds, SnapRegion.Exterior, false, SnapObstacle.DefaultSnapProximity, distance); + this.snapObstacle.BoundsChangeRequested += SnapObstacle_BoundsChangeRequested; + } + + return this.snapObstacle; + } + } + + private void SnapObstacle_BoundsChangeRequested(object sender, HandledEventArgs e) + { + this.Bounds = e.Data; + } + + /// + /// Occurs when it is appropriate for the parent to steal focus. + /// + public event EventHandler RelinquishFocus; + protected virtual void OnRelinquishFocus() + { + // Only relinquish focus if we have it in the first place + if (MenuStripEx.IsAnyMenuActive) + { + return; + } + + if (RelinquishFocus != null) + { + RelinquishFocus(this, EventArgs.Empty); + } + } + + public FloatingToolForm() + { + this.KeyPreview = true; + controlAddedDelegate = new ControlEventHandler(ControlAddedHandler); + controlRemovedDelegate = new ControlEventHandler(ControlRemovedHandler); + keyUpDelegate = new KeyEventHandler(KeyUpHandler); + + this.ControlAdded += controlAddedDelegate; // we don't override OnControlAdded so we can re-use the method (see code below for ControlAdded) + this.ControlRemoved += controlRemovedDelegate; + + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + try + { + SystemLayer.UserSessions.SessionChanged += new EventHandler(UserSessions_SessionChanged); + Microsoft.Win32.SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged); + } + + catch (Exception ex) + { + Tracing.Ping("Exception while signing up for some system events: " + ex.ToString()); + } + } + + private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) + { + if (Visible && IsShown) + { + EnsureFormIsOnScreen(); + } + } + + private void UserSessions_SessionChanged(object sender, EventArgs e) + { + if (Visible && IsShown) + { + EnsureFormIsOnScreen(); + } + } + + protected override void OnClick(EventArgs e) + { + OnRelinquishFocus(); + base.OnClick(e); + } + + public event CmdKeysEventHandler ProcessCmdKeyEvent; + + protected override bool ProcessCmdKey(ref Message msg, Keys keyData) + { + bool result = false; + + if (Utility.IsArrowKey(keyData)) + { + KeyEventArgs kea = new KeyEventArgs(keyData); + + switch (msg.Msg) + { + case 0x100: // WM_KEYDOWN: + this.OnKeyDown(kea); + return kea.Handled; + + /* + case NativeMethods.WmConstants.WM_KEYUP: + this.OnKeyUp(kea); + return kea.Handled; + */ + } + } + else + { + if (ProcessCmdKeyEvent != null) + { + result = ProcessCmdKeyEvent(this, ref msg, keyData); + } + } + + if (!result) + { + result = base.ProcessCmdKey(ref msg, keyData); + } + + return result; + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + + try + { + SystemLayer.UserSessions.SessionChanged -= new EventHandler(UserSessions_SessionChanged); + Microsoft.Win32.SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged); + } + + catch (Exception) + { + // Ignore any errors + } + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + // + // FloatingToolForm + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(292, 271); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FloatingToolForm"; + this.ShowInTaskbar = false; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.ForceActiveTitleBar = true; + } + #endregion + + private void ControlAddedHandler(object sender, ControlEventArgs e) + { + e.Control.ControlAdded += controlAddedDelegate; + e.Control.ControlRemoved += controlRemovedDelegate; + e.Control.KeyUp += keyUpDelegate; + } + + private void ControlRemovedHandler(object sender, ControlEventArgs e) + { + e.Control.ControlAdded -= controlAddedDelegate; + e.Control.ControlRemoved -= controlRemovedDelegate; + e.Control.KeyUp -= keyUpDelegate; + } + + private void KeyUpHandler(object sender, KeyEventArgs e) + { + if (!e.Handled) + { + this.OnKeyUp(e); + } + } + + private void UpdateSnapObstacleBounds() + { + if (this.snapObstacle != null) + { + this.snapObstacle.SetBounds(this.Bounds); + } + } + + private void UpdateParking() + { + if (this.FormBorderStyle == FormBorderStyle.Fixed3D || + this.FormBorderStyle == FormBorderStyle.FixedDialog || + this.FormBorderStyle == FormBorderStyle.FixedSingle || + this.FormBorderStyle == FormBorderStyle.FixedToolWindow) + { + ISnapManagerHost ismh = this.Owner as ISnapManagerHost; + + if (ismh != null) + { + SnapManager mySM = ismh.SnapManager; + mySM.ReparkObstacle(this); + } + } + } + + protected override void OnVisibleChanged(EventArgs e) + { + if (Visible) + { + EnsureFormIsOnScreen(); + } + + base.OnVisibleChanged(e); + } + + protected override void OnResizeBegin(EventArgs e) + { + UpdateSnapObstacleBounds(); + UpdateParking(); + base.OnResizeBegin(e); + } + + protected override void OnResize(EventArgs e) + { + UpdateSnapObstacleBounds(); + base.OnResize(e); + UpdateParking(); + } + + protected override void OnResizeEnd(EventArgs e) + { + this.moving = false; + UpdateSnapObstacleBounds(); + UpdateParking(); + base.OnResizeEnd(e); + OnRelinquishFocus(); + } + + protected override void OnSizeChanged(EventArgs e) + { + UpdateSnapObstacleBounds(); + UpdateParking(); + base.OnSizeChanged(e); + } + + private Size movingCursorDelta = Size.Empty; // dx,dy from mousex,y to bounds.Location + private bool moving = false; + + protected override void OnMoving(MovingEventArgs mea) + { + ISnapManagerHost snapHost = this.Owner as ISnapManagerHost; + + if (snapHost != null) + { + SnapManager sm = snapHost.SnapManager; + + // Make sure the window titlebar always follows a constant distance from the mouse cursor + // Otherwise the window may "slip" as it snaps and unsnaps + if (!this.moving) + { + this.movingCursorDelta = new Size( + Cursor.Position.X - mea.Rectangle.X, + Cursor.Position.Y - mea.Rectangle.Y); + + this.moving = true; + } + + mea.Rectangle = new Rectangle( + Cursor.Position.X - this.movingCursorDelta.Width, + Cursor.Position.Y - this.movingCursorDelta.Height, + mea.Rectangle.Width, + mea.Rectangle.Height); + + this.snapObstacle.SetBounds(mea.Rectangle); + + Point pt = mea.Rectangle.Location; + Point newPt = sm.AdjustObstacleDestination(this.SnapObstacle, pt); + Rectangle newRect = new Rectangle(newPt, mea.Rectangle.Size); + + this.snapObstacle.SetBounds(newRect); + + mea.Rectangle = newRect; + } + + base.OnMoving(mea); + } + + protected override void OnMove(EventArgs e) + { + UpdateSnapObstacleBounds(); + base.OnMove(e); + } + + protected override void OnEnabledChanged(EventArgs e) + { + if (this.snapObstacle != null) + { + this.snapObstacle.Enabled = this.Enabled; + } + + base.OnEnabledChanged(e); + } + + protected override void OnLoad(EventArgs e) + { + ISnapManagerHost smh = this.Owner as ISnapManagerHost; + + if (smh != null) + { + smh.SnapManager.AddSnapObstacle(this); + } + + base.OnLoad(e); + } + } +} diff --git a/src/FloatingToolForm.resx b/src/FloatingToolForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/FloodMode.cs b/src/FloodMode.cs new file mode 100644 index 0000000..873e693 --- /dev/null +++ b/src/FloodMode.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum FloodMode + { + Local = 0, + Global + } +} diff --git a/src/FontInfo.cs b/src/FontInfo.cs new file mode 100644 index 0000000..973e9c8 --- /dev/null +++ b/src/FontInfo.cs @@ -0,0 +1,258 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using System.Reflection; +using System.Runtime.Serialization; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Carries information about the Font details that we support. + /// Does not carry text alignment information. + /// + [Serializable] + internal class FontInfo + : IDisposable, + ISerializable, + ICloneable + { + private FontFamily family; + private float size; + private FontStyle style; + + private const string fontFamilyNameTag = "family.Name"; + private const string sizeTag = "size"; + private const string styleTag = "style"; + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(fontFamilyNameTag, this.family.Name); + info.AddValue(sizeTag, this.size); + info.AddValue(styleTag, (int)this.style); + } + + protected FontInfo(SerializationInfo info, StreamingContext context) + { + string familyName = info.GetString(fontFamilyNameTag); + + try + { + this.family = new FontFamily(familyName); + } + + catch (ArgumentException) + { + this.family = FontFamily.GenericSansSerif; + } + + this.size = info.GetSingle(sizeTag); + + int styleInt = info.GetInt32(styleTag); + this.style = (FontStyle)styleInt; + } + + public static bool operator ==(FontInfo lhs, FontInfo rhs) + { + if ((lhs.family == rhs.family) && (lhs.size == rhs.size) && (lhs.style == rhs. style)) + { + return true; + } + else + { + return false; + } + } + + public static bool operator!= (FontInfo lhs, FontInfo rhs) + { + return !(lhs == rhs); + } + + public override bool Equals(object obj) + { + return this == (FontInfo)obj; + } + + public override int GetHashCode() + { + return unchecked(family.GetHashCode() + size.GetHashCode() + style.GetHashCode()); + } + + /// + /// Constructs an instance of the FontInfo class. + /// + /// The FontFamily to associate with this class. The FontInfo instance takes ownership of this FontFamily instance: do not call Dispose() on it. + /// The Size of the font. + /// The FontStyle of the font. + public FontInfo(FontFamily fontFamily, float size, FontStyle fontStyle) + { + this.FontFamily = fontFamily; + this.Size = size; + this.FontStyle = fontStyle; + } + + ~FontInfo() + { + Dispose(false); + } + + /// + /// The FontFamily property gets and sets the font family. + /// + public FontFamily FontFamily + { + get + { + return family; + } + + set + { + family = value; + } + } + + /// + /// The Size property gets and sets the size of the text. + /// + public float Size + { + get + { + return this.size; + } + + set + { + this.size = value; + } + } + + /// + /// The FontStyle property gets and sets the font style to bold and or italic and or underline. + /// + public FontStyle FontStyle + { + get + { + return this.style; + } + + set + { + this.style = value; + } + } + + public bool CanCreateFont() + { + if (this.FontFamily.IsStyleAvailable(this.FontStyle)) // check to see if style is available in family + { + return true; + } + else // find the style it is available in (coersion) + { + if (this.FontFamily.IsStyleAvailable(FontStyle.Regular)) + { + return true; + } + else if (this.FontFamily.IsStyleAvailable(FontStyle.Italic)) + { + return true; + } + else if (this.FontFamily.IsStyleAvailable(FontStyle.Bold)) + { + return true; + } + else if (this.FontFamily.IsStyleAvailable(FontStyle.Underline)) + { + return true; + } + } + + return false; + } + + public Font CreateFont() + { + // it may be possible that the font style may not be available in the font returned. But I am not sure. + // I am checking for this possibility in the textconfig widget getFontInfo fucntion + if (this.FontFamily.IsStyleAvailable(this.FontStyle)) // check to see if style is availble in family + { + return new Font(this.FontFamily, (float)Math.Max(1.0, (double)this.Size), this.FontStyle); + } + else // find the style it is available in (coersion) + { + FontStyle fs = new FontStyle(); + + if (this.FontFamily.IsStyleAvailable(FontStyle.Regular)) + { + fs = FontStyle.Regular; + } + else if (this.FontFamily.IsStyleAvailable(FontStyle.Italic)) + { + fs = FontStyle.Italic; + } + else if (this.FontFamily.IsStyleAvailable(FontStyle.Bold)) + { + fs = FontStyle.Bold; + } + else if (this.FontFamily.IsStyleAvailable(FontStyle.Underline)) + { + fs = FontStyle.Underline; + } + + try + { + return new Font(this.FontFamily, (float)Math.Max(1.0, (double)this.Size), fs); + } + + catch + { + return null; + } + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.family != null) + { + this.family.Dispose(); + this.family = null; + } + } + } + + public FontInfo Clone() + { + return new FontInfo(this.family, this.size, this.style); + } + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/GeneratedCode/GeneratedCode.vcproj b/src/GeneratedCode/GeneratedCode.vcproj new file mode 100644 index 0000000..cceab70 --- /dev/null +++ b/src/GeneratedCode/GeneratedCode.vcproj @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GradientInfo.cs b/src/GradientInfo.cs new file mode 100644 index 0000000..88ce1b2 --- /dev/null +++ b/src/GradientInfo.cs @@ -0,0 +1,99 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace PaintDotNet +{ + [Serializable] + internal sealed class GradientInfo + : ICloneable + { + private GradientType gradientType; + private bool alphaOnly; + + public GradientType GradientType + { + get + { + return this.gradientType; + } + } + + public bool AlphaOnly + { + get + { + return this.alphaOnly; + } + } + + public GradientRenderer CreateGradientRenderer() + { + UserBlendOps.NormalBlendOp normalBlendOp = new UserBlendOps.NormalBlendOp(); + + switch (this.gradientType) + { + case GradientType.LinearClamped: + return new GradientRenderers.LinearClamped(this.alphaOnly, normalBlendOp); + + case GradientType.LinearReflected: + return new GradientRenderers.LinearReflected(this.alphaOnly, normalBlendOp); + + case GradientType.LinearDiamond: + return new GradientRenderers.LinearDiamond(this.alphaOnly, normalBlendOp); + + case GradientType.Radial: + return new GradientRenderers.Radial(this.alphaOnly, normalBlendOp); + + case GradientType.Conical: + return new GradientRenderers.Conical(this.alphaOnly, normalBlendOp); + + default: + throw new InvalidEnumArgumentException(); + } + } + + public override bool Equals(object obj) + { + GradientInfo asGI = obj as GradientInfo; + + if (asGI == null) + { + return false; + } + + return (asGI.GradientType == this.GradientType && asGI.AlphaOnly == this.AlphaOnly); + } + + public override int GetHashCode() + { + return unchecked(this.gradientType.GetHashCode() + this.alphaOnly.GetHashCode()); + } + + public GradientInfo(GradientType gradientType, bool alphaOnly) + { + this.gradientType = gradientType; + this.alphaOnly = alphaOnly; + } + + public GradientInfo Clone() + { + return new GradientInfo(this.gradientType, this.alphaOnly); + } + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/GradientType.cs b/src/GradientType.cs new file mode 100644 index 0000000..84af908 --- /dev/null +++ b/src/GradientType.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum GradientType + { + LinearClamped, + LinearReflected, + LinearDiamond, + Radial, + Conical + } +} diff --git a/src/GraphicsPathWrapper.cs b/src/GraphicsPathWrapper.cs new file mode 100644 index 0000000..c90b844 --- /dev/null +++ b/src/GraphicsPathWrapper.cs @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + /// + /// Encapsulates the data necessary to create a PdnGraphicsPath object + /// so that we can serialize one to the clipboard (or anywhere else + /// for that matter) + /// + [Serializable] + internal class GraphicsPathWrapper + { + private PointF[] points; + private byte[] types; + private FillMode fillMode; + + public PdnGraphicsPath CreateGraphicsPath() + { + return new PdnGraphicsPath(points, types, fillMode); + } + + public GraphicsPathWrapper(PdnGraphicsPath path) + { + points = (PointF[])path.PathPoints.Clone(); + types = (byte[])path.PathTypes.Clone(); + fillMode = path.FillMode; + } + } +} diff --git a/src/HistoryControl.cs b/src/HistoryControl.cs new file mode 100644 index 0000000..e340601 --- /dev/null +++ b/src/HistoryControl.cs @@ -0,0 +1,891 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Diagnostics; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal sealed class HistoryControl + : Control + { + private enum ItemType + { + Undo, + Redo + } + + private bool managedFocus = false; + private VScrollBar vScrollBar; + private HistoryStack historyStack = null; + private int itemHeight; + private int undoItemHighlight = -1; + private int redoItemHighlight = -1; + private int scrollOffset = 0; + private Point lastMouseClientPt = new Point(-1, -1); + private int ignoreScrollOffsetSet = 0; + + private void SuspendScrollOffsetSet() + { + ++this.ignoreScrollOffsetSet; + } + + private void ResumeScrollOffsetSet() + { + --this.ignoreScrollOffsetSet; + } + + public bool ManagedFocus + { + get + { + return this.managedFocus; + } + + set + { + this.managedFocus = value; + } + } + + public event EventHandler RelinquishFocus; + private void OnRelinquishFocus() + { + if (RelinquishFocus != null) + { + RelinquishFocus(this, EventArgs.Empty); + } + } + + private int ItemCount + { + get + { + if (this.historyStack == null) + { + return 0; + } + else + { + return this.historyStack.UndoStack.Count + this.historyStack.RedoStack.Count; + } + } + } + + public event EventHandler ScrollOffsetChanged; + private void OnScrollOffsetChanged() + { + this.vScrollBar.Value = Utility.Clamp(this.scrollOffset, this.vScrollBar.Minimum, this.vScrollBar.Maximum); + + if (ScrollOffsetChanged != null) + { + ScrollOffsetChanged(this, EventArgs.Empty); + } + } + + public int MinScrollOffset + { + get + { + return 0; + } + } + + public int MaxScrollOffset + { + get + { + return Math.Max(0, ViewHeight - ClientSize.Height); + } + } + + public int ScrollOffset + { + get + { + return this.scrollOffset; + } + + set + { + if (this.ignoreScrollOffsetSet <= 0) + { + int clampedOffset = Utility.Clamp(value, MinScrollOffset, MaxScrollOffset); + + if (this.scrollOffset != clampedOffset) + { + this.scrollOffset = clampedOffset; + OnScrollOffsetChanged(); + Invalidate(false); + } + } + } + } + + public Rectangle ViewRectangle + { + get + { + return new Rectangle(0, 0, ViewWidth, ViewHeight); + } + } + + public Rectangle ClientRectangleToViewRectangle(Rectangle clientRect) + { + Point clientPt = ClientPointToViewPoint(clientRect.Location); + return new Rectangle(clientPt, clientRect.Size); + } + + public int ViewWidth + { + get + { + int width; + + if (this.vScrollBar.Visible) + { + width = ClientSize.Width - this.vScrollBar.Width; + } + else + { + width = ClientSize.Width; + } + + return width; + } + } + + private int ViewHeight + { + get + { + return ItemCount * this.itemHeight; + } + } + + private void EnsureItemIsFullyVisible(ItemType itemType, int itemIndex) + { + Point itemPt = StackIndexToViewPoint(itemType, itemIndex); + Rectangle itemRect = new Rectangle(itemPt, new Size(ViewWidth, this.itemHeight)); + + int minOffset = itemRect.Bottom - ClientSize.Height; + int maxOffset = itemRect.Top; + + ScrollOffset = Utility.Clamp(ScrollOffset, minOffset, maxOffset); + } + + private Point ClientPointToViewPoint(Point pt) + { + return new Point(pt.X, pt.Y + ScrollOffset); + } + + private void ViewPointToStackIndex(Point viewPt, out ItemType itemType, out int itemIndex) + { + Rectangle undoRect = UndoViewRectangle; + + if (viewPt.Y >= undoRect.Top && viewPt.Y < undoRect.Bottom) + { + itemType = ItemType.Undo; + itemIndex = (viewPt.Y - undoRect.Top) / this.itemHeight; + } + else + { + Rectangle redoRect = RedoViewRectangle; + + itemType = ItemType.Redo; + itemIndex = (viewPt.Y - redoRect.Top) / this.itemHeight; + } + } + + private Point StackIndexToViewPoint(ItemType itemType, int itemIndex) + { + int y; + Rectangle typeRect; + + if (itemType == ItemType.Undo) + { + typeRect = UndoViewRectangle; + } + else // if (itemTYpe == ItemType.Redo) + { + typeRect = RedoViewRectangle; + } + + y = (itemIndex * this.itemHeight) + typeRect.Top; + return new Point(0, y); + } + + public event EventHandler HistoryChanged; + private void OnHistoryChanged() + { + this.vScrollBar.Maximum = ViewHeight; + + if (HistoryChanged != null) + { + HistoryChanged(this, EventArgs.Empty); + } + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int totalItems; + + if (this.historyStack == null) + { + totalItems = 0; + } + else + { + totalItems = this.historyStack.UndoStack.Count + this.historyStack.RedoStack.Count; + } + + int totalHeight = totalItems * this.itemHeight; + + if (totalHeight > ClientSize.Height) + { + this.vScrollBar.Visible = true; + this.vScrollBar.Location = new Point(ClientSize.Width - this.vScrollBar.Width, 0); + this.vScrollBar.Height = ClientSize.Height; + this.vScrollBar.Minimum = 0; + this.vScrollBar.Maximum = totalHeight; + this.vScrollBar.LargeChange = ClientSize.Height; + this.vScrollBar.SmallChange = this.itemHeight; + } + else + { + this.vScrollBar.Visible = false; + } + + if (this.historyStack != null) + { + ScrollOffset = Utility.Clamp(ScrollOffset, MinScrollOffset, MaxScrollOffset); + } + + base.OnLayout(levent); + } + + private Rectangle UndoViewRectangle + { + get + { + return new Rectangle(0, 0, ViewWidth, this.itemHeight * this.historyStack.UndoStack.Count); + } + } + + private Rectangle RedoViewRectangle + { + get + { + int undoRectBottom = this.itemHeight * this.historyStack.UndoStack.Count; + return new Rectangle(0, undoRectBottom, ViewWidth, this.itemHeight * this.historyStack.RedoStack.Count); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + if (this.historyStack != null) + { + using (SolidBrush backBrush = new SolidBrush(BackColor)) + { + e.Graphics.FillRectangle(backBrush, e.ClipRectangle); + } + + e.Graphics.TranslateTransform(0, -this.scrollOffset); + + int afterImageHMargin = UI.ScaleWidth(1); + + StringFormat stringFormat = (StringFormat)StringFormat.GenericTypographic.Clone(); + stringFormat.LineAlignment = StringAlignment.Center; + stringFormat.Trimming = StringTrimming.EllipsisCharacter; + + Rectangle visibleViewRectangle = ClientRectangleToViewRectangle(ClientRectangle); + + // Fill in the background for the undo items + Rectangle undoRect = UndoViewRectangle; + e.Graphics.FillRectangle(SystemBrushes.Window, undoRect); + + // We only want to draw what's visible, so figure out the first and last + // undo items that are actually visible and only draw them. + Rectangle visibleUndoRect = Rectangle.Intersect(visibleViewRectangle, undoRect); + + int beginUndoIndex; + int endUndoIndex; + if (visibleUndoRect.Width > 0 && visibleUndoRect.Height > 0) + { + ItemType itemType; + ViewPointToStackIndex(visibleUndoRect.Location, out itemType, out beginUndoIndex); + ViewPointToStackIndex(new Point(visibleUndoRect.Left, visibleUndoRect.Bottom - 1), out itemType, out endUndoIndex); + } + else + { + beginUndoIndex = 0; + endUndoIndex = -1; + } + + // Draw undo items + for (int i = beginUndoIndex; i <= endUndoIndex; ++i) + { + Image image; + ImageResource imageResource = this.historyStack.UndoStack[i].Image; + + if (imageResource != null) + { + image = imageResource.Reference; + } + else + { + image = null; + } + + int drawWidth; + if (image != null) + { + drawWidth = (image.Width * this.itemHeight) / image.Height; + } + else + { + drawWidth = this.itemHeight; + } + + Brush textBrush; + + if (i == this.undoItemHighlight) + { + Rectangle itemRect = new Rectangle( + 0, + i * this.itemHeight, + ViewWidth, + this.itemHeight); + + e.Graphics.FillRectangle(SystemBrushes.Highlight, itemRect); + textBrush = SystemBrushes.HighlightText; + } + else + { + textBrush = SystemBrushes.WindowText; + } + + if (image != null) + { + e.Graphics.DrawImage( + image, + new Rectangle(0, i * this.itemHeight, drawWidth, this.itemHeight), + new Rectangle(0, 0, image.Width, image.Height), + GraphicsUnit.Pixel); + } + + int textX = drawWidth + afterImageHMargin; + + Rectangle textRect = new Rectangle( + textX, + i * this.itemHeight, + ViewWidth - textX, + this.itemHeight); + + e.Graphics.DrawString( + this.historyStack.UndoStack[i].Name, + Font, + textBrush, + textRect, + stringFormat); + } + + // Fill in the background for the redo items + Rectangle redoRect = RedoViewRectangle; + e.Graphics.FillRectangle(Brushes.SlateGray, redoRect); + + Font redoFont = new Font(Font, Font.Style | FontStyle.Italic); + + // We only want to draw what's visible, so figure out the first and last + // redo items that are actually visible and only draw them. + Rectangle visibleRedoRect = Rectangle.Intersect(visibleViewRectangle, redoRect); + + int beginRedoIndex; + int endRedoIndex; + if (visibleRedoRect.Width > 0 && visibleRedoRect.Height > 0) + { + ItemType itemType; + ViewPointToStackIndex(visibleRedoRect.Location, out itemType, out beginRedoIndex); + ViewPointToStackIndex(new Point(visibleRedoRect.Left, visibleRedoRect.Bottom - 1), out itemType, out endRedoIndex); + } + else + { + beginRedoIndex = 0; + endRedoIndex = -1; + } + + // Draw redo items + for (int i = beginRedoIndex; i <= endRedoIndex; ++i) + { + Image image; + ImageResource imageResource = this.historyStack.RedoStack[i].Image; + + if (imageResource != null) + { + image = imageResource.Reference; + } + else + { + image = null; + } + + int drawWidth; + + if (image != null) + { + drawWidth = (image.Width * this.itemHeight) / image.Height; + } + else + { + drawWidth = this.itemHeight; + } + + int y = redoRect.Top + i * this.itemHeight; + + Brush textBrush; + if (i == this.redoItemHighlight) + { + Rectangle itemRect = new Rectangle( + 0, + y, + ViewWidth, + this.itemHeight); + + e.Graphics.FillRectangle(SystemBrushes.Highlight, itemRect); + textBrush = SystemBrushes.HighlightText; + } + else + { + textBrush = SystemBrushes.InactiveCaptionText; + } + + if (image != null) + { + e.Graphics.DrawImage( + image, + new Rectangle(0, y, drawWidth, this.itemHeight), + new Rectangle(0, 0, image.Width, image.Height), + GraphicsUnit.Pixel); + } + + int textX = drawWidth + afterImageHMargin; + + Rectangle textRect = new Rectangle( + textX, + y, + ViewWidth - textX, + this.itemHeight); + + e.Graphics.DrawString( + this.historyStack.RedoStack[i].Name, + redoFont, + textBrush, + textRect, + stringFormat); + } + + redoFont.Dispose(); + redoFont = null; + + stringFormat.Dispose(); + stringFormat = null; + + e.Graphics.TranslateTransform(0, this.scrollOffset); + } + + base.OnPaint(e); + } + + public HistoryStack HistoryStack + { + get + { + return this.historyStack; + } + + set + { + if (this.historyStack != null) + { + this.historyStack.Changed -= History_Changed; + this.historyStack.SteppedForward -= History_SteppedForward; + this.historyStack.SteppedBackward -= History_SteppedBackward; + this.historyStack.HistoryFlushed -= History_HistoryFlushed; + this.historyStack.NewHistoryMemento -= History_NewHistoryMemento; + } + + this.historyStack = value; + PerformLayout(); + + if (this.historyStack != null) + { + this.historyStack.Changed += History_Changed; + this.historyStack.SteppedForward += History_SteppedForward; + this.historyStack.SteppedBackward += History_SteppedBackward; + this.historyStack.HistoryFlushed += History_HistoryFlushed; + this.historyStack.NewHistoryMemento += History_NewHistoryMemento; + EnsureLastUndoItemIsFullyVisible(); + } + + Refresh(); + OnHistoryChanged(); + } + } + + private void EnsureLastUndoItemIsFullyVisible() + { + int index = this.historyStack.UndoStack.Count - 1; + EnsureItemIsFullyVisible(ItemType.Undo, index); + } + + private void History_HistoryFlushed(object sender, EventArgs e) + { + if (IsDisposed) + { + return; + } + + EnsureLastUndoItemIsFullyVisible(); + PerformMouseMove(); + PerformLayout(); + Refresh(); + } + + private void History_SteppedForward(object sender, EventArgs e) + { + if (IsDisposed) + { + return; + } + + this.undoItemHighlight = -1; + this.redoItemHighlight = -1; + EnsureLastUndoItemIsFullyVisible(); + PerformMouseMove(); + PerformLayout(); + Refresh(); + } + + private void History_SteppedBackward(object sender, EventArgs e) + { + if (IsDisposed) + { + return; + } + + this.undoItemHighlight = -1; + this.redoItemHighlight = -1; + EnsureLastUndoItemIsFullyVisible(); + PerformMouseMove(); + PerformLayout(); + Refresh(); + } + + private void History_NewHistoryMemento(object sender, EventArgs e) + { + if (IsDisposed) + { + return; + } + + EnsureLastUndoItemIsFullyVisible(); + PerformMouseMove(); + PerformLayout(); + Invalidate(); + } + + private void History_Changed(object sender, EventArgs e) + { + if (IsDisposed) + { + return; + } + + PerformMouseMove(); + PerformLayout(); + Refresh(); + OnHistoryChanged(); + } + + public HistoryControl() + { + UI.InitScaling(this); + this.itemHeight = UI.ScaleHeight(16); + + SetStyle(ControlStyles.StandardDoubleClick, false); + + InitializeComponent(); + } + + private void KeyUpHandler(object sender, KeyEventArgs e) + { + this.OnKeyUp(e); + } + + private void OnItemClicked(ItemType itemType, int itemIndex) + { + HistoryMemento hm; + + if (itemType == ItemType.Undo) + { + if (itemIndex >= 0 && itemIndex < this.historyStack.UndoStack.Count) + { + hm = this.historyStack.UndoStack[itemIndex]; + } + else + { + hm = null; + } + } + else + { + if (itemIndex >= 0 && itemIndex < this.historyStack.RedoStack.Count) + { + hm = this.historyStack.RedoStack[itemIndex]; + } + else + { + hm = null; + } + } + + if (hm != null) + { + EnsureItemIsFullyVisible(itemType, itemIndex); + OnItemClicked(itemType, hm); + } + } + + private void OnItemClicked(ItemType itemType, HistoryMemento hm) + { + int hmID = hm.ID; + + if (itemType == ItemType.Undo) + { + if (hmID == this.historyStack.UndoStack[historyStack.UndoStack.Count - 1].ID) + { + if (historyStack.UndoStack.Count > 1) + { + this.historyStack.StepBackward(); + } + } + else + { + SuspendScrollOffsetSet(); + + this.historyStack.BeginStepGroup(); + + using (new WaitCursorChanger(this)) + { + while (this.historyStack.UndoStack[this.historyStack.UndoStack.Count - 1].ID != hmID) + { + this.historyStack.StepBackward(); + } + } + + this.historyStack.EndStepGroup(); + + ResumeScrollOffsetSet(); + } + } + else // if (itemType == ItemType.Redo) + { + SuspendScrollOffsetSet(); + + // Step forward to redo + this.historyStack.BeginStepGroup(); + + using (new WaitCursorChanger(this)) + { + while (this.historyStack.UndoStack[this.historyStack.UndoStack.Count - 1].ID != hmID) + { + this.historyStack.StepForward(); + } + } + + this.historyStack.EndStepGroup(); + + ResumeScrollOffsetSet(); + } + + Focus(); + } + + protected override void OnResize(EventArgs e) + { + PerformLayout(); + base.OnResize(e); + } + + protected override void OnSizeChanged(EventArgs e) + { + PerformLayout(); + base.OnSizeChanged(e); + } + + protected override void OnMouseEnter(EventArgs e) + { + if (this.historyStack != null) + { + if (this.managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive) + { + Focus(); + } + } + + base.OnMouseEnter(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (this.historyStack != null) + { + Point clientPt = new Point(e.X, e.Y); + Point viewPt = ClientPointToViewPoint(clientPt); + + ItemType itemType; + int itemIndex; + ViewPointToStackIndex(viewPt, out itemType, out itemIndex); + + switch (itemType) + { + case ItemType.Undo: + if (itemIndex >= 0 && itemIndex < this.historyStack.UndoStack.Count) + { + this.undoItemHighlight = itemIndex; + } + else + { + this.undoItemHighlight = -1; + } + + this.redoItemHighlight = -1; + break; + + case ItemType.Redo: + this.undoItemHighlight = -1; + + if (itemIndex >= 0 && itemIndex < this.historyStack.RedoStack.Count) + { + this.redoItemHighlight = itemIndex; + } + else + { + this.redoItemHighlight = -1; + } + break; + + default: + throw new InvalidEnumArgumentException(); + } + + Refresh(); + this.lastMouseClientPt = clientPt; + } + + base.OnMouseMove(e); + } + + protected override void OnClick(EventArgs e) + { + if (this.historyStack != null) + { + Point viewPt = ClientPointToViewPoint(this.lastMouseClientPt); + + ItemType itemType; + int itemIndex; + ViewPointToStackIndex(viewPt, out itemType, out itemIndex); + + OnItemClicked(itemType, itemIndex); + } + + base.OnClick(e); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + if (this.historyStack != null) + { + int items = (e.Delta * SystemInformation.MouseWheelScrollLines) / SystemInformation.MouseWheelScrollDelta; + int pixels = items * this.itemHeight; + ScrollOffset -= pixels; + + PerformMouseMove(); + } + + base.OnMouseWheel(e); + } + + private void PerformMouseMove() + { + Point clientPt = PointToClient(Control.MousePosition); + + if (ClientRectangle.Contains(clientPt)) + { + MouseEventArgs me = new MouseEventArgs(MouseButtons.None, 0, clientPt.X, clientPt.Y, 0); + OnMouseMove(me); + } + } + + protected override void OnMouseLeave(EventArgs e) + { + if (this.historyStack != null) + { + this.undoItemHighlight = -1; + this.redoItemHighlight = -1; + Refresh(); + + if (this.Focused && this.managedFocus) + { + OnRelinquishFocus(); + } + } + + base.OnMouseLeave(e); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.vScrollBar = new VScrollBar(); + SuspendLayout(); + // + // vScrollBar + // + this.vScrollBar.Name = "vScrollBar"; + this.vScrollBar.ValueChanged += new EventHandler(VScrollBar_ValueChanged); + // + // HistoryControl + // + this.Name = "HistoryControl"; + this.TabStop = false; + this.Controls.Add(this.vScrollBar); + this.ResizeRedraw = true; + this.DoubleBuffered = true; + ResumeLayout(); + PerformLayout(); + } + #endregion + + private void VScrollBar_ValueChanged(object sender, EventArgs e) + { + ScrollOffset = this.vScrollBar.Value; + } + } +} diff --git a/src/HistoryControl.resx b/src/HistoryControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/HistoryForm.cs b/src/HistoryForm.cs new file mode 100644 index 0000000..d424ea4 --- /dev/null +++ b/src/HistoryForm.cs @@ -0,0 +1,337 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class HistoryForm + : FloatingToolForm + { + private PaintDotNet.HistoryControl historyControl; + private System.Windows.Forms.ImageList imageList; + private PaintDotNet.SystemLayer.ToolStripEx toolStrip; + private ToolStripButton rewindButton; + private ToolStripButton undoButton; + private ToolStripButton redoButton; + private ToolStripButton fastForwardButton; + private System.ComponentModel.IContainer components; + + public HistoryControl HistoryControl + { + get + { + return historyControl; + } + } + + public HistoryForm() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.imageList.TransparentColor = Utility.TransparentKey; + this.toolStrip.ImageList = this.imageList; + + int rewindIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.HistoryRewindIcon.png").Reference, imageList.TransparentColor); + int undoIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuEditUndoIcon.png").Reference, imageList.TransparentColor); + int redoIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuEditRedoIcon.png").Reference, imageList.TransparentColor); + int fastForwardIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.HistoryFastForwardIcon.png").Reference, imageList.TransparentColor); + + rewindButton.ImageIndex = rewindIndex; + undoButton.ImageIndex = undoIndex; + redoButton.ImageIndex = redoIndex; + fastForwardButton.ImageIndex = fastForwardIndex; + + this.Text = PdnResources.GetString("HistoryForm.Text"); + + this.rewindButton.ToolTipText = PdnResources.GetString("HistoryForm.RewindButton.ToolTipText"); + this.undoButton.ToolTipText = PdnResources.GetString("HistoryForm.UndoButton.ToolTipText"); + this.redoButton.ToolTipText = PdnResources.GetString("HistoryForm.RedoButton.ToolTipText"); + this.fastForwardButton.ToolTipText = PdnResources.GetString("HistoryForm.FastForwardButton.ToolTipText"); + + this.MinimumSize = this.Size; + } + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout (levent); + + // We have to test for null in case Layout is raised before our + // InitializeComponent is called (or is finished) + if (historyControl != null) + { + historyControl.Size = new Size(ClientRectangle.Width, ClientRectangle.Height - (toolStrip.Height + + (ClientRectangle.Height - toolStrip.Bottom))); + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.historyControl = new PaintDotNet.HistoryControl(); + this.imageList = new System.Windows.Forms.ImageList(this.components); + this.toolStrip = new PaintDotNet.SystemLayer.ToolStripEx(); + this.rewindButton = new System.Windows.Forms.ToolStripButton(); + this.undoButton = new System.Windows.Forms.ToolStripButton(); + this.redoButton = new System.Windows.Forms.ToolStripButton(); + this.fastForwardButton = new System.Windows.Forms.ToolStripButton(); + this.toolStrip.SuspendLayout(); + this.SuspendLayout(); + // + // historyControl + // + this.historyControl.Dock = System.Windows.Forms.DockStyle.Top; + this.historyControl.HistoryStack = null; + this.historyControl.Location = new System.Drawing.Point(0, 0); + this.historyControl.Name = "historyControl"; + this.historyControl.Size = new System.Drawing.Size(160, 152); + this.historyControl.TabIndex = 0; + this.historyControl.HistoryChanged += new System.EventHandler(this.HistoryControl_HistoryChanged); + this.historyControl.RelinquishFocus += new EventHandler(HistoryControl_RelinquishFocus); + this.historyControl.ManagedFocus = true; + // + // imageList + // + this.imageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit; + this.imageList.ImageSize = new System.Drawing.Size(16, 16); + this.imageList.TransparentColor = System.Drawing.Color.Transparent; + // + // toolStrip + // + this.toolStrip.Dock = System.Windows.Forms.DockStyle.Bottom; + this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.rewindButton, + this.undoButton, + this.redoButton, + this.fastForwardButton}); + this.toolStrip.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow; + this.toolStrip.Location = new System.Drawing.Point(0, 139); + this.toolStrip.Name = "toolStrip"; + this.toolStrip.Size = new System.Drawing.Size(160, 19); + this.toolStrip.TabIndex = 2; + this.toolStrip.Text = "toolStrip1"; + this.toolStrip.RelinquishFocus += new EventHandler(ToolStrip_RelinquishFocus); + // + // rewindButton + // + this.rewindButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.rewindButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.rewindButton.Name = "rewindButton"; + this.rewindButton.Size = new System.Drawing.Size(23, 4); + this.rewindButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // undoButton + // + this.undoButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.undoButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.undoButton.Name = "undoButton"; + this.undoButton.Size = new System.Drawing.Size(23, 4); + this.undoButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // redoButton + // + this.redoButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.redoButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.redoButton.Name = "redoButton"; + this.redoButton.Size = new System.Drawing.Size(23, 4); + this.redoButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // fastForwardButton + // + this.fastForwardButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.fastForwardButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.fastForwardButton.Name = "fastForwardButton"; + this.fastForwardButton.Size = new System.Drawing.Size(23, 4); + this.fastForwardButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // HistoryForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(165, 158); + this.Controls.Add(this.toolStrip); + this.Controls.Add(this.historyControl); + this.Name = "HistoryForm"; + this.Enter += new System.EventHandler(this.HistoryForm_Enter); + this.Controls.SetChildIndex(this.historyControl, 0); + this.Controls.SetChildIndex(this.toolStrip, 0); + this.toolStrip.ResumeLayout(false); + this.toolStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + #endregion + + private void HistoryControl_RelinquishFocus(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + private void ToolStrip_RelinquishFocus(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + public event EventHandler UndoButtonClicked; + protected virtual void OnUndoButtonClicked() + { + if (UndoButtonClicked != null) + { + UndoButtonClicked(this, EventArgs.Empty); + } + } + + public void PerformUndoClick() + { + OnUndoButtonClicked(); + } + + public event EventHandler RedoButtonClicked; + protected virtual void OnRedoButtonClicked() + { + if (RedoButtonClicked != null) + { + RedoButtonClicked(this, EventArgs.Empty); + } + } + + public void PerformRedoClick() + { + OnRedoButtonClicked(); + } + + public event EventHandler RewindButtonClicked; + protected virtual void OnRewindButtonClicked() + { + if (RewindButtonClicked != null) + { + RewindButtonClicked(this, EventArgs.Empty); + } + } + + public void PerformRewindClick() + { + OnRewindButtonClicked(); + } + + public event EventHandler FastForwardButtonClicked; + protected virtual void OnFastForwardButtonClicked() + { + if (FastForwardButtonClicked != null) + { + FastForwardButtonClicked(this, EventArgs.Empty); + } + } + + public void PerformFastForwardClick() + { + OnFastForwardButtonClicked(); + } + + private void HistoryForm_Enter(object sender, System.EventArgs e) + { + PerformLayout(); + } + + private void UpdateHistoryButtons() + { + if (historyControl.HistoryStack == null) + { + rewindButton.Enabled = false; + undoButton.Enabled = false; + fastForwardButton.Enabled = false; + redoButton.Enabled = false; + } + else + { + // Find reasons to disable the rewind and undo buttons + if (historyControl.HistoryStack.UndoStack.Count <= 1) + { + rewindButton.Enabled = false; + undoButton.Enabled = false; + } + else + { + rewindButton.Enabled = true; + undoButton.Enabled = true; + } + + // Find reasons to disable the redo and fast forward buttons + if (historyControl.HistoryStack.RedoStack.Count == 0) + { + fastForwardButton.Enabled = false; + redoButton.Enabled = false; + } + else + { + fastForwardButton.Enabled = true; + redoButton.Enabled = true; + } + } + } + + private void HistoryControl_HistoryChanged(object sender, System.EventArgs e) + { + OnRelinquishFocus(); + UpdateHistoryButtons(); + } + + private void OnToolStripButtonClick(object sender, EventArgs e) + { + if (sender == undoButton) + { + OnUndoButtonClicked(); + } + else if (sender == redoButton) + { + OnRedoButtonClicked(); + } + else if (sender == rewindButton) + { + OnRewindButtonClicked(); + } + else if (sender == fastForwardButton) + { + OnFastForwardButtonClicked(); + } + + OnRelinquishFocus(); + } + } +} diff --git a/src/HistoryForm.resx b/src/HistoryForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/HistoryFunction.cs b/src/HistoryFunction.cs new file mode 100644 index 0000000..436e687 --- /dev/null +++ b/src/HistoryFunction.cs @@ -0,0 +1,299 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Threading; + +namespace PaintDotNet +{ + internal abstract class HistoryFunction + { + private int criticalRegionCount = 0; + private bool executed = false; + private volatile bool pleaseCancel = false; + private ISynchronizeInvoke eventSink = null; + + public bool IsAsync + { + get + { + return this.eventSink != null; + } + } + + public ISynchronizeInvoke EventSink + { + get + { + if (!IsAsync) + { + throw new InvalidOperationException("EventSink property is only accessible when IsAsync is true"); + } + + return this.eventSink; + } + } + + public bool Cancellable + { + get + { + return ((this.actionFlags & ActionFlags.Cancellable) == ActionFlags.Cancellable); + } + } + + private ActionFlags actionFlags; + public ActionFlags ActionFlags + { + get + { + return this.actionFlags; + } + } + + protected bool PleaseCancel + { + get + { + return this.pleaseCancel; + } + } + + private void ExecuteTrampoline(object context) + { + Execute((IHistoryWorkspace)context); + } + + /// + /// Executes the HistoryFunction. + /// + /// + /// A HistoryMemento instance if an operation was performed successfully, + /// or null for success but no operation was performed. + /// + /// There was error while performing the operation. No changes have been made to the HistoryWorkspace (no-op). + /// + /// + /// If this HistoryFunction's ActionFlags contain the HistoryFlags.Cancellable bit, then it will be executed in + /// a background thread. + /// + public HistoryMemento Execute(IHistoryWorkspace historyWorkspace) + { + SystemLayer.Tracing.LogFeature("HF(" + GetType().Name + ")"); + + HistoryMemento returnVal = null; + Exception exception = null; + + try + { + try + { + if (this.executed) + { + throw new InvalidOperationException("Already executed this HistoryFunction"); + } + + this.executed = true; + + returnVal = OnExecute(historyWorkspace); + return returnVal; + } + + catch (ArgumentOutOfRangeException aoorex) + { + if (this.criticalRegionCount > 0) + { + throw; + } + else + { + throw new HistoryFunctionNonFatalException(null, aoorex); + } + } + + catch (OutOfMemoryException oomex) + { + if (this.criticalRegionCount > 0) + { + throw; + } + else + { + throw new HistoryFunctionNonFatalException(null, oomex); + } + } + } + + catch (Exception ex) + { + if (IsAsync) + { + exception = ex; + return returnVal; + } + else + { + throw; + } + } + + finally + { + if (IsAsync) + { + OnFinished(returnVal, exception); + } + } + } + + /// + /// Executes the function asynchronously. + /// + /// + /// + /// + /// If you use this method to execute the function, completion will be signified + /// using the Finished event. This will be raised no matter if the function completes + /// successfully or not. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "eventSink")] + public void BeginExecute(ISynchronizeInvoke eventSink, IHistoryWorkspace historyWorkspace, EventHandler> finishedCallback) + { + if (finishedCallback == null) + { + throw new ArgumentNullException("finishedCallback"); + } + + if (this.eventSink != null) + { + throw new InvalidOperationException("already executing this function"); + } + + this.eventSink = eventSink; + this.Finished += finishedCallback; + System.Threading.ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteTrampoline), historyWorkspace); + } + + /// + /// This event is raised when the function has finished execution, whether it finished successfully or not. + /// + public event EventHandler> Finished; + + private void OnFinished(HistoryMemento memento, Exception exception) + { + if (this.eventSink.InvokeRequired) + { + this.eventSink.BeginInvoke( + new Procedure(OnFinished), + new object[2] { memento, exception }); + } + else + { + if (exception != null) + { + throw new WorkerThreadException(exception); + } + + if (Finished != null) + { + Finished(this, new EventArgs(memento)); + } + } + } + + /// + /// This event is raised when the function wants to report its progress. + /// + /// + /// This event is only ever raised if the function has the ActionFlags.ReportsProgres flag set. + /// There is no guarantee that the value reported via this event will start at 0 or finish at 100, + /// nor that it will report values in non-descending order. Clients of this event are advised + /// to clamp the values reported to the range [0, 100] and to define their own policy for + /// handling progress values that are less than the previously reported progress values. + /// + public event ProgressEventHandler Progress; + protected virtual void OnProgress(double percent) + { + if ((this.actionFlags & ActionFlags.ReportsProgress) != ActionFlags.ReportsProgress) + { + System.Diagnostics.Debug.WriteLine("This HistoryFunction does not support reporting progress, yet it called OnProgress()"); + } + else if (Progress != null) + { + this.eventSink.BeginInvoke(Progress, new object[2] { this, new ProgressEventArgs(percent) }); + } + } + + /// + /// Call this method from within OnExecute() in order to mark areas of your code where + /// the throwing of an OutOfMemoryException does not guarantee the coherency of data. + /// + /// + /// If OnExecute() is not within a critical region and throws an OutOfMemoryException, + /// it will be wrapped inside a HistoryFunctionNonFatalException as the InnerException. + /// This prevents the execution host from treating it as an operation that must + /// close the application. + /// Once you enter a critical region, you may not leave it. Therefore, it is recommended + /// that you do as much preparatory work as possible before entering a critical region. + /// Once a HistoryFunction has entered its critical region, it may not be cancelled. + /// + protected void EnterCriticalRegion() + { + Interlocked.Increment(ref this.criticalRegionCount); + } + + /// + /// If the HistoryFunction is being executed asynchronously using BeginExecute() and EndExecute(), + /// and if it also has the ActionFlags.Cancellable flag, then you may request that it cancel its + /// long running operation by calling this method. + /// + /// + /// The request to cancel may have no effect, and the history function may still complete normally. + /// This may happen if the function has already entered its critical region, or if it has already + /// completed before this method is called. + /// + public void RequestCancel() + { + if (!Cancellable) + { + throw new InvalidOperationException("This HistoryFunction is not cancellable"); + } + + if (!IsAsync) + { + throw new InvalidOperationException("This function is not in the process of being executed asynchronously, and therefore cannot be cancelled"); + } + + this.pleaseCancel = true; + OnCancelRequested(); + } + + public event EventHandler CancelRequested; + protected virtual void OnCancelRequested() + { + if (!this.pleaseCancel) + { + throw new InvalidOperationException("OnCancelRequested() was called when pleaseCancel equaled false"); + } + + if (CancelRequested != null) + { + this.eventSink.BeginInvoke(CancelRequested, new object[2] { this, EventArgs.Empty }); + } + } + + public abstract HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace); + + public HistoryFunction(ActionFlags actionFlags) + { + this.actionFlags = actionFlags; + } + } +} diff --git a/src/HistoryFunctionNonFatalException.cs b/src/HistoryFunctionNonFatalException.cs new file mode 100644 index 0000000..fd3d5a3 --- /dev/null +++ b/src/HistoryFunctionNonFatalException.cs @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// This class is treated differently than an Exception in that the Message property + /// is used as a localized error string that will be displayed to the user. Also, + /// exceptions of this type are treated as non-fatal, and it is assumed that any + /// function being executed has not caused any changes. + /// If null is given for localizedErrorText, a generic message may be provided to + /// the user instead. + /// + internal class HistoryFunctionNonFatalException + : Exception + { + private string localizedErrorText; + public string LocalizedErrorText + { + get + { + return this.localizedErrorText; + } + } + + private const string message = "Non-fatal exception encountered"; + + public HistoryFunctionNonFatalException() + { + this.localizedErrorText = null; + } + + public HistoryFunctionNonFatalException(string localizedErrorText) + : base(message) + { + this.localizedErrorText = localizedErrorText; + } + + public HistoryFunctionNonFatalException(string localizedErrorText, Exception innerException) + : base(message, innerException) + { + this.localizedErrorText = localizedErrorText; + } + } +} diff --git a/src/HistoryFunctionResult.cs b/src/HistoryFunctionResult.cs new file mode 100644 index 0000000..47c91c8 --- /dev/null +++ b/src/HistoryFunctionResult.cs @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum HistoryFunctionResult + { + /// + /// The HistoryFunction completed successfully, and a new item has been placed in to the HistoryStack. + /// + Success, + + /// + /// The HistoryFunction completed successfully, but did nothing. + /// + SuccessNoOp, + + /// + /// The HistoryFunction was cancelled by the user. No changes have been made. + /// + Cancelled, + + /// + /// There was not enough memory to execute the HistoryFunction. No changes have been made. + /// + OutOfMemory, + + /// + /// There was an error executing the HistoryFunction. No changes have been made. + /// + NonFatalError + } +} diff --git a/src/HistoryFunctions/AddNewBlankLayerFunction.cs b/src/HistoryFunctions/AddNewBlankLayerFunction.cs new file mode 100644 index 0000000..322e487 --- /dev/null +++ b/src/HistoryFunctions/AddNewBlankLayerFunction.cs @@ -0,0 +1,45 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class AddNewBlankLayerFunction + : HistoryFunction + { + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + BitmapLayer newLayer = null; + newLayer = new BitmapLayer(historyWorkspace.Document.Width, historyWorkspace.Document.Height); + string newLayerNameFormat = PdnResources.GetString("AddNewBlankLayer.LayerName.Format"); + newLayer.Name = string.Format(newLayerNameFormat, (1 + historyWorkspace.Document.Layers.Count).ToString()); + + int newLayerIndex = historyWorkspace.ActiveLayerIndex + 1; + + NewLayerHistoryMemento ha = new NewLayerHistoryMemento( + PdnResources.GetString("AddNewBlankLayer.HistoryMementoName"), + PdnResources.GetImageResource("Icons.MenuLayersAddNewLayerIcon.png"), + historyWorkspace, + newLayerIndex); + + EnterCriticalRegion(); + + historyWorkspace.Document.Layers.Insert(newLayerIndex, newLayer); + + return ha; + } + + public AddNewBlankLayerFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/CropToSelectionFunction.cs b/src/HistoryFunctions/CropToSelectionFunction.cs new file mode 100644 index 0000000..91f0401 --- /dev/null +++ b/src/HistoryFunctions/CropToSelectionFunction.cs @@ -0,0 +1,114 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using PaintDotNet.Actions; +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + /// + /// Crops the image to the currently selected region. + /// + internal sealed class CropToSelectionFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("CropAction.Name"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (historyWorkspace.Selection.IsEmpty) + { + return null; + } + else + { + PdnRegion selectionRegion = historyWorkspace.Selection.CreateRegion(); + + if (selectionRegion.GetArea() == 0) + { + selectionRegion.Dispose(); + return null; + } + + SelectionHistoryMemento sha = new SelectionHistoryMemento(StaticName, null, historyWorkspace); + ReplaceDocumentHistoryMemento rdha = new ReplaceDocumentHistoryMemento(StaticName, null, historyWorkspace); + Rectangle boundingBox; + Rectangle[] inverseRegionRects = null; + + boundingBox = Utility.GetRegionBounds(selectionRegion); + + using (PdnRegion inverseRegion = new PdnRegion(boundingBox)) + { + inverseRegion.Exclude(selectionRegion); + + inverseRegionRects = Utility.TranslateRectangles( + inverseRegion.GetRegionScansReadOnlyInt(), + -boundingBox.X, + -boundingBox.Y); + } + + selectionRegion.Dispose(); + selectionRegion = null; + + Document oldDocument = historyWorkspace.Document; // TODO: serialize this to disk so we don't *have* to store the full thing + Document newDocument = new Document(boundingBox.Width, boundingBox.Height); + + // copy the document's meta data over + newDocument.ReplaceMetaDataFrom(oldDocument); + + foreach (Layer layer in oldDocument.Layers) + { + if (layer is BitmapLayer) + { + BitmapLayer oldLayer = (BitmapLayer)layer; + Surface croppedSurface = oldLayer.Surface.CreateWindow(boundingBox); + BitmapLayer newLayer = new BitmapLayer(croppedSurface); + + ColorBgra clearWhite = ColorBgra.White.NewAlpha(0); + + foreach (Rectangle rect in inverseRegionRects) + { + newLayer.Surface.Clear(rect, clearWhite); + } + + newLayer.LoadProperties(oldLayer.SaveProperties()); + newDocument.Layers.Add(newLayer); + } + else + { + throw new InvalidOperationException("Crop does not support Layers that are not BitmapLayers"); + } + } + + CompoundHistoryMemento cha = new CompoundHistoryMemento( + StaticName, + PdnResources.GetImageResource("Icons.MenuImageCropIcon.png"), + new HistoryMemento[] { sha, rdha }); + + EnterCriticalRegion(); + historyWorkspace.Document = newDocument; + + return cha; + } + } + + public CropToSelectionFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/DeleteLayerFunction.cs b/src/HistoryFunctions/DeleteLayerFunction.cs new file mode 100644 index 0000000..816b322 --- /dev/null +++ b/src/HistoryFunctions/DeleteLayerFunction.cs @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class DeleteLayerFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("DeleteLayer.HistoryMementoName"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuLayersDeleteLayerIcon.png"); + } + } + + private int layerIndex; + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (this.layerIndex < 0 || this.layerIndex >= historyWorkspace.Document.Layers.Count) + { + throw new ArgumentOutOfRangeException("layerIndex = " + this.layerIndex + + ", expected [0, " + historyWorkspace.Document.Layers.Count + ")"); + } + + HistoryMemento hm = new DeleteLayerHistoryMemento(StaticName, StaticImage, historyWorkspace, historyWorkspace.Document.Layers.GetAt(this.layerIndex)); + + EnterCriticalRegion(); + historyWorkspace.Document.Layers.RemoveAt(this.layerIndex); + + return hm; + } + + public DeleteLayerFunction(int layerIndex) + : base(ActionFlags.None) + { + this.layerIndex = layerIndex; + } + } +} diff --git a/src/HistoryFunctions/DeselectFunction.cs b/src/HistoryFunctions/DeselectFunction.cs new file mode 100644 index 0000000..92f2998 --- /dev/null +++ b/src/HistoryFunctions/DeselectFunction.cs @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class DeselectFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("DeselectAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuEditDeselectIcon.png"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (historyWorkspace.Selection.IsEmpty) + { + return null; + } + else + { + SelectionHistoryMemento sha = new SelectionHistoryMemento(StaticName, StaticImage, historyWorkspace); + + EnterCriticalRegion(); + historyWorkspace.Selection.Reset(); + + return sha; + } + } + + public DeselectFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/DuplicateLayerFunction.cs b/src/HistoryFunctions/DuplicateLayerFunction.cs new file mode 100644 index 0000000..3d46a20 --- /dev/null +++ b/src/HistoryFunctions/DuplicateLayerFunction.cs @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class DuplicateLayerFunction + : HistoryFunction + { + private int layerIndex; + + public static string StaticName + { + get + { + return PdnResources.GetString("DuplicateLayer.HistoryMementoName"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuLayersDuplicateLayerIcon.png"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (this.layerIndex < 0 || this.layerIndex >= historyWorkspace.Document.Layers.Count) + { + throw new ArgumentOutOfRangeException("layerIndex = " + layerIndex + ", expected [0, " + historyWorkspace.Document.Layers.Count + ")"); + } + + Layer newLayer = null; + + newLayer = (Layer)historyWorkspace.ActiveLayer.Clone(); + newLayer.IsBackground = false; + int newIndex = 1 + this.layerIndex; + + HistoryMemento ha = new NewLayerHistoryMemento( + StaticName, + StaticImage, + historyWorkspace, + newIndex); + + EnterCriticalRegion(); + historyWorkspace.Document.Layers.Insert(newIndex, newLayer); + newLayer.Invalidate(); + + return ha; + } + + public DuplicateLayerFunction(int layerIndex) + : base(ActionFlags.None) + { + this.layerIndex = layerIndex; + } + } +} diff --git a/src/HistoryFunctions/EraseSelectionFunction.cs b/src/HistoryFunctions/EraseSelectionFunction.cs new file mode 100644 index 0000000..c67ba38 --- /dev/null +++ b/src/HistoryFunctions/EraseSelectionFunction.cs @@ -0,0 +1,80 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class EraseSelectionFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("EraseSelectionAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuEditEraseSelectionIcon.png"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (historyWorkspace.Selection.IsEmpty) + { + return null; + } + + SelectionHistoryMemento shm = new SelectionHistoryMemento(string.Empty, null, historyWorkspace); + + PdnRegion region = historyWorkspace.Selection.CreateRegion(); + + BitmapLayer layer = ((BitmapLayer)historyWorkspace.ActiveLayer); + PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(region); + + HistoryMemento hm = new BitmapHistoryMemento( + null, + null, + historyWorkspace, + historyWorkspace.ActiveLayerIndex, + simplifiedRegion); + + HistoryMemento chm = new CompoundHistoryMemento( + StaticName, + StaticImage, + new HistoryMemento[] { shm, hm }); + + EnterCriticalRegion(); + + layer.Surface.Clear(region, ColorBgra.FromBgra(255, 255, 255, 0)); + + layer.Invalidate(simplifiedRegion); + historyWorkspace.Document.Invalidate(simplifiedRegion); + simplifiedRegion.Dispose(); + region.Dispose(); + historyWorkspace.Selection.Reset(); + + return chm; + } + + public EraseSelectionFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/FillSelectionFunction.cs b/src/HistoryFunctions/FillSelectionFunction.cs new file mode 100644 index 0000000..92aa3cc --- /dev/null +++ b/src/HistoryFunctions/FillSelectionFunction.cs @@ -0,0 +1,73 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class FillSelectionFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("FillSelectionAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuEditFillSelectionIcon.png"); + } + } + + private ColorBgra fillColor; + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (historyWorkspace.Selection.IsEmpty) + { + return null; + } + + PdnRegion region = historyWorkspace.Selection.CreateRegion(); + BitmapLayer layer = ((BitmapLayer)historyWorkspace.ActiveLayer); + PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(region); + + HistoryMemento hm = new BitmapHistoryMemento( + StaticName, + StaticImage, + historyWorkspace, + historyWorkspace.ActiveLayerIndex, + simplifiedRegion); + + EnterCriticalRegion(); + + layer.Surface.Clear(region, this.fillColor); + layer.Invalidate(simplifiedRegion); + + simplifiedRegion.Dispose(); + region.Dispose(); + + return hm; + } + + public FillSelectionFunction(ColorBgra fillColor) + : base(ActionFlags.None) + { + this.fillColor = fillColor; + } + } +} diff --git a/src/HistoryFunctions/FlattenFunction.cs b/src/HistoryFunctions/FlattenFunction.cs new file mode 100644 index 0000000..a60a834 --- /dev/null +++ b/src/HistoryFunctions/FlattenFunction.cs @@ -0,0 +1,69 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections.Generic; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class FlattenFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("FlattenFunction.Name"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + object savedSelection = null; + List actions = new List(); + + if (!historyWorkspace.Selection.IsEmpty) + { + savedSelection = historyWorkspace.Selection.Save(); + DeselectFunction da = new DeselectFunction(); + HistoryMemento hm = da.Execute(historyWorkspace); + actions.Add(hm); + } + + ReplaceDocumentHistoryMemento rdha = new ReplaceDocumentHistoryMemento(null, null, historyWorkspace); + actions.Add(rdha); + + CompoundHistoryMemento chm = new CompoundHistoryMemento( + StaticName, + PdnResources.GetImageResource("Icons.MenuImageFlattenIcon.png"), + actions); + + // TODO: we can save memory here by serializing, then flattening on to an existing layer + Document flat = historyWorkspace.Document.Flatten(); + + EnterCriticalRegion(); + historyWorkspace.Document = flat; + + if (savedSelection != null) + { + SelectionHistoryMemento shm = new SelectionHistoryMemento(null, null, historyWorkspace); + historyWorkspace.Selection.Restore(savedSelection); + chm.PushNewAction(shm); + } + + return chm; + } + + public FlattenFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/FlipDocumentFunction.cs b/src/HistoryFunctions/FlipDocumentFunction.cs new file mode 100644 index 0000000..3508335 --- /dev/null +++ b/src/HistoryFunctions/FlipDocumentFunction.cs @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + internal abstract class FlipDocumentFunction + : HistoryFunction + { + private string historyName; + private ImageResource undoImage; + private FlipType flipType; + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + List actions = new List(); + + if (!historyWorkspace.Selection.IsEmpty) + { + DeselectFunction da = new DeselectFunction(); + EnterCriticalRegion(); + HistoryMemento hm = da.Execute(historyWorkspace); + actions.Add(hm); + } + + int count = historyWorkspace.Document.Layers.Count; + + for (int i = 0; i < count; ++i) + { + HistoryMemento memento = new FlipLayerHistoryMemento(this.historyName, undoImage, historyWorkspace, i, flipType); + EnterCriticalRegion(); + HistoryMemento mementoToAdd = memento.PerformUndo(); + actions.Add(mementoToAdd); + } + + return new CompoundHistoryMemento(this.historyName, undoImage, actions); + } + + public FlipDocumentFunction(string historyName, ImageResource image, FlipType flipType) + : base(ActionFlags.None) + { + this.historyName = historyName; + this.undoImage = image; + this.flipType = flipType; + } + } +} diff --git a/src/HistoryFunctions/FlipDocumentHorizontalFunction.cs b/src/HistoryFunctions/FlipDocumentHorizontalFunction.cs new file mode 100644 index 0000000..be107ad --- /dev/null +++ b/src/HistoryFunctions/FlipDocumentHorizontalFunction.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class FlipDocumentHorizontalFunction + : FlipDocumentFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("FlipDocumentHorizontalAction.Name"); + } + } + + public FlipDocumentHorizontalFunction() + : base(StaticName, + PdnResources.GetImageResource("Icons.MenuImageFlipHorizontalIcon.png"), + FlipType.Horizontal) + { + } + } +} diff --git a/src/HistoryFunctions/FlipDocumentVerticalFunction.cs b/src/HistoryFunctions/FlipDocumentVerticalFunction.cs new file mode 100644 index 0000000..dc9c1a2 --- /dev/null +++ b/src/HistoryFunctions/FlipDocumentVerticalFunction.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class FlipDocumentVerticalFunction + : FlipDocumentFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("FlipDocumentVerticalAction.Name"); + } + } + + public FlipDocumentVerticalFunction() + : base(StaticName, + PdnResources.GetImageResource("Icons.MenuImageFlipVerticalIcon.png"), + FlipType.Vertical) + { + } + } +} diff --git a/src/HistoryFunctions/FlipLayerFunction.cs b/src/HistoryFunctions/FlipLayerFunction.cs new file mode 100644 index 0000000..1643673 --- /dev/null +++ b/src/HistoryFunctions/FlipLayerFunction.cs @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + internal abstract class FlipLayerFunction + : HistoryFunction + { + private string historyName; + private FlipType flipType; + private ImageResource undoImage; + private int layerIndex; + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + CompoundHistoryMemento chm = new CompoundHistoryMemento(this.historyName, this.undoImage); + + if (!historyWorkspace.Selection.IsEmpty) + { + DeselectFunction df = new DeselectFunction(); + EnterCriticalRegion(); + HistoryMemento hm = df.Execute(historyWorkspace); + chm.PushNewAction(hm); + } + + FlipLayerHistoryMemento flha = new FlipLayerHistoryMemento( + null, + null, + historyWorkspace, + this.layerIndex, + this.flipType); + + EnterCriticalRegion(); + HistoryMemento flha2 = flha.PerformUndo(); + chm.PushNewAction(flha); + + return chm; + } + + public FlipLayerFunction(string historyName, ImageResource image, FlipType flipType, int layerIndex) + : base(ActionFlags.None) + { + this.historyName = historyName; + this.flipType = flipType; + this.undoImage = image; + this.layerIndex = layerIndex; + } + } +} diff --git a/src/HistoryFunctions/InvertSelectionFunction.cs b/src/HistoryFunctions/InvertSelectionFunction.cs new file mode 100644 index 0000000..80f89c0 --- /dev/null +++ b/src/HistoryFunctions/InvertSelectionFunction.cs @@ -0,0 +1,83 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class InvertSelectionFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("InvertSelectionAction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuEditInvertSelectionIcon.png"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (historyWorkspace.Selection.IsEmpty) + { + return null; + } + else + { + SelectionHistoryMemento sha = new SelectionHistoryMemento( + StaticName, + StaticImage, + historyWorkspace); + + //PdnGraphicsPath selectedPath = historyWorkspace.Selection.GetPathReadOnly(); + PdnGraphicsPath selectedPath = historyWorkspace.Selection.CreatePath(); + + PdnGraphicsPath boundsOutline = new PdnGraphicsPath(); + boundsOutline.AddRectangle(historyWorkspace.Document.Bounds); + + PdnGraphicsPath clippedPath = PdnGraphicsPath.Combine(selectedPath, CombineMode.Intersect, boundsOutline); + PdnGraphicsPath invertedPath = PdnGraphicsPath.Combine(clippedPath, CombineMode.Xor, boundsOutline); + + selectedPath.Dispose(); + selectedPath = null; + + clippedPath.Dispose(); + clippedPath = null; + + EnterCriticalRegion(); + historyWorkspace.Selection.PerformChanging(); + historyWorkspace.Selection.Reset(); + historyWorkspace.Selection.SetContinuation(invertedPath, CombineMode.Replace, true); + historyWorkspace.Selection.CommitContinuation(); + historyWorkspace.Selection.PerformChanged(); + + boundsOutline.Dispose(); + boundsOutline = null; + + return sha; + } + } + + public InvertSelectionFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/MergeLayerDownFunction.cs b/src/HistoryFunctions/MergeLayerDownFunction.cs new file mode 100644 index 0000000..54e7786 --- /dev/null +++ b/src/HistoryFunctions/MergeLayerDownFunction.cs @@ -0,0 +1,84 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using PaintDotNet.HistoryMementos; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class MergeLayerDownFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("MergeLayerDown.HistoryMementoName"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.MenuLayersMergeLayerDownIcon.png"); + } + } + + private int layerIndex; + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (this.layerIndex < 1 || this.layerIndex >= historyWorkspace.Document.Layers.Count) + { + throw new ArgumentException("layerIndex must be greater than or equal to 1, and a valid layer index. layerIndex=" + + layerIndex + ", allowableRange=[0," + historyWorkspace.Document.Layers.Count + ")"); + } + + int bottomLayerIndex = this.layerIndex - 1; + Rectangle bounds = historyWorkspace.Document.Bounds; + PdnRegion region = new PdnRegion(bounds); + + BitmapHistoryMemento bhm = new BitmapHistoryMemento( + null, + null, + historyWorkspace, + bottomLayerIndex, + region); + + BitmapLayer topLayer = (BitmapLayer)historyWorkspace.Document.Layers[this.layerIndex]; + BitmapLayer bottomLayer = (BitmapLayer)historyWorkspace.Document.Layers[bottomLayerIndex]; + RenderArgs bottomRA = new RenderArgs(bottomLayer.Surface); + + EnterCriticalRegion(); + + topLayer.Render(bottomRA, region); + bottomLayer.Invalidate(); + + bottomRA.Dispose(); + bottomRA = null; + + region.Dispose(); + region = null; + + DeleteLayerFunction dlf = new DeleteLayerFunction(this.layerIndex); + HistoryMemento dlhm = dlf.Execute(historyWorkspace); + + CompoundHistoryMemento chm = new CompoundHistoryMemento(StaticName, StaticImage, new HistoryMemento[] { bhm, dlhm }); + return chm; + } + + public MergeLayerDownFunction(int layerIndex) + : base(ActionFlags.None) + { + this.layerIndex = layerIndex; + } + } +} diff --git a/src/HistoryFunctions/RotateDocumentFunction.cs b/src/HistoryFunctions/RotateDocumentFunction.cs new file mode 100644 index 0000000..fec99f6 --- /dev/null +++ b/src/HistoryFunctions/RotateDocumentFunction.cs @@ -0,0 +1,206 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class RotateDocumentFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("RotateAction.Name"); + } + } + + private RotateType rotation; + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + int newWidth; + int newHeight; + + // Get new width and Height + switch (rotation) + { + case RotateType.Clockwise90: + case RotateType.CounterClockwise90: + newWidth = historyWorkspace.Document.Height; + newHeight = historyWorkspace.Document.Width; + break; + + case RotateType.Rotate180: + newWidth = historyWorkspace.Document.Width; + newHeight = historyWorkspace.Document.Height; + break; + + default: + throw new InvalidEnumArgumentException("invalid RotateType"); + } + + // Figure out which icon and text to use + string iconResName; + string suffix; + + switch (rotation) + { + case RotateType.Rotate180: + iconResName = "Icons.MenuImageRotate180Icon.png"; + suffix = PdnResources.GetString("RotateAction.180"); + break; + + case RotateType.Clockwise90: + iconResName = "Icons.MenuImageRotate90CWIcon.png"; + suffix = PdnResources.GetString("RotateAction.90CW"); + break; + + case RotateType.CounterClockwise90: + iconResName = "Icons.MenuImageRotate90CCWIcon.png"; + suffix = PdnResources.GetString("RotateAction.90CCW"); + break; + + default: + throw new InvalidEnumArgumentException("invalid RotateType"); + } + + // Initialize the new Doc + string haNameFormat = PdnResources.GetString("RotateAction.HistoryMementoName.Format"); + string haName = string.Format(haNameFormat, StaticName, suffix); + ImageResource haImage = PdnResources.GetImageResource(iconResName); + + List actions = new List(); + + // do the memory allocation now: if this fails, we can still bail out cleanly + Document newDoc = new Document(newWidth, newHeight); + + if (!historyWorkspace.Selection.IsEmpty) + { + DeselectFunction da = new DeselectFunction(); + EnterCriticalRegion(); + HistoryMemento hm = da.Execute(historyWorkspace); + actions.Add(hm); + } + + ReplaceDocumentHistoryMemento rdha = new ReplaceDocumentHistoryMemento(null, null, historyWorkspace); + actions.Add(rdha); + + newDoc.ReplaceMetaDataFrom(historyWorkspace.Document); + + // TODO: serialize oldDoc to disk, and let the GC purge it if needed + OnProgress(0.0); + + for (int i = 0; i < historyWorkspace.Document.Layers.Count; ++i) + { + Layer layer = historyWorkspace.Document.Layers.GetAt(i); + + double progressStart = 100.0 * ((double)i / (double)historyWorkspace.Document.Layers.Count); + double progressEnd = 100.0 * ((double)(i + 1) / (double)historyWorkspace.Document.Layers.Count); + + if (layer is BitmapLayer) + { + Layer nl = RotateLayer((BitmapLayer)layer, rotation, newWidth, newHeight, progressStart, progressEnd); + newDoc.Layers.Add(nl); + } + else + { + throw new InvalidOperationException("Cannot Rotate non-BitmapLayers"); + } + + if (this.PleaseCancel) + { + break; + } + + OnProgress(progressEnd); + } + + CompoundHistoryMemento chm = new CompoundHistoryMemento( + haName, + haImage, + actions); + + if (this.PleaseCancel) + { + chm = null; + } + else + { + EnterCriticalRegion(); + historyWorkspace.Document = newDoc; + } + + return chm; + } + + private BitmapLayer RotateLayer(BitmapLayer layer, RotateType rotationType, int width, int height, double startProgress, double endProgress) + { + Surface surface = new Surface(width, height); + + if (rotationType == RotateType.Rotate180) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + surface[x, y] = layer.Surface[width - x - 1, height - y - 1]; + } + + OnProgress(((double)y / (double)height) * (endProgress - startProgress) + startProgress); + } + } + else if (rotationType == RotateType.CounterClockwise90) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + surface[x, y] = layer.Surface[height - y - 1, x]; + } + + OnProgress(((double)y / (double)height) * (endProgress - startProgress) + startProgress); + } + } + else if (rotationType == RotateType.Clockwise90) + { + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + surface[x, y] = layer.Surface[y, width - 1 - x]; + } + + OnProgress(((double)y / (double)height) * (endProgress - startProgress) + startProgress); + } + } + + BitmapLayer returnMe = new BitmapLayer(surface, true); + returnMe.LoadProperties(layer.SaveProperties()); + return returnMe; + } + + // TODO: because of ProgressDialog's incorrect code, ActionFlags.Cancellable doesn't work right + // fix this post-3.0. it is not a priority right now because this is the only action that + // uses this flag right now. + // Also had to disable progress reporting as there are some problems with running this + // action in the background since it has to perform a Deselect. This then causes things + // like invalidation to be performed on the wrong thread, etc. See bug #2358 + public RotateDocumentFunction(RotateType rotation) + : base(0 /*ActionFlags.ReportsProgress*/ /*| ActionFlags.Cancellable*/) + { + this.rotation = rotation; + } + } +} diff --git a/src/HistoryFunctions/SelectAllFunction.cs b/src/HistoryFunctions/SelectAllFunction.cs new file mode 100644 index 0000000..f79afba --- /dev/null +++ b/src/HistoryFunctions/SelectAllFunction.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class SelectAllFunction + : HistoryFunction + { + public static string StaticName + { + get + { + return PdnResources.GetString("SelectAllAction.Name"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + SelectionHistoryMemento sha = new SelectionHistoryMemento( + StaticName, + PdnResources.GetImageResource("Icons.MenuEditSelectAllIcon.png"), + historyWorkspace); + + EnterCriticalRegion(); + historyWorkspace.Selection.PerformChanging(); + historyWorkspace.Selection.Reset(); + historyWorkspace.Selection.SetContinuation(historyWorkspace.Document.Bounds, CombineMode.Replace); + historyWorkspace.Selection.CommitContinuation(); + historyWorkspace.Selection.PerformChanged(); + + return sha; + } + + public SelectAllFunction() + : base(ActionFlags.None) + { + } + } +} diff --git a/src/HistoryFunctions/SwapLayerFunction.cs b/src/HistoryFunctions/SwapLayerFunction.cs new file mode 100644 index 0000000..c1961ad --- /dev/null +++ b/src/HistoryFunctions/SwapLayerFunction.cs @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; + +namespace PaintDotNet.HistoryFunctions +{ + internal sealed class SwapLayerFunction + : HistoryFunction + { + private int layer1Index; + private int layer2Index; + + public static string StaticName + { + get + { + return PdnResources.GetString("SwapLayerFunction.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + // TODO: find a real icon for this? + //return PdnResources.GetImageResource("todo.png"); + return PdnResources.GetImageResource("Icons.MenuLayersMoveLayerUpIcon.png"); + } + } + + public override HistoryMemento OnExecute(IHistoryWorkspace historyWorkspace) + { + if (layer1Index < 0 || layer1Index >= historyWorkspace.Document.Layers.Count || + layer2Index < 0 || layer2Index >= historyWorkspace.Document.Layers.Count) + { + throw new ArgumentOutOfRangeException("layer1Index = " + this.layer1Index + ", layer2Index = " + layer2Index + ", expected [0," + historyWorkspace.Document.Layers.Count + ")"); + } + + SwapLayerHistoryMemento slhm = new SwapLayerHistoryMemento( + StaticName, + StaticImage, + historyWorkspace, + layer1Index, + layer2Index); + + Layer layer1 = historyWorkspace.Document.Layers.GetAt(layer1Index); + Layer layer2 = historyWorkspace.Document.Layers.GetAt(layer2Index); + + EnterCriticalRegion(); + historyWorkspace.Document.Layers[layer1Index] = layer2; + historyWorkspace.Document.Layers[layer2Index] = layer1; + + layer1.Invalidate(); + layer2.Invalidate(); + + return slhm; + } + + public SwapLayerFunction(int layer1Index, int layer2Index) + : base(ActionFlags.None) + { + this.layer1Index = layer1Index; + this.layer2Index = layer2Index; + } + } +} diff --git a/src/HistoryMemento.cs b/src/HistoryMemento.cs new file mode 100644 index 0000000..1def4e3 --- /dev/null +++ b/src/HistoryMemento.cs @@ -0,0 +1,181 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Threading; + +namespace PaintDotNet +{ + /// + /// A HistoryMemento is generally used to save part of the state of the Document + /// so that an action that is yet to be performed can be undone at a later time. + /// For example, if you are going to paint in a certain region, you first create a + /// HistoryMemento that saves the contents of the area you are painting to. Then you + /// paint. Then you push the history action on to the history stack. + /// + /// Using the HistoryMementoData class you can serialize your data to disk so that it + /// doesn't fester in memory. There are important rules to follow here though: + /// 1. Don't hold a reference to a Layer. Store a reference to the DocumentWorkspace and + /// the layer's index instead, and access it via Workspace.Document.Layers[index]. + /// 2. The exception to #1 is if you are deleting a layer. But you should use + /// DeleteLayerHistoryMemento for that. If you need to delete a layer as part of a + /// compound action, use CompoundHistoryMemento in conjunction with + /// DeleteLayerHistoryMemento. + /// 3. To generalize, avoid serializing something unless you're replacing or deleting it. + /// (and by 'serializing' I mean 'putting it in your HistoryMementoData class') + /// It is better to hold a 'navigation reference' as opposed to a real reference. + /// An example of a 'navigation reference' is listed in #1, where we don't store a ref + /// to the layer itself but we store the information needed to navigate to it. + /// The reasoning for this is made clear if you consider the following case. Assume you + /// are holding on to a layer reference ("private Layer theLayer;"). Next, assume that + /// the layer is deleted. Then the deletion is undone. The new layer in memory is not + /// the layer you have a reference to even though they hold the same data. Changes made + /// to one do not show up in the other one. Put another way, history actions should + /// store large objects and their locations "by value," and not "by reference." + /// + internal abstract class HistoryMemento + { + private string name; + public string Name + { + get + { + return this.name; + } + + set + { + this.name = value; + } + } + + private ImageResource image; + public ImageResource Image + { + get + { + return this.image; + } + + set + { + this.image = value; + } + } + + protected int id; + private static int nextId = 0; + public int ID + { + get + { + return this.id; + } + + set + { + this.id = value; + } + } + + private Guid seriesGuid = Guid.Empty; + public Guid SeriesGuid + { + get + { + return this.seriesGuid; + } + + set + { + this.seriesGuid = value; + } + } + + private PersistedObject historyMementoData = null; + + /// + /// Gets or sets the HistoryMementoData associated with this HistoryMemento. + /// + /// + /// Setting this property will immediately serialize the given object to disk. + /// + protected HistoryMementoData Data + { + get + { + if (historyMementoData == null) + { + return null; + } + else + { + return (HistoryMementoData)historyMementoData.Object; + } + } + + set + { + this.historyMementoData = new PersistedObject(value, false); + } + } + + /// + /// Ensures that the memory held by the Data property is serialized to disk and + /// freed from memory. + /// + public void Flush() + { + if (historyMementoData != null) + { + historyMementoData.Flush(); + } + + OnFlush(); + } + + protected virtual void OnFlush() + { + } + + /// + /// This will perform the necessary work required to undo an action. + /// Note that the returned HistoryMemento should have the same ID. + /// + /// + /// Returns a HistoryMemento that can be used to redo the action. + /// Note that this property should hold: undoAction = undoAction.PerformUndo().PerformUndo() + /// + protected abstract HistoryMemento OnUndo(); + + /// + /// This method ensures that the returned HistoryMemento has the appropriate ID tag. + /// + /// Returns a HistoryMemento that can be used to redo the action. + /// The ID of this HistoryMemento will be the same as the object that this + /// method was called on. + public HistoryMemento PerformUndo() + { + HistoryMemento ha = OnUndo(); + ha.ID = this.ID; + ha.SeriesGuid = this.SeriesGuid; + return ha; + } + + public HistoryMemento(string name, ImageResource image) + { + SystemLayer.Tracing.LogFeature("HM(" + GetType().Name + ")"); + + this.name = name; + this.image = image; + this.id = Interlocked.Increment(ref nextId); + } + } +} diff --git a/src/HistoryMementoData.cs b/src/HistoryMementoData.cs new file mode 100644 index 0000000..b2eabc5 --- /dev/null +++ b/src/HistoryMementoData.cs @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Stores data that should be serializable/deserializable + /// for a HistoryMemento. + /// + [Serializable] + internal abstract class HistoryMementoData + : IDisposable + { + ~HistoryMementoData() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + } +} diff --git a/src/HistoryMementos/BitmapHistoryMemento.cs b/src/HistoryMementos/BitmapHistoryMemento.cs new file mode 100644 index 0000000..5d43382 --- /dev/null +++ b/src/HistoryMementos/BitmapHistoryMemento.cs @@ -0,0 +1,337 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.InteropServices; +using System.Threading; + +namespace PaintDotNet.HistoryMementos +{ + internal class BitmapHistoryMemento + : HistoryMemento + { + private IHistoryWorkspace historyWorkspace; + private int layerIndex; + private string tempFileName; + private DeleteFileOnFree tempFileHandle; + private Guid poMaskedSurfaceRef; // if this is non-Guid.Empty, then tempFileName, tempFileHandle, and Data must be null + private Guid poUndoMaskedSurfaceRef; + + private class DeleteFileOnFree + : IDisposable + { + private IntPtr bstrFileName; + + public DeleteFileOnFree(string fileName) + { + this.bstrFileName = Marshal.StringToBSTR(fileName); + } + + ~DeleteFileOnFree() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (this.bstrFileName != IntPtr.Zero) + { + string fileName = Marshal.PtrToStringBSTR(this.bstrFileName); + Marshal.FreeBSTR(this.bstrFileName); + bool result = FileSystem.TryDeleteFile(fileName); + this.bstrFileName = IntPtr.Zero; + } + } + } + + [Serializable] + private sealed class BitmapHistoryMementoData + : HistoryMementoData + { + // only one of the following may be non-null + private IrregularSurface undoImage; + private PdnRegion savedRegion; + + public IrregularSurface UndoImage + { + get + { + return undoImage; + } + } + + public PdnRegion SavedRegion + { + get + { + return savedRegion; + } + } + + public BitmapHistoryMementoData(IrregularSurface undoImage, PdnRegion savedRegion) + { + if (undoImage != null && savedRegion != null) + { + throw new ArgumentException("Only one of undoImage or savedRegion may be non-null"); + } + + this.undoImage = undoImage; + this.savedRegion = savedRegion; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (undoImage != null) + { + undoImage.Dispose(); + undoImage = null; + } + + if (savedRegion != null) + { + savedRegion.Dispose(); + savedRegion = null; + } + } + + base.Dispose(disposing); + } + } + + private static unsafe void LoadOrSaveSurfaceRegion(FileStream fileHandle, Surface surface, PdnRegion region, bool trueForSave) + { + Rectangle[] scans = region.GetRegionScansReadOnlyInt(); + Rectangle regionBounds = region.GetBoundsInt(); + Rectangle surfaceBounds = surface.Bounds; + int scanCount = 0; + + void*[] ppvBuffers; + uint[] lengths; + + regionBounds.Intersect(surfaceBounds); + long length = (long)regionBounds.Width * (long)regionBounds.Height * (long)ColorBgra.SizeOf; + + if (scans.Length == 1 && + length <= uint.MaxValue && + surface.IsContiguousMemoryRegion(regionBounds)) + { + ppvBuffers = new void*[1]; + lengths = new uint[1]; + + ppvBuffers[0] = surface.GetPointAddressUnchecked(regionBounds.Location); + lengths[0] = (uint)length; + } + else + { + for (int i = 0; i < scans.Length; ++i) + { + Rectangle rect = scans[i]; + rect.Intersect(surfaceBounds); + + if (rect.Width != 0 && rect.Height != 0) + { + scanCount += rect.Height; + } + } + + int scanIndex = 0; + ppvBuffers = new void*[scanCount]; + lengths = new uint[scanCount]; + + for (int i = 0; i < scans.Length; ++i) + { + Rectangle rect = scans[i]; + rect.Intersect(surfaceBounds); + + if (rect.Width != 0 && rect.Height != 0) + { + for (int y = rect.Top; y < rect.Bottom; ++y) + { + ppvBuffers[scanIndex] = surface.GetPointAddressUnchecked(rect.Left, y); + lengths[scanIndex] = (uint)(rect.Width * ColorBgra.SizeOf); + ++scanIndex; + } + } + } + } + + if (trueForSave) + { + FileSystem.WriteToStreamingFileGather(fileHandle, ppvBuffers, lengths); + } + else + { + FileSystem.ReadFromStreamScatter(fileHandle, ppvBuffers, lengths); + } + } + + private static unsafe void SaveSurfaceRegion(FileStream outputHandle, Surface surface, PdnRegion region) + { + LoadOrSaveSurfaceRegion(outputHandle, surface, region, true); + } + + private static unsafe void LoadSurfaceRegion(FileStream inputHandle, Surface surface, PdnRegion region) + { + LoadOrSaveSurfaceRegion(inputHandle, surface, region, false); + } + + public BitmapHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, + int layerIndex, Guid poMaskedSurfaceRef) + : base(name, image) + { + this.layerIndex = layerIndex; + this.historyWorkspace = historyWorkspace; + this.poMaskedSurfaceRef = poMaskedSurfaceRef; + } + + public BitmapHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, + int layerIndex, PdnRegion changedRegion) + : this(name, image, historyWorkspace, layerIndex, changedRegion, + ((BitmapLayer)historyWorkspace.Document.Layers[layerIndex]).Surface) + { + } + + public BitmapHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, + int layerIndex, PdnRegion changedRegion, Surface copyFromThisSurface) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.layerIndex = layerIndex; + + PdnRegion region = changedRegion.Clone(); + this.tempFileName = FileSystem.GetTempFileName(); + + FileStream outputStream = null; + + try + { + outputStream = FileSystem.OpenStreamingFile(this.tempFileName, FileAccess.Write); + SaveSurfaceRegion(outputStream, copyFromThisSurface, region); + } + + finally + { + if (outputStream != null) + { + outputStream.Dispose(); + outputStream = null; + } + } + + this.tempFileHandle = new DeleteFileOnFree(this.tempFileName); + BitmapHistoryMementoData data = new BitmapHistoryMementoData(null, region); + + this.Data = data; + } + + public BitmapHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, + int layerIndex, IrregularSurface saved) + : this(name, image, historyWorkspace, layerIndex, saved, false) + { + } + + public BitmapHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, int layerIndex, + IrregularSurface saved, bool takeOwnershipOfSaved) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.layerIndex = layerIndex; + + IrregularSurface iss; + + if (takeOwnershipOfSaved) + { + iss = saved; + } + else + { + iss = (IrregularSurface)saved.Clone(); + } + + BitmapHistoryMementoData data = new BitmapHistoryMementoData(iss, null); + this.Data = data; + } + + protected override HistoryMemento OnUndo() + { + BitmapHistoryMementoData data = this.Data as BitmapHistoryMementoData; + BitmapLayer layer = (BitmapLayer)this.historyWorkspace.Document.Layers[this.layerIndex]; + + PdnRegion region; + MaskedSurface maskedSurface = null; + + if (this.poMaskedSurfaceRef != Guid.Empty) + { + PersistedObject poMS = PersistedObjectLocker.Get(this.poMaskedSurfaceRef); + maskedSurface = poMS.Object; + region = maskedSurface.CreateRegion(); + } + else if (data.UndoImage == null) + { + region = data.SavedRegion; + } + else + { + region = data.UndoImage.Region; + } + + BitmapHistoryMemento redo; + + if (this.poUndoMaskedSurfaceRef == Guid.Empty) + { + redo = new BitmapHistoryMemento(Name, Image, this.historyWorkspace, this.layerIndex, region); + redo.poUndoMaskedSurfaceRef = this.poMaskedSurfaceRef; + } + else + { + redo = new BitmapHistoryMemento(Name, Image, this.historyWorkspace, this.layerIndex, this.poUndoMaskedSurfaceRef); + } + + PdnRegion simplified = Utility.SimplifyAndInflateRegion(region); + + if (maskedSurface != null) + { + maskedSurface.Draw(layer.Surface); + } + else if (data.UndoImage == null) + { + using (FileStream input = FileSystem.OpenStreamingFile(this.tempFileName, FileAccess.Read)) + { + LoadSurfaceRegion(input, layer.Surface, data.SavedRegion); + } + + data.SavedRegion.Dispose(); + this.tempFileHandle.Dispose(); + this.tempFileHandle = null; + } + else + { + data.UndoImage.Draw(layer.Surface); + data.UndoImage.Dispose(); + } + + layer.Invalidate(simplified); + simplified.Dispose(); + + return redo; + } + } +} diff --git a/src/HistoryMementos/CompoundHistoryMemento.cs b/src/HistoryMementos/CompoundHistoryMemento.cs new file mode 100644 index 0000000..a105cb6 --- /dev/null +++ b/src/HistoryMementos/CompoundHistoryMemento.cs @@ -0,0 +1,83 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + /// + /// Lets you combine multiple HistoryMementos that can be undon/redone + /// in a single operation, and be referred to by one name. + /// The actions will be undone in the reverse order they are given to + /// the constructor via the actions array. + /// You can use 'null' for a HistoryMemento and it will be ignored. + /// + internal class CompoundHistoryMemento + : HistoryMemento + { + private List actions; + + protected override void OnFlush() + { + for (int i = 0; i < actions.Count; ++i) + { + if (actions[i] != null) + { + actions[i].Flush(); + } + } + } + + protected override HistoryMemento OnUndo() + { + List redoActions = new List(actions.Count); + + for (int i = 0; i < actions.Count; ++i) + { + HistoryMemento ha = actions[actions.Count - i - 1]; + HistoryMemento rha = null; + + if (ha != null) + { + rha = ha.PerformUndo(); + } + + redoActions.Add(rha); + } + + CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, redoActions); + return cha; + } + + public void PushNewAction(HistoryMemento newHA) + { + actions.Add(newHA); + } + + public CompoundHistoryMemento(string name, ImageResource image, List actions) + : base(name, image) + { + this.actions = new List(actions); + } + + public CompoundHistoryMemento(string name, ImageResource image, HistoryMemento[] actions) + : base(name, image) + { + this.actions = new List(actions); + } + + public CompoundHistoryMemento(string name, ImageResource image) + : this(name, image, new HistoryMemento[0]) + { + } + } +} diff --git a/src/HistoryMementos/DeleteLayerHistoryMemento.cs b/src/HistoryMementos/DeleteLayerHistoryMemento.cs new file mode 100644 index 0000000..f9ec66a --- /dev/null +++ b/src/HistoryMementos/DeleteLayerHistoryMemento.cs @@ -0,0 +1,73 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + /// + /// Provides the ability to undo deleting a layer. + /// + internal class DeleteLayerHistoryMemento + : HistoryMemento + { + private int index; + private IHistoryWorkspace historyWorkspace; + + [Serializable] + private sealed class DeleteLayerHistoryMementoData + : HistoryMementoData + { + private Layer layer; + + public Layer Layer + { + get + { + return layer; + } + } + + public DeleteLayerHistoryMementoData(Layer layer) + { + this.layer = layer; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (layer != null) + { + layer.Dispose(); + layer = null; + } + } + } + } + + protected override HistoryMemento OnUndo() + { + DeleteLayerHistoryMementoData data = (DeleteLayerHistoryMementoData)this.Data; + HistoryMemento ha = new NewLayerHistoryMemento(Name, Image, this.historyWorkspace, this.index); + this.historyWorkspace.Document.Layers.Insert(index, data.Layer); + ((Layer)this.historyWorkspace.Document.Layers[index]).Invalidate(); + return ha; + } + + public DeleteLayerHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, Layer deleteMe) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.index = historyWorkspace.Document.Layers.IndexOf(deleteMe); + this.Data = new DeleteLayerHistoryMementoData(deleteMe); + } + } +} diff --git a/src/HistoryMementos/FlipLayerHistoryMemento.cs b/src/HistoryMementos/FlipLayerHistoryMemento.cs new file mode 100644 index 0000000..2f44f95 --- /dev/null +++ b/src/HistoryMementos/FlipLayerHistoryMemento.cs @@ -0,0 +1,78 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + internal class FlipLayerHistoryMemento + : HistoryMemento + { + private IHistoryWorkspace historyWorkspace; + private int layerIndex; + private FlipType flipType; + + private void Flip(Surface surface) + { + switch (this.flipType) + { + case FlipType.Horizontal: + for (int y = 0; y < surface.Height; ++y) + { + for (int x = 0; x < surface.Width / 2; ++x) + { + ColorBgra temp = surface[x, y]; + surface[x, y] = surface[surface.Width - x - 1, y]; + surface[surface.Width - x - 1, y] = temp; + } + } + + break; + + case FlipType.Vertical: + for (int x = 0; x < surface.Width; ++x) + { + for (int y = 0; y < surface.Height / 2; ++y) + { + ColorBgra temp = surface[x, y]; + surface[x, y] = surface[x, surface.Height - y - 1]; + surface[x, surface.Height - y - 1] = temp; + } + } + + break; + + default: + throw new InvalidOperationException("FlipType was invalid"); + } + + return; + } + + protected override HistoryMemento OnUndo() + { + FlipLayerHistoryMemento fha = new FlipLayerHistoryMemento(this.Name, this.Image, + this.historyWorkspace, layerIndex, flipType); + + BitmapLayer layer = (BitmapLayer)this.historyWorkspace.Document.Layers[layerIndex]; + Flip(layer.Surface); + layer.Invalidate(); + return fha; + } + + public FlipLayerHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, int layerIndex, FlipType flipType) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.layerIndex = layerIndex; + this.flipType = flipType; + } + } +} diff --git a/src/HistoryMementos/LayerPropertyHistoryMemento.cs b/src/HistoryMementos/LayerPropertyHistoryMemento.cs new file mode 100644 index 0000000..c65ec0d --- /dev/null +++ b/src/HistoryMementos/LayerPropertyHistoryMemento.cs @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections.Specialized; + +namespace PaintDotNet.HistoryMementos +{ + internal class LayerPropertyHistoryMemento + : HistoryMemento + { + private object properties; + private IHistoryWorkspace historyWorkspace; + private int layerIndex; + + protected override HistoryMemento OnUndo() + { + HistoryMemento ha = new LayerPropertyHistoryMemento(Name, Image, this.historyWorkspace, this.layerIndex); + Layer layer = (Layer)this.historyWorkspace.Document.Layers[layerIndex]; + layer.LoadProperties(properties, true); + layer.PerformPropertyChanged(); + return ha; + } + + public LayerPropertyHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, int layerIndex) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.layerIndex = layerIndex; + this.properties = ((Layer)this.historyWorkspace.Document.Layers[layerIndex]).SaveProperties(); + } + } +} diff --git a/src/HistoryMementos/MetaDataHistoryMemento.cs b/src/HistoryMementos/MetaDataHistoryMemento.cs new file mode 100644 index 0000000..bed5524 --- /dev/null +++ b/src/HistoryMementos/MetaDataHistoryMemento.cs @@ -0,0 +1,75 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + /// + /// Saves the state of the Document's metadata. + /// + internal class MetaDataHistoryMemento + : HistoryMemento + { + private IHistoryWorkspace historyWorkspace; + + [Serializable] + private class MetaDataHistoryMementoData + : HistoryMementoData + { + private Document document; + + public Document Document + { + get + { + return this.document; + } + } + + public MetaDataHistoryMementoData(Document document) + { + this.document = document; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.document != null) + { + this.document.Dispose(); + this.document = null; + } + } + + base.Dispose(disposing); + } + } + + public MetaDataHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + Document document = new Document(1, 1); // we need some place to store the metadata... + document.ReplaceMetaDataFrom(historyWorkspace.Document); + MetaDataHistoryMementoData data = new MetaDataHistoryMementoData(document); + this.Data = data; + } + + protected override HistoryMemento OnUndo() + { + MetaDataHistoryMemento redo = new MetaDataHistoryMemento(this.Name, this.Image, this.historyWorkspace); + MetaDataHistoryMementoData data = (MetaDataHistoryMementoData)this.Data; + this.historyWorkspace.Document.ReplaceMetaDataFrom(data.Document); + return redo; + } + } +} diff --git a/src/HistoryMementos/NewLayerHistoryMemento.cs b/src/HistoryMementos/NewLayerHistoryMemento.cs new file mode 100644 index 0000000..5aaeb7d --- /dev/null +++ b/src/HistoryMementos/NewLayerHistoryMemento.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + internal class NewLayerHistoryMemento + : HistoryMemento + { + private int layerIndex; + private IHistoryWorkspace historyWorkspace; + + protected override HistoryMemento OnUndo() + { + DeleteLayerHistoryMemento ha = new DeleteLayerHistoryMemento(Name, Image, this.historyWorkspace, + (Layer)this.historyWorkspace.Document.Layers[layerIndex]); + + ha.ID = this.ID; + this.historyWorkspace.Document.Layers.RemoveAt(layerIndex); + this.historyWorkspace.Document.Invalidate(); + return ha; + } + + public NewLayerHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, int layerIndex) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.layerIndex = layerIndex; + } + } +} diff --git a/src/HistoryMementos/NullHistoryMemento.cs b/src/HistoryMementos/NullHistoryMemento.cs new file mode 100644 index 0000000..9526e84 --- /dev/null +++ b/src/HistoryMementos/NullHistoryMemento.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + /// + /// This history action doesn't really do anything. It is useful for putting in a + /// "New Image" placeholder, since the first item in the undo stack can't really + /// be "undone". + /// NullHistoryMemento instances are also not undoable. + /// + internal class NullHistoryMemento + : HistoryMemento + { + protected override HistoryMemento OnUndo() + { + throw new InvalidOperationException("NullHistoryMementos are not undoable"); + } + + public NullHistoryMemento(string name, ImageResource image) + : base(name, image) + { + } + } +} diff --git a/src/HistoryMementos/ReplaceDocumentHistoryMemento.cs b/src/HistoryMementos/ReplaceDocumentHistoryMemento.cs new file mode 100644 index 0000000..9ddb548 --- /dev/null +++ b/src/HistoryMementos/ReplaceDocumentHistoryMemento.cs @@ -0,0 +1,73 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + /// + /// This HistoryMemento can be used to save an entire Document for undo purposes + /// Create this HistoryMemento, then use SetDocument(), then push this on to the + /// History using PushNewAction. + /// + internal class ReplaceDocumentHistoryMemento + : HistoryMemento + { + private IHistoryWorkspace historyWorkspace; + + [Serializable] + private sealed class ReplaceDocumentHistoryMementoData + : HistoryMementoData + { + private Document oldDocument; + + public Document OldDocument + { + get + { + return oldDocument; + } + } + + public ReplaceDocumentHistoryMementoData(Document oldDocument) + { + this.oldDocument = oldDocument; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (oldDocument != null) + { + oldDocument.Dispose(); + oldDocument = null; + } + } + } + } + + public ReplaceDocumentHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + + ReplaceDocumentHistoryMementoData data = new ReplaceDocumentHistoryMementoData(this.historyWorkspace.Document); + this.Data = data; + } + + protected override HistoryMemento OnUndo() + { + ReplaceDocumentHistoryMemento ha = new ReplaceDocumentHistoryMemento(Name, Image, this.historyWorkspace); + this.historyWorkspace.Document = ((ReplaceDocumentHistoryMementoData)Data).OldDocument; + return ha; + } + } +} diff --git a/src/HistoryMementos/SelectionHistoryMemento.cs b/src/HistoryMementos/SelectionHistoryMemento.cs new file mode 100644 index 0000000..a81fda9 --- /dev/null +++ b/src/HistoryMementos/SelectionHistoryMemento.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet.HistoryMementos +{ + internal class SelectionHistoryMemento + : HistoryMemento + { + private object savedSelectionData; + private IHistoryWorkspace historyWorkspace; + + public SelectionHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.savedSelectionData = this.historyWorkspace.Selection.Save(); + } + + protected override HistoryMemento OnUndo() + { + SelectionHistoryMemento sha = new SelectionHistoryMemento(Name, Image, this.historyWorkspace); + this.historyWorkspace.Selection.Restore(this.savedSelectionData); + return sha; + } + } +} diff --git a/src/HistoryMementos/SwapLayerHistoryMemento.cs b/src/HistoryMementos/SwapLayerHistoryMemento.cs new file mode 100644 index 0000000..bec25de --- /dev/null +++ b/src/HistoryMementos/SwapLayerHistoryMemento.cs @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + internal class SwapLayerHistoryMemento + : HistoryMemento + { + private int layerIndex1; + private int layerIndex2; + private IHistoryWorkspace historyWorkspace; + + protected override HistoryMemento OnUndo() + { + SwapLayerHistoryMemento slha = new SwapLayerHistoryMemento(this.Name, this.Image, + this.historyWorkspace, this.layerIndex2, this.layerIndex1); + + Layer layer1 = (Layer)this.historyWorkspace.Document.Layers[this.layerIndex1]; + Layer layer2 = (Layer)this.historyWorkspace.Document.Layers[this.layerIndex2]; + + int firstIndex = Math.Min(layerIndex1, layerIndex2); + int secondIndex = Math.Max(layerIndex1, layerIndex2); + + if (secondIndex - firstIndex == 1) + { + this.historyWorkspace.Document.Layers.RemoveAt(layerIndex1); + this.historyWorkspace.Document.Layers.Insert(layerIndex2, layer1); + } + else + { + // general version + this.historyWorkspace.Document.Layers[layerIndex1] = layer2; + this.historyWorkspace.Document.Layers[layerIndex2] = layer1; + } + + ((Layer)this.historyWorkspace.Document.Layers[this.layerIndex1]).Invalidate(); + ((Layer)this.historyWorkspace.Document.Layers[this.layerIndex2]).Invalidate(); + + return slha; + } + + public SwapLayerHistoryMemento(string name, ImageResource image, IHistoryWorkspace historyWorkspace, int layerIndex1, int layerIndex2) + : base(name, image) + { + this.historyWorkspace = historyWorkspace; + this.layerIndex1 = layerIndex1; + this.layerIndex2 = layerIndex2; + + if (this.layerIndex1 < 0 || this.layerIndex2 < 0 || + this.layerIndex1 >= this.historyWorkspace.Document.Layers.Count || + this.layerIndex2 >= this.historyWorkspace.Document.Layers.Count) + { + throw new ArgumentOutOfRangeException("layerIndex[1|2]", "out of range"); + } + } + } +} diff --git a/src/HistoryMementos/ToolHistoryMemento.cs b/src/HistoryMementos/ToolHistoryMemento.cs new file mode 100644 index 0000000..4b7e1c8 --- /dev/null +++ b/src/HistoryMementos/ToolHistoryMemento.cs @@ -0,0 +1,60 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.HistoryMementos +{ + /// + /// A Tool may implement this class in order to provide history actions that do + /// not deactivate the tool while being undone or redone. + /// + internal abstract class ToolHistoryMemento + : HistoryMemento + { + private DocumentWorkspace documentWorkspace; + private Type toolType; + + protected DocumentWorkspace DocumentWorkspace + { + get + { + return this.documentWorkspace; + } + } + + public Type ToolType + { + get + { + return this.toolType; + } + } + + protected abstract HistoryMemento OnToolUndo(); + + protected sealed override HistoryMemento OnUndo() + { + if (this.documentWorkspace.GetToolType() != this.toolType) + { + this.documentWorkspace.SetToolFromType(this.toolType); + } + + return OnToolUndo(); + } + + public ToolHistoryMemento(DocumentWorkspace documentWorkspace, string name, ImageResource image) + : base(name, image) + { + this.documentWorkspace = documentWorkspace; + this.toolType = documentWorkspace.GetToolType(); + } + } +} diff --git a/src/HistoryStack.cs b/src/HistoryStack.cs new file mode 100644 index 0000000..9b3efe2 --- /dev/null +++ b/src/HistoryStack.cs @@ -0,0 +1,395 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// The HistoryStack class for the History "concept". + /// Serves as the undo and redo stacks. + /// + [Serializable] + internal class HistoryStack + { + private List undoStack; + private List redoStack; + private DocumentWorkspace documentWorkspace; + private int stepGroupDepth; + private int isExecutingMemento = 0; // 0 -> false, >0 -> true + + public bool IsExecutingMemento + { + get + { + return this.isExecutingMemento > 0; + } + } + + private void PushExecutingMemento() + { + ++this.isExecutingMemento; + } + + private void PopExecutingMemento() + { + --this.isExecutingMemento; + } + + public List UndoStack + { + get + { + return this.undoStack; + } + } + + public List RedoStack + { + get + { + return this.redoStack; + } + } + + public void BeginStepGroup() + { + ++this.stepGroupDepth; + } + + public void EndStepGroup() + { + --this.stepGroupDepth; + + if (this.stepGroupDepth == 0) + { + OnFinishedStepGroup(); + } + } + + public event EventHandler FinishedStepGroup; + protected void OnFinishedStepGroup() + { + if (FinishedStepGroup != null) + { + FinishedStepGroup(this, EventArgs.Empty); + } + } + + public event EventHandler SteppedBackward; + protected void OnSteppedBackward() + { + if (SteppedBackward != null) + { + SteppedBackward(this, EventArgs.Empty); + } + } + + public event EventHandler SteppedForward; + protected void OnSteppedForward() + { + if (SteppedForward != null) + { + SteppedForward(this, EventArgs.Empty); + } + } + + /// + /// Event handler for when a new history memento has been added. + /// + public event EventHandler NewHistoryMemento; + protected void OnNewHistoryMemento() + { + if (NewHistoryMemento != null) + { + NewHistoryMemento(this, EventArgs.Empty); + } + } + + /// + /// Event handler for when changes have been made to the history. + /// + public event EventHandler Changed; + protected void OnChanged() + { + if (Changed != null) + { + Changed(this, EventArgs.Empty); + } + } + + public event EventHandler Changing; + protected void OnChanging() + { + if (Changing != null) + { + Changing(this, EventArgs.Empty); + } + } + + public event EventHandler HistoryFlushed; + protected void OnHistoryFlushed() + { + if (HistoryFlushed != null) + { + HistoryFlushed(this, EventArgs.Empty); + } + } + + public event ExecutingHistoryMementoEventHandler ExecutingHistoryMemento; + protected void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e) + { + if (ExecutingHistoryMemento != null) + { + ExecutingHistoryMemento(this, e); + } + } + + public event ExecutedHistoryMementoEventHandler ExecutedHistoryMemento; + protected void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e) + { + if (ExecutedHistoryMemento != null) + { + ExecutedHistoryMemento(this, e); + } + } + + public void PerformChanged() + { + OnChanged(); + } + + public HistoryStack(DocumentWorkspace documentWorkspace) + { + this.documentWorkspace = documentWorkspace; + undoStack = new List(); + redoStack = new List(); + } + + private HistoryStack( + List undoStack, + List redoStack) + { + this.undoStack = new List(undoStack); + this.redoStack = new List(redoStack); + } + + /// + /// When the user does something new, it will clear out the redo stack. + /// + public void PushNewMemento(HistoryMemento value) + { + Utility.GCFullCollect(); + + OnChanging(); + + ClearRedoStack(); + undoStack.Add(value); + OnNewHistoryMemento(); + + OnChanged(); + + value.Flush(); + Utility.GCFullCollect(); + } + + /// + /// Takes one item from the redo stack, "redoes" it, then places the redo + /// memento object to the top of the undo stack. + /// + public void StepForward() + { + PushExecutingMemento(); + + try + { + StepForwardImpl(); + } + + finally + { + PopExecutingMemento(); + } + } + + private void StepForwardImpl() + { + HistoryMemento topMemento = redoStack[0]; + ToolHistoryMemento asToolHistoryMemento = topMemento as ToolHistoryMemento; + + if (asToolHistoryMemento != null && asToolHistoryMemento.ToolType != this.documentWorkspace.GetToolType()) + { + this.documentWorkspace.SetToolFromType(asToolHistoryMemento.ToolType); + StepForward(); + } + else + { + OnChanging(); + + ExecutingHistoryMementoEventArgs ehaea1 = new ExecutingHistoryMementoEventArgs(topMemento, true, false); + + if (asToolHistoryMemento == null && topMemento.SeriesGuid != Guid.Empty) + { + ehaea1.SuspendTool = true; + } + + OnExecutingHistoryMemento(ehaea1); + + if (ehaea1.SuspendTool) + { + this.documentWorkspace.PushNullTool(); + } + + HistoryMemento redoMemento = redoStack[0]; + + // Possibly useful invariant here: + // ehaea1.HistoryMemento.SeriesGuid == ehaea2.HistoryMemento.SeriesGuid == ehaea3.HistoryMemento.SeriesGuid + ExecutingHistoryMementoEventArgs ehaea2 = new ExecutingHistoryMementoEventArgs(redoMemento, false, ehaea1.SuspendTool); + OnExecutingHistoryMemento(ehaea2); + + HistoryMemento undoMemento = redoMemento.PerformUndo(); + + redoStack.RemoveAt(0); + undoStack.Add(undoMemento); + + ExecutedHistoryMementoEventArgs ehaea3 = new ExecutedHistoryMementoEventArgs(undoMemento); + OnExecutedHistoryMemento(ehaea3); + + OnChanged(); + OnSteppedForward(); + + undoMemento.Flush(); + + if (ehaea1.SuspendTool) + { + this.documentWorkspace.PopNullTool(); + } + } + + if (this.stepGroupDepth == 0) + { + OnFinishedStepGroup(); + } + } + + /// + /// Undoes the top of the undo stack, then places the redo memento object to the + /// top of the redo stack. + /// + public void StepBackward() + { + PushExecutingMemento(); + + try + { + StepBackwardImpl(); + } + + finally + { + PopExecutingMemento(); + } + } + + private void StepBackwardImpl() + { + HistoryMemento topMemento = undoStack[undoStack.Count - 1]; + ToolHistoryMemento asToolHistoryMemento = topMemento as ToolHistoryMemento; + + if (asToolHistoryMemento != null && asToolHistoryMemento.ToolType != this.documentWorkspace.GetToolType()) + { + this.documentWorkspace.SetToolFromType(asToolHistoryMemento.ToolType); + StepBackward(); + } + else + { + OnChanging(); + + ExecutingHistoryMementoEventArgs ehaea1 = new ExecutingHistoryMementoEventArgs(topMemento, true, false); + + if (asToolHistoryMemento == null && topMemento.SeriesGuid == Guid.Empty) + { + ehaea1.SuspendTool = true; + } + + OnExecutingHistoryMemento(ehaea1); + + if (ehaea1.SuspendTool) + { + this.documentWorkspace.PushNullTool(); + } + + HistoryMemento undoMemento = undoStack[undoStack.Count - 1]; + + ExecutingHistoryMementoEventArgs ehaea2 = new ExecutingHistoryMementoEventArgs(undoMemento, false, ehaea1.SuspendTool); + OnExecutingHistoryMemento(ehaea2); + + HistoryMemento redoMemento = undoStack[undoStack.Count - 1].PerformUndo(); + undoStack.RemoveAt(undoStack.Count - 1); + redoStack.Insert(0, redoMemento); + + // Possibly useful invariant here: + // ehaea1.HistoryMemento.SeriesGuid == ehaea2.HistoryMemento.SeriesGuid == ehaea3.HistoryMemento.SeriesGuid + ExecutedHistoryMementoEventArgs ehaea3 = new ExecutedHistoryMementoEventArgs(redoMemento); + OnExecutedHistoryMemento(ehaea3); + + OnChanged(); + OnSteppedBackward(); + + redoMemento.Flush(); + + if (ehaea1.SuspendTool) + { + this.documentWorkspace.PopNullTool(); + } + } + + if (this.stepGroupDepth == 0) + { + OnFinishedStepGroup(); + } + } + + public void ClearAll() + { + OnChanging(); + + foreach (HistoryMemento ha in undoStack) + { + ha.Flush(); + } + + foreach (HistoryMemento ha in redoStack) + { + ha.Flush(); + } + + undoStack = new List(); + redoStack = new List(); + OnChanged(); + OnHistoryFlushed(); + } + + public void ClearRedoStack() + { + foreach (HistoryMemento ha in redoStack) + { + ha.Flush(); + } + + OnChanging(); + redoStack = new List(); + OnChanged(); + } + } +} diff --git a/src/IAlphaBlendingConfig.cs b/src/IAlphaBlendingConfig.cs new file mode 100644 index 0000000..896442e --- /dev/null +++ b/src/IAlphaBlendingConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IAlphaBlendingConfig + { + event EventHandler AlphaBlendingChanged; + + bool AlphaBlending + { + get; + set; + } + + void PerformAlphaBlendingChanged(); + } +} diff --git a/src/IAntiAliasingConfig.cs b/src/IAntiAliasingConfig.cs new file mode 100644 index 0000000..c529464 --- /dev/null +++ b/src/IAntiAliasingConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IAntiAliasingConfig + { + event EventHandler AntiAliasingChanged; + + bool AntiAliasing + { + get; + set; + } + + void PerformAntiAliasingChanged(); + } +} diff --git a/src/IBrushConfig.cs b/src/IBrushConfig.cs new file mode 100644 index 0000000..2838cda --- /dev/null +++ b/src/IBrushConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IBrushConfig + { + event EventHandler BrushInfoChanged; + + BrushInfo BrushInfo + { + get; + set; + } + + void PerformBrushChanged(); + } +} diff --git a/src/IColorPickerConfig.cs b/src/IColorPickerConfig.cs new file mode 100644 index 0000000..e163a04 --- /dev/null +++ b/src/IColorPickerConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IColorPickerConfig + { + event EventHandler ColorPickerClickBehaviorChanged; + + ColorPickerClickBehavior ColorPickerClickBehavior + { + get; + set; + } + + void PerformColorPickerClickBehaviorChanged(); + } +} diff --git a/src/IDocumentList.cs b/src/IDocumentList.cs new file mode 100644 index 0000000..78edfb5 --- /dev/null +++ b/src/IDocumentList.cs @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IDocumentList + { + /// + /// This event is raised when the user clicks on a Document in the list. + /// + event EventHandler>> DocumentClicked; + + event EventHandler DocumentListChanged; + + DocumentWorkspace[] DocumentList + { + get; + } + + int DocumentCount + { + get; + } + + void AddDocumentWorkspace(DocumentWorkspace addMe); + void RemoveDocumentWorkspace(DocumentWorkspace removeMe); + void SelectDocumentWorkspace(DocumentWorkspace selectMe); + } +} diff --git a/src/IFloodModeConfig.cs b/src/IFloodModeConfig.cs new file mode 100644 index 0000000..36ab716 --- /dev/null +++ b/src/IFloodModeConfig.cs @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + internal interface IFloodModeConfig + { + event EventHandler FloodModeChanged; + + FloodMode FloodMode + { + get; + set; + } + + void PerformFloodModeChanged(); + } +} diff --git a/src/IGradientConfig.cs b/src/IGradientConfig.cs new file mode 100644 index 0000000..b4a03c2 --- /dev/null +++ b/src/IGradientConfig.cs @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + internal interface IGradientConfig + { + event EventHandler GradientInfoChanged; + + GradientInfo GradientInfo + { + get; + set; + } + + void PerformGradientInfoChanged(); + } +} diff --git a/src/IHistoryWorkspace.cs b/src/IHistoryWorkspace.cs new file mode 100644 index 0000000..cc7851c --- /dev/null +++ b/src/IHistoryWorkspace.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + internal interface IHistoryWorkspace + { + Document Document + { + get; + set; + } + + Selection Selection + { + get; + } + + Layer ActiveLayer + { + get; + } + + int ActiveLayerIndex + { + get; + } + } +} diff --git a/src/IPenConfig.cs b/src/IPenConfig.cs new file mode 100644 index 0000000..40c71f6 --- /dev/null +++ b/src/IPenConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IPenConfig + { + event EventHandler PenInfoChanged; + + PenInfo PenInfo + { + get; + set; + } + + void PerformPenChanged(); + } +} diff --git a/src/IResamplingConfig.cs b/src/IResamplingConfig.cs new file mode 100644 index 0000000..5dd2380 --- /dev/null +++ b/src/IResamplingConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IResamplingConfig + { + event EventHandler ResamplingAlgorithmChanged; + + ResamplingAlgorithm ResamplingAlgorithm + { + get; + set; + } + + void PerformResamplingAlgorithmChanged(); + } +} diff --git a/src/ISelectionCombineModeConfig.cs b/src/ISelectionCombineModeConfig.cs new file mode 100644 index 0000000..a4282aa --- /dev/null +++ b/src/ISelectionCombineModeConfig.cs @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + internal interface ISelectionCombineModeConfig + { + event EventHandler SelectionCombineModeChanged; + + CombineMode SelectionCombineMode + { + get; + set; + } + + void PerformSelectionCombineModeChanged(); + } +} diff --git a/src/ISelectionDrawModeConfig.cs b/src/ISelectionDrawModeConfig.cs new file mode 100644 index 0000000..e525076 --- /dev/null +++ b/src/ISelectionDrawModeConfig.cs @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + internal interface ISelectionDrawModeConfig + { + event EventHandler SelectionDrawModeInfoChanged; + + SelectionDrawModeInfo SelectionDrawModeInfo + { + get; + set; + } + + void PerformSelectionDrawModeInfoChanged(); + } +} diff --git a/src/IShapeTypeConfig.cs b/src/IShapeTypeConfig.cs new file mode 100644 index 0000000..3c216ae --- /dev/null +++ b/src/IShapeTypeConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IShapeTypeConfig + { + void PerformShapeDrawTypeChanged(); + + ShapeDrawType ShapeDrawType + { + get; + set; + } + + event EventHandler ShapeDrawTypeChanged; + } +} diff --git a/src/IStatusBarProgress.cs b/src/IStatusBarProgress.cs new file mode 100644 index 0000000..ac9510a --- /dev/null +++ b/src/IStatusBarProgress.cs @@ -0,0 +1,23 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IStatusBarProgress + { + void EraseProgressStatusBar(); + void EraseProgressStatusBarAsync(); + double GetProgressStatusBarValue(); + void ResetProgressStatusBar(); + void ResetProgressStatusBarAsync(); + void SetProgressStatusBar(double percent); + } +} diff --git a/src/ITextConfig.cs b/src/ITextConfig.cs new file mode 100644 index 0000000..d1af022 --- /dev/null +++ b/src/ITextConfig.cs @@ -0,0 +1,58 @@ +//////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; + +namespace PaintDotNet +{ + internal interface ITextConfig + { + event EventHandler FontInfoChanged; + event EventHandler FontSmoothingChanged; + event EventHandler FontAlignmentChanged; + + FontInfo FontInfo + { + get; + set; + } + + FontFamily FontFamily + { + get; + set; + } + + float FontSize + { + get; + set; + } + + FontStyle FontStyle + { + get; + set; + } + + FontSmoothing FontSmoothing + { + get; + set; + } + + TextAlignment FontAlignment + { + get; + set; + } + } +} diff --git a/src/IToleranceConfig.cs b/src/IToleranceConfig.cs new file mode 100644 index 0000000..b412bfd --- /dev/null +++ b/src/IToleranceConfig.cs @@ -0,0 +1,26 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IToleranceConfig + { + void PerformToleranceChanged(); + + float Tolerance + { + get; + set; + } + + event EventHandler ToleranceChanged; + } +} diff --git a/src/IToolChooser.cs b/src/IToolChooser.cs new file mode 100644 index 0000000..cfc6762 --- /dev/null +++ b/src/IToolChooser.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal interface IToolChooser + { + void SelectTool(Type toolType); + void SelectTool(Type toolType, bool raiseEvent); + void SetTools(ToolInfo[] toolInfos); + event ToolClickedEventHandler ToolClicked; + } +} diff --git a/src/IconBox.cs b/src/IconBox.cs new file mode 100644 index 0000000..900b969 --- /dev/null +++ b/src/IconBox.cs @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class IconBox : + System.Windows.Forms.UserControl + { + private Bitmap renderSurface = null; + private Bitmap icon = null; + + public Bitmap Icon + { + get + { + return icon; + } + + set + { + if (value == null) + { + value = new Bitmap(1, 1); + + using (Graphics g = Graphics.FromImage(value)) + { + g.Clear(Color.Transparent); + } + } + + icon = value; + + if (renderSurface != null) + { + renderSurface.Dispose(); + } + + renderSurface = null; + Invalidate(); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + e.Graphics.Clear(this.BackColor); + Rectangle srcBounds = new Rectangle(new Point(0, 0), this.icon.Size); + Rectangle dstBounds = new Rectangle(new Point(0, 0), this.ClientSize); + e.Graphics.DrawImage(this.Icon, dstBounds, srcBounds, GraphicsUnit.Pixel); + + base.OnPaint(e); + } + + public IconBox() + { + this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true); + + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + this.ResizeRedraw = true; + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + } + #endregion + } +} diff --git a/src/IconBox.resx b/src/IconBox.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Interop.WIA/Interop.WIA.dll b/src/Interop.WIA/Interop.WIA.dll new file mode 100644 index 0000000..48fcb7e Binary files /dev/null and b/src/Interop.WIA/Interop.WIA.dll differ diff --git a/src/LayerControl.cs b/src/LayerControl.cs new file mode 100644 index 0000000..45b6482 --- /dev/null +++ b/src/LayerControl.cs @@ -0,0 +1,625 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +using System.Diagnostics; + +namespace PaintDotNet +{ + internal class LayerControl + : UserControl + { + private class PanelWithLayout + : PanelEx + { + private LayerControl parentLayerControl; + public LayerControl ParentLayerControl + { + get + { + return this.parentLayerControl; + } + + set + { + this.parentLayerControl = value; + } + } + + public PanelWithLayout() + { + this.HideHScroll = true; + } + + public void PositionLayers() + { + if (this.parentLayerControl != null && + this.parentLayerControl.layerControls != null) + { + int cursor = this.AutoScrollPosition.Y; + int newWidth = this.ClientRectangle.Width; + + for (int i = this.parentLayerControl.layerControls.Count - 1; i >= 0; --i) + { + LayerElement lec = this.parentLayerControl.layerControls[i]; + lec.Width = newWidth; + lec.Top = cursor; + cursor += lec.Height; + } + } + } + + protected override void OnResize(EventArgs eventargs) + { + SystemLayer.UI.SuspendControlPainting(this); + PositionLayers(); + this.AutoScrollPosition = new Point(0, -this.AutoScrollOffset.Y); + base.OnResize(eventargs); + SystemLayer.UI.ResumeControlPainting(this); + Invalidate(true); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + PositionLayers(); + base.OnLayout(levent); + } + } + + public void PositionLayers() + { + this.layerControlPanel.PositionLayers(); + } + + private EventHandler elementClickDelegate; + private EventHandler elementDoubleClickDelegate; + private EventHandler documentChangedDelegate; + private EventHandler> documentChangingDelegate; + private EventHandler layerChangedDelegate; + private KeyEventHandler keyUpDelegate; + private IndexEventHandler layerInsertedDelegate; + private IndexEventHandler layerRemovedDelegate; + + private int elementHeight; + private int thumbnailSize; + + private AppWorkspace appWorkspace; + private Document document; + + private List layerControls; + private PanelWithLayout layerControlPanel; + + private ThumbnailManager thumbnailManager; + + [Browsable(false)] + public LayerElement[] Layers + { + get + { + if (layerControls == null) + { + return new LayerElement[0]; + } + else + { + return this.layerControls.ToArray(); + } + } + } + + public Layer ActiveLayer + { + get + { + int[] selected = SelectedLayerIndexes; + + if (selected.Length == 1) + { + return this.Layers[selected[0]].Layer; + } + else + { + return null; + } + } + } + + public int ActiveLayerIndex + { + get + { + int[] selected = SelectedLayerIndexes; + + if (selected.Length == 1) + { + return selected[0]; + } + else + { + return -1; + } + } + } + + private int[] SelectedLayerIndexes + { + get + { + LayerElement[] layers = this.Layers; + List layerIndexes = new List(); + + for (int i = 0; i < layers.Length; ++i) + { + if (layers[i].IsSelected) + { + layerIndexes.Add(i); + } + } + + return layerIndexes.ToArray(); + } + } + + public void ClearLayerSelection() + { + LayerElement[] layers = this.Layers; + + for (int i = 0; i < layers.Length; ++i) + { + layers[i].IsSelected = false; + } + } + + public new BorderStyle BorderStyle + { + get + { + return layerControlPanel.BorderStyle; + } + + set + { + layerControlPanel.BorderStyle = value; + } + } + + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + public LayerControl() + { + this.elementHeight = 6 + SystemLayer.UI.ScaleWidth(LayerElement.ThumbSizePreScaling); + + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + + elementClickDelegate = new EventHandler(ElementClickHandler); + elementDoubleClickDelegate = new EventHandler(ElementDoubleClickHandler); + documentChangedDelegate = new EventHandler(DocumentChangedHandler); + documentChangingDelegate = DocumentChangingHandler; + layerInsertedDelegate = new IndexEventHandler(LayerInsertedHandler); + layerRemovedDelegate = new IndexEventHandler(LayerRemovedHandler); + layerChangedDelegate = new EventHandler(LayerChangedHandler); + keyUpDelegate = new KeyEventHandler(KeyUpHandler); + + this.thumbnailManager = new ThumbnailManager(this); + this.thumbnailSize = SystemLayer.UI.ScaleWidth(LayerElement.ThumbSizePreScaling); + layerControls = new List(); + } + + private void SetupNewDocument(Document newDocument) + { + //this.thumbnailManager.ClearQueue(); + + // Subscribe to the eevents + this.document = newDocument; + this.document.Layers.Inserted += layerInsertedDelegate; + this.document.Layers.RemovedAt += layerRemovedDelegate; + + SystemLayer.UI.SuspendControlPainting(this.layerControlPanel); + + for (int i = 0; i < this.document.Layers.Count; ++i) + { + this.LayerInsertedHandler(this, new IndexEventArgs(i)); + } + + if (this.appWorkspace != null) + { + foreach (LayerElement lec in layerControls) + { + if (lec.Layer == appWorkspace.ActiveDocumentWorkspace.ActiveLayer) + { + lec.IsSelected = true; + } + else + { + lec.IsSelected = false; + } + } + } + + SystemLayer.UI.ResumeControlPainting(this.layerControlPanel); + this.layerControlPanel.Invalidate(true); + + OnActiveLayerChanged(ActiveLayer); + } + + private void TearDownOldDocument() + { + SuspendLayout(); + + foreach (LayerElement lec in this.layerControls) + { + lec.Click -= elementClickDelegate; + lec.DoubleClick -= elementDoubleClickDelegate; + lec.KeyUp -= keyUpDelegate; + lec.Layer = null; + layerControlPanel.Controls.Remove(lec); + lec.Dispose(); + } + + ResumeLayout(true); + + this.layerControls.Clear(); + + //this.thumbnailManager.ClearQueue(); + + // Unsubscribe to the Events + if (this.document != null) + { + this.document.Layers.Inserted -= layerInsertedDelegate; + this.document.Layers.RemovedAt -= layerRemovedDelegate; + this.document = null; + } + } + + private void DocumentChangingHandler(object sender, EventArgs e) + { + TearDownOldDocument(); + } + + private void DocumentChangedHandler(object sender, EventArgs e) + { + SetupNewDocument(appWorkspace.ActiveDocumentWorkspace.Document); + } + + private void LayerRemovedHandler(object sender, IndexEventArgs e) + { + LayerElement lec = layerControls[e.Index]; + this.thumbnailManager.RemoveFromQueue(lec.Layer); + lec.Click -= this.elementClickDelegate; + lec.DoubleClick -= this.elementDoubleClickDelegate; + lec.KeyUp -= keyUpDelegate; + lec.Layer = null; + layerControls.Remove(lec); + layerControlPanel.Controls.Remove(lec); + lec.Dispose(); + PerformLayout(); + } + + private void InitializeLayerElement(LayerElement lec, Layer l) + { + lec.Height = elementHeight; + lec.Layer = l; + lec.Click += elementClickDelegate; + lec.DoubleClick += elementDoubleClickDelegate; + lec.KeyUp += keyUpDelegate; + lec.IsSelected = false; + } + + private void SetActive(LayerElement lec) + { + SetActive(lec.Layer); + } + + private void SetActive(Layer layer) + { + foreach (LayerElement lec in layerControls) + { + bool active = (lec.Layer == layer); + lec.IsSelected = active; + + if (active) + { + OnActiveLayerChanged(lec.Layer); + layerControlPanel.ScrollControlIntoView(lec); + lec.Select(); + Update(); + } + } + } + + private void LayerInsertedHandler(object sender, IndexEventArgs e) + { + this.SuspendLayout(); + this.layerControlPanel.SuspendLayout(); + Layer layer = (Layer)this.document.Layers[e.Index]; + LayerElement lec = new LayerElement(); + lec.ThumbnailManager = this.thumbnailManager; + lec.ThumbnailSize = this.thumbnailSize; + InitializeLayerElement(lec, layer); + layerControls.Insert(e.Index, lec); + layerControlPanel.Controls.Add(lec); + layerControlPanel.ScrollControlIntoView(lec); + lec.Select(); + SetActive(lec); + lec.RefreshPreview(); + this.layerControlPanel.ResumeLayout(false); + this.ResumeLayout(false); + this.layerControlPanel.PerformLayout(); + PerformLayout(); + + Refresh(); + } + + public void RefreshPreviews() + { + for (int i = 0; i < this.layerControls.Count; ++i) + { + this.layerControls[i].RefreshPreview(); + } + } + + public event EventHandler RelinquishFocus; + protected void OnRelinquishFocus() + { + if (RelinquishFocus != null) + { + RelinquishFocus(this, EventArgs.Empty); + } + } + + protected override void OnClick(EventArgs e) + { + OnRelinquishFocus(); + base.OnClick(e); + } + + /// + /// This event is raised whenever the user clicks on a layer within the + /// LayerControl to activate it. + /// + public event EventHandler> ClickedOnLayer; + private void OnClickedOnLayer(Layer layer) + { + if (ClickedOnLayer != null) + { + ClickedOnLayer(this, new EventArgs(layer)); + } + } + + /// + /// This event is raised whenever the selected layer is changed. Note that + /// this can occur without user intervention, which distinguishes this event + /// from ClickedOnLayer. + /// + public event EventHandler> ActiveLayerChanged; + private void OnActiveLayerChanged(Layer layer) + { + if (ActiveLayerChanged != null) + { + ActiveLayerChanged(this, new EventArgs(layer)); + } + } + + public event EventHandler> DoubleClickedOnLayer; + private void OnDoubleClickedOnLayer(Layer layer) + { + if (DoubleClickedOnLayer != null) + { + DoubleClickedOnLayer(this, new EventArgs(layer)); + } + } + + private void ElementClickHandler(object sender, EventArgs e) + { + LayerElement lec = (LayerElement)sender; + + if (Control.ModifierKeys == Keys.Control) + { + lec.IsSelected = !lec.IsSelected; + } + else + { + ClearLayerSelection(); + lec.IsSelected = true; + } + + SetActive(lec); + OnClickedOnLayer(lec.Layer); + } + + private void ElementDoubleClickHandler(object sender, EventArgs e) + { + OnDoubleClickedOnLayer(((LayerElement)sender).Layer); + } + + private void LayerChangedHandler(object sender, EventArgs e) + { + SetActive(appWorkspace.ActiveDocumentWorkspace.ActiveLayer); + } + + public void SuspendLayerPreviewUpdates() + { + foreach (LayerElement element in this.layerControls) + { + element.SuspendPreviewUpdates(); + } + } + + public void ResumeLayerPreviewUpdates() + { + foreach (LayerElement element in this.layerControls) + { + element.ResumePreviewUpdates(); + } + } + + private void KeyUpHandler(object sender, KeyEventArgs e) + { + this.OnKeyUp(e); + } + + [Browsable(false)] + public AppWorkspace AppWorkspace + { + get + { + return this.appWorkspace; + } + + set + { + if (this.appWorkspace != value) + { + if (this.appWorkspace != null) + { + TearDownOldDocument(); + + this.appWorkspace.ActiveDocumentWorkspaceChanging -= Workspace_ActiveDocumentWorkspaceChanging; + this.appWorkspace.ActiveDocumentWorkspaceChanged -= Workspace_ActiveDocumentWorkspaceChanged; + } + + this.appWorkspace = value; + + if (this.appWorkspace != null) + { + this.appWorkspace.ActiveDocumentWorkspaceChanging += Workspace_ActiveDocumentWorkspaceChanging; + this.appWorkspace.ActiveDocumentWorkspaceChanged += Workspace_ActiveDocumentWorkspaceChanged; + + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + SetupNewDocument(this.appWorkspace.ActiveDocumentWorkspace.Document); + } + } + } + } + } + + private void Workspace_ActiveDocumentWorkspaceChanging(object sender, EventArgs e) + { + TearDownOldDocument(); + + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + this.appWorkspace.ActiveDocumentWorkspace.DocumentChanging -= documentChangingDelegate; + this.appWorkspace.ActiveDocumentWorkspace.DocumentChanged -= documentChangedDelegate; + this.appWorkspace.ActiveDocumentWorkspace.ActiveLayerChanged -= layerChangedDelegate; + } + } + + private void Workspace_ActiveDocumentWorkspaceChanged(object sender, EventArgs e) + { + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + appWorkspace.ActiveDocumentWorkspace.DocumentChanging += documentChangingDelegate; + appWorkspace.ActiveDocumentWorkspace.DocumentChanged += documentChangedDelegate; + appWorkspace.ActiveDocumentWorkspace.ActiveLayerChanged += layerChangedDelegate; + + if (appWorkspace.ActiveDocumentWorkspace.Document != null) + { + SetupNewDocument(appWorkspace.ActiveDocumentWorkspace.Document); + } + } + } + + [Browsable(false)] + public Document Document + { + get + { + return this.document; + } + + set + { + if (this.appWorkspace != null) + { + throw new InvalidOperationException("Workspace property is already set"); + } + + if (this.document != null) + { + TearDownOldDocument(); + } + + if (value != null) + { + SetupNewDocument(value); + } + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + + if (this.thumbnailManager != null) + { + this.thumbnailManager.Dispose(); + this.thumbnailManager = null; + } + } + + base.Dispose(disposing); + } + + private void LayerControlPanel_Click(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.layerControlPanel = new PanelWithLayout(); + this.SuspendLayout(); + // + // layerControlPanel + // + this.layerControlPanel.AutoScroll = true; + this.layerControlPanel.Dock = System.Windows.Forms.DockStyle.Fill; + this.layerControlPanel.Location = new System.Drawing.Point(0, 0); + this.layerControlPanel.Name = "layerControlPanel"; + this.layerControlPanel.ParentLayerControl = this; + this.layerControlPanel.Size = new System.Drawing.Size(150, 150); + this.layerControlPanel.TabIndex = 2; + this.layerControlPanel.Click += new EventHandler(LayerControlPanel_Click); + // + // LayerControl + // + this.Controls.Add(this.layerControlPanel); + this.Name = "LayerControl"; + this.ResumeLayout(false); + + } + + #endregion + } +} diff --git a/src/LayerControl.resx b/src/LayerControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/LayerElement.cs b/src/LayerElement.cs new file mode 100644 index 0000000..75ce645 --- /dev/null +++ b/src/LayerElement.cs @@ -0,0 +1,384 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class LayerElement + : UserControl + { + public static int ThumbSizePreScaling = 40; + + private Layer layer; + private bool isSelected; + private PropertyEventHandler layerPropertyChangedDelegate; + private System.Windows.Forms.Label layerDescription; + private System.Windows.Forms.PictureBox icon; + private System.Windows.Forms.CheckBox layerVisible; + private ThumbnailManager thumbnailManager; + private int thumbnailSize = 16; + private int suspendPreviewUpdates = 0; + + public ThumbnailManager ThumbnailManager + { + get + { + return this.thumbnailManager; + } + + set + { + this.thumbnailManager = value; + } + } + + public int ThumbnailSize + { + get + { + return this.thumbnailSize; + } + + set + { + if (this.thumbnailSize != value) + { + this.thumbnailSize = value; + RefreshPreview(); + } + } + } + + public CheckBox LayerVisible + { + get + { + return this.layerVisible; + } + } + + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + public bool IsSelected + { + get + { + return this.isSelected; + } + set + { + this.isSelected = value; + + if (this.isSelected) + { + this.layerDescription.BackColor = SystemColors.Highlight; + this.layerDescription.ForeColor = SystemColors.HighlightText; + this.layerVisible.BackColor = this.layerDescription.BackColor; + this.icon.BackColor = SystemColors.Highlight; + } + else // !selected + { + this.layerDescription.ForeColor = SystemColors.WindowText; + this.layerDescription.BackColor = SystemColors.Window; + this.layerVisible.BackColor = this.layerDescription.BackColor; + this.icon.BackColor = SystemColors.Window; + } + + Update(); + } + } + + public Image Image + { + get + { + return this.icon.Image; + } + + set + { + if (this.icon.Image != null) + { + this.icon.Image.Dispose(); + this.icon.Image = null; + } + + this.icon.Image = value; + Invalidate(true); + Update(); + } + } + + public Layer Layer + { + get + { + return this.layer; + } + + set + { + if (object.ReferenceEquals(this.layer, value)) + { + return; + } + + if (this.layer != null) + { + this.layer.PropertyChanged -= this.layerPropertyChangedDelegate; + this.layer.Invalidated -= new InvalidateEventHandler(Layer_Invalidated); + } + + this.layer = value; + + if (this.layer != null) + { + this.layer.PropertyChanged += this.layerPropertyChangedDelegate; + this.layer.Invalidated += new InvalidateEventHandler(Layer_Invalidated); + this.layerPropertyChangedDelegate(layer, new PropertyEventArgs("")); // sync up + + // Add italics if it's the background layer + if (this.layer.IsBackground) + { + this.layerDescription.Font = new Font(this.layerDescription.Font.FontFamily, this.layerDescription.Font.Size, + this.layerDescription.Font.Style | FontStyle.Italic); + } + + RefreshPreview(); + } + + Update(); + } + } + + public LayerElement() + { + // This call is required by the Windows.Forms Form Designer. + this.SuspendLayout(); + InitializeComponent(); + InitializeComponent2(); + this.ResumeLayout(false); + this.IsSelected = false; + + layerPropertyChangedDelegate = new PropertyEventHandler(LayerPropertyChangedHandler); + + this.TabStop = false; + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + this.Layer = null; + + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + private void LayerPropertyChangedHandler(object sender, PropertyEventArgs e) + { + this.layerDescription.Text = layer.Name; + this.layerVisible.Checked = layer.Visible; + } + + private void InitializeComponent2() + { + this.Size = new System.Drawing.Size(200, SystemLayer.UI.ScaleWidth(LayerElement.ThumbSizePreScaling)); + this.icon.Size = new System.Drawing.Size(6 + this.Height, this.Height); + this.layerDescription.Location = new System.Drawing.Point(this.icon.Right, 0); + this.layerVisible.Size = new System.Drawing.Size(16, this.Height); + } + + #region Component Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.layerDescription = new System.Windows.Forms.Label(); + this.icon = new System.Windows.Forms.PictureBox(); + this.layerVisible = new System.Windows.Forms.CheckBox(); + this.SuspendLayout(); + // + // layerDescription + // + this.layerDescription.BackColor = SystemColors.Window; + this.layerDescription.Dock = System.Windows.Forms.DockStyle.Fill; + this.layerDescription.Name = "layerDescription"; + this.layerDescription.Size = new System.Drawing.Size(150, 50); + this.layerDescription.TabIndex = 9; + this.layerDescription.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.layerDescription.Click += new System.EventHandler(this.Control_Click); + this.layerDescription.DoubleClick += new System.EventHandler(this.Control_DoubleClick); + // + // icon + // + this.icon.BackColor = System.Drawing.SystemColors.Control; + this.icon.Dock = System.Windows.Forms.DockStyle.Left; + this.icon.Location = new System.Drawing.Point(0, 0); + this.icon.Name = "icon"; + this.icon.TabStop = false; + this.icon.Click += new System.EventHandler(this.Control_Click); + this.icon.DoubleClick += new System.EventHandler(this.Control_DoubleClick); + // + // layerVisible + // + this.layerVisible.BackColor = SystemColors.Window; + this.layerVisible.Checked = true; + this.layerVisible.CheckState = System.Windows.Forms.CheckState.Checked; + this.layerVisible.Dock = System.Windows.Forms.DockStyle.Right; + this.layerVisible.FlatStyle = System.Windows.Forms.FlatStyle.Standard; + this.layerVisible.Location = new System.Drawing.Point(184, 0); + this.layerVisible.Name = "layerVisible"; + this.layerVisible.TabIndex = 7; + this.layerVisible.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.LayerVisible_KeyPress); + this.layerVisible.CheckStateChanged += new System.EventHandler(this.LayerVisible_CheckStateChanged); + this.layerVisible.KeyUp += new System.Windows.Forms.KeyEventHandler(this.LayerVisible_KeyUp); + // + // LayerElement + // + this.Controls.Add(this.layerDescription); + this.Controls.Add(this.icon); + this.Controls.Add(this.layerVisible); + this.Name = "LayerElement"; + this.ResumeLayout(false); + + } + #endregion + + private void Control_Click(object sender, System.EventArgs e) + { + OnClick(e); + } + + private void Control_DoubleClick(object sender, System.EventArgs e) + { + OnDoubleClick(e); + } + + private void LayerVisible_CheckStateChanged(object sender, System.EventArgs e) + { + this.layer.Visible = this.layerVisible.Checked; + Update(); + } + + private void LayerVisible_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) + { + this.OnKeyPress(e); + } + + private void LayerVisible_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) + { + this.OnKeyUp(e); + } + + private void Layer_Invalidated(object sender, InvalidateEventArgs e) + { + RefreshPreview(); + } + + public void SuspendPreviewUpdates() + { + ++suspendPreviewUpdates; + } + + public void ResumePreviewUpdates() + { + --suspendPreviewUpdates; + } + + protected override void OnHandleCreated(EventArgs e) + { + RefreshPreview(); + base.OnHandleCreated(e); + } + + public void RefreshPreview() + { + if (this.suspendPreviewUpdates > 0) + { + return; + } + + if (!this.IsHandleCreated) + { + return; + } + + this.thumbnailManager.QueueThumbnailUpdate(this.layer, this.thumbnailSize, OnThumbnailRendered); + } + + private void OnThumbnailRendered(object sender, EventArgs> e) + { + if (!IsDisposed) + { + Bitmap thumbBitmap = e.Data.Second.CreateAliasedBitmap(); + Bitmap bitmap = new Bitmap(this.icon.Width, this.icon.Height, PixelFormat.Format32bppArgb); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CompositingMode = CompositingMode.SourceCopy; + g.Clear(Color.Transparent); + + Rectangle thumbRect = new Rectangle( + (bitmap.Width - thumbBitmap.Width) / 2, + (bitmap.Height - thumbBitmap.Height) / 2, + thumbBitmap.Width, + thumbBitmap.Height); + + g.DrawImage( + thumbBitmap, + thumbRect, + new Rectangle(new Point(0, 0), thumbBitmap.Size), + GraphicsUnit.Pixel); + + Rectangle outlineRect = thumbRect; + --outlineRect.X; + --outlineRect.Y; + ++outlineRect.Width; + ++outlineRect.Height; + g.DrawRectangle(Pens.Black, outlineRect); + + g.CompositingMode = CompositingMode.SourceOver; + + Rectangle dropShadowRect = outlineRect; + dropShadowRect.Inflate(1, 1); + ++dropShadowRect.Width; + ++dropShadowRect.Height; + Utility.DrawDropShadow1px(g, dropShadowRect); + } + + thumbBitmap.Dispose(); + this.Image = bitmap; + } + } + } +} diff --git a/src/LayerElement.resx b/src/LayerElement.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/LayerForm.cs b/src/LayerForm.cs new file mode 100644 index 0000000..0b433ac --- /dev/null +++ b/src/LayerForm.cs @@ -0,0 +1,523 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class LayerForm + : FloatingToolForm + { + private PaintDotNet.LayerControl layerControl; + private System.Windows.Forms.ImageList imageList; + private PaintDotNet.SystemLayer.ToolStripEx toolStrip; + private ToolStripButton addNewLayerButton; + private ToolStripButton deleteLayerButton; + private ToolStripButton duplicateLayerButton; + private ToolStripButton mergeLayerDownButton; + private ToolStripButton moveLayerUpButton; + private ToolStripButton moveLayerDownButton; + private ToolStripButton propertiesButton; + private System.ComponentModel.IContainer components; + + public LayerControl LayerControl + { + get + { + return layerControl; + } + } + + protected override void OnVisibleChanged(EventArgs e) + { + if (this.Visible) + { + foreach (LayerElement le in this.layerControl.Layers) + { + le.RefreshPreview(); + } + } + + base.OnVisibleChanged (e); + } + + public LayerForm() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + imageList.TransparentColor = Utility.TransparentKey; + + toolStrip.ImageList = this.imageList; + + int addNewLayerIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuLayersAddNewLayerIcon.png").Reference, imageList.TransparentColor); + int deleteLayerIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuLayersDeleteLayerIcon.png").Reference, imageList.TransparentColor); + int moveLayerUpIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuLayersMoveLayerUpIcon.png").Reference, imageList.TransparentColor); + int moveLayerDownIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuLayersMoveLayerDownIcon.png").Reference, imageList.TransparentColor); + int duplicateLayerIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuEditCopyIcon.png").Reference, imageList.TransparentColor); + int mergeLayerDownIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuLayersMergeLayerDownIcon.png").Reference, imageList.TransparentColor); + int propertiesIndex = imageList.Images.Add(PdnResources.GetImageResource("Icons.MenuLayersLayerPropertiesIcon.png").Reference, imageList.TransparentColor); + + addNewLayerButton.ImageIndex = addNewLayerIndex; + deleteLayerButton.ImageIndex = deleteLayerIndex; + moveLayerUpButton.ImageIndex = moveLayerUpIndex; + moveLayerDownButton.ImageIndex = moveLayerDownIndex; + duplicateLayerButton.ImageIndex = duplicateLayerIndex; + mergeLayerDownButton.ImageIndex = mergeLayerDownIndex; + propertiesButton.ImageIndex = propertiesIndex; + + layerControl.KeyUp += new KeyEventHandler(LayerControl_KeyUp); + + this.Text = PdnResources.GetString("LayerForm.Text"); + this.addNewLayerButton.ToolTipText = PdnResources.GetString("LayerForm.AddNewLayerButton.ToolTipText"); + this.deleteLayerButton.ToolTipText = PdnResources.GetString("LayerForm.DeleteLayerButton.ToolTipText"); + this.duplicateLayerButton.ToolTipText = PdnResources.GetString("LayerForm.DuplicateLayerButton.ToolTipText"); + this.mergeLayerDownButton.ToolTipText = PdnResources.GetString("LayerForm.MergeLayerDownButton.ToolTipText"); + this.moveLayerUpButton.ToolTipText = PdnResources.GetString("LayerForm.MoveLayerUpButton.ToolTipText"); + this.moveLayerDownButton.ToolTipText = PdnResources.GetString("LayerForm.MoveLayerDownButton.ToolTipText"); + this.propertiesButton.ToolTipText = PdnResources.GetString("LayerForm.PropertiesButton.ToolTipText"); + + this.MinimumSize = this.Size; + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + base.OnLayout(levent); + + if (layerControl != null) + { + layerControl.Size = new Size(ClientRectangle.Width, ClientRectangle.Height - + (this.toolStrip.Height + (ClientRectangle.Height - ClientRectangle.Bottom))); + } + } + + public event EventHandler NewLayerButtonClick; + private void OnNewLayerButtonClick() + { + if (NewLayerButtonClick != null) + { + NewLayerButtonClick(this, EventArgs.Empty); + } + } + + public event EventHandler DeleteLayerButtonClick; + private void OnDeleteLayerButtonClick() + { + if (DeleteLayerButtonClick != null) + { + DeleteLayerButtonClick(this, EventArgs.Empty); + } + } + + public event EventHandler DuplicateLayerButtonClick; + private void OnDuplicateLayerButtonClick() + { + if (DuplicateLayerButtonClick != null) + { + DuplicateLayerButtonClick(this, EventArgs.Empty); + } + } + + public event EventHandler MergeLayerDownClick; + private void OnMergeLayerDownButtonClick() + { + if (MergeLayerDownClick != null) + { + MergeLayerDownClick(this, EventArgs.Empty); + } + } + + public event EventHandler MoveLayerUpButtonClick; + private void OnMoveLayerUpButtonClick() + { + if (MoveLayerUpButtonClick != null) + { + MoveLayerUpButtonClick(this, EventArgs.Empty); + } + } + + public event EventHandler MoveLayerDownButtonClick; + private void OnMoveLayerDownButtonClick() + { + if (MoveLayerDownButtonClick != null) + { + MoveLayerDownButtonClick(this, EventArgs.Empty); + } + } + + public event EventHandler PropertiesButtonClick; + private void OnPropertiesButtonClick() + { + if (PropertiesButtonClick != null) + { + PropertiesButtonClick(this, EventArgs.Empty); + } + } + + public void PerformNewLayerClick() + { + this.OnNewLayerButtonClick(); + } + + public void PerformDeleteLayerClick() + { + this.OnDeleteLayerButtonClick(); + } + + public void PerformDuplicateLayerClick() + { + this.OnDuplicateLayerButtonClick(); + } + + public void PerformMoveLayerUpClick() + { + this.OnMoveLayerUpButtonClick(); + } + + public void PerformMoveLayerDownClick() + { + this.OnMoveLayerDownButtonClick(); + } + + public void PerformPropertiesClick() + { + this.OnPropertiesButtonClick(); + } + + private void NewLayerButton_Click(object sender, System.EventArgs e) + { + OnNewLayerButtonClick(); + } + + private void DeleteLayerButton_Click(object sender, System.EventArgs e) + { + OnDeleteLayerButtonClick(); + } + + private void DuplicateLayerButton_Click(object sender, System.EventArgs e) + { + OnDuplicateLayerButtonClick(); + } + + private void MoveUpButton_Click(object sender, System.EventArgs e) + { + OnMoveLayerUpButtonClick(); + } + + private void MoveDownButton_Click(object sender, System.EventArgs e) + { + OnMoveLayerDownButtonClick(); + } + + private void PropertiesButton_Click(object sender, System.EventArgs e) + { + OnPropertiesButtonClick(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.layerControl = new LayerControl(); + this.imageList = new ImageList(this.components); + this.toolStrip = new SystemLayer.ToolStripEx(); + this.addNewLayerButton = new ToolStripButton(); + this.deleteLayerButton = new ToolStripButton(); + this.duplicateLayerButton = new ToolStripButton(); + this.mergeLayerDownButton = new ToolStripButton(); + this.moveLayerUpButton = new ToolStripButton(); + this.moveLayerDownButton = new ToolStripButton(); + this.propertiesButton = new ToolStripButton(); + this.toolStrip.SuspendLayout(); + this.SuspendLayout(); + // + // layerControl + // + this.layerControl.Dock = System.Windows.Forms.DockStyle.Fill; + this.layerControl.Document = null; + this.layerControl.Location = new System.Drawing.Point(0, 0); + this.layerControl.Name = "layerControl"; + this.layerControl.Size = new System.Drawing.Size(160, 158); + this.layerControl.TabIndex = 5; + this.layerControl.AppWorkspace = null; + this.layerControl.ActiveLayerChanged += this.LayerControl_ClickOnLayer; + this.layerControl.ClickedOnLayer += this.LayerControl_ClickOnLayer; + this.layerControl.DoubleClickedOnLayer += this.LayerControl_DoubleClickedOnLayer; + this.layerControl.RelinquishFocus += new EventHandler(LayerControl_RelinquishFocus); + // + // imageList + // + this.imageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit; + this.imageList.ImageSize = new System.Drawing.Size(16, 16); + this.imageList.TransparentColor = System.Drawing.Color.Transparent; + // + // toolStrip + // + this.toolStrip.Dock = System.Windows.Forms.DockStyle.Bottom; + this.toolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.addNewLayerButton, + this.deleteLayerButton, + this.duplicateLayerButton, + this.mergeLayerDownButton, + this.moveLayerUpButton, + this.moveLayerDownButton, + this.propertiesButton + }); + this.toolStrip.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow; + this.toolStrip.Location = new System.Drawing.Point(0, 132); + this.toolStrip.Name = "toolStrip"; + this.toolStrip.Size = new System.Drawing.Size(160, 26); + this.toolStrip.TabIndex = 7; + this.toolStrip.TabStop = true; + this.toolStrip.RelinquishFocus += new EventHandler(ToolStrip_RelinquishFocus); + // + // addNewLayerButton + // + this.addNewLayerButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.addNewLayerButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.addNewLayerButton.Name = "addNewLayerButton"; + this.addNewLayerButton.Size = new System.Drawing.Size(23, 4); + this.addNewLayerButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // deleteLayerButton + // + this.deleteLayerButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.deleteLayerButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.deleteLayerButton.Name = "deleteLayerButton"; + this.deleteLayerButton.Size = new System.Drawing.Size(23, 4); + this.deleteLayerButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // duplicateLayerButton + // + this.duplicateLayerButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.duplicateLayerButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.duplicateLayerButton.Name = "duplicateLayerButton"; + this.duplicateLayerButton.Size = new System.Drawing.Size(23, 4); + this.duplicateLayerButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // mergeLayerDownButton + // + this.mergeLayerDownButton.DisplayStyle = ToolStripItemDisplayStyle.Image; + this.mergeLayerDownButton.Name = "mergeLayerDownButton"; + this.mergeLayerDownButton.Click += new EventHandler(OnToolStripButtonClick); + // + // moveLayerUpButton + // + this.moveLayerUpButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.moveLayerUpButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.moveLayerUpButton.Name = "moveLayerUpButton"; + this.moveLayerUpButton.Size = new System.Drawing.Size(23, 4); + this.moveLayerUpButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // moveLayerDownButton + // + this.moveLayerDownButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.moveLayerDownButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.moveLayerDownButton.Name = "moveLayerDownButton"; + this.moveLayerDownButton.Size = new System.Drawing.Size(23, 4); + this.moveLayerDownButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // propertiesButton + // + this.propertiesButton.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.propertiesButton.ImageTransparentColor = System.Drawing.Color.Magenta; + this.propertiesButton.Name = "propertiesButton"; + this.propertiesButton.Size = new System.Drawing.Size(23, 4); + this.propertiesButton.Click += new System.EventHandler(this.OnToolStripButtonClick); + // + // LayerForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.AutoValidate = System.Windows.Forms.AutoValidate.EnablePreventFocusChange; + this.ClientSize = new System.Drawing.Size(165, 158); + this.Controls.Add(this.toolStrip); + this.Controls.Add(this.layerControl); + this.Name = "LayersForm"; + this.Controls.SetChildIndex(this.layerControl, 0); + this.Controls.SetChildIndex(this.toolStrip, 0); + this.toolStrip.ResumeLayout(false); + this.toolStrip.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private void LayerControl_RelinquishFocus(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + private void ToolStrip_RelinquishFocus(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + private void DetermineButtonEnableStates() + { + DetermineButtonEnableStates(this.layerControl.ActiveLayerIndex); + } + + private void DetermineButtonEnableStates(int index) + { + if (layerControl.AppWorkspace == null) + { + return; + } + + // Find a reason to disable the Move Layer Down button + if (layerControl.AppWorkspace.ActiveDocumentWorkspace == null || + layerControl.AppWorkspace.ActiveDocumentWorkspace.Document == null || + index == 0) + { + this.moveLayerDownButton.Enabled = false; + } + else + { + this.moveLayerDownButton.Enabled = true; + } + + // Find a reason to disable the Move Layer Up button + if (layerControl.AppWorkspace.ActiveDocumentWorkspace == null || + layerControl.AppWorkspace.ActiveDocumentWorkspace.Document == null || + index == (layerControl.AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count - 1)) + { + this.moveLayerUpButton.Enabled = false; + } + else + { + this.moveLayerUpButton.Enabled = true; + } + + // Find reasons to disable the Delete Layer button + if (layerControl.AppWorkspace.ActiveDocumentWorkspace == null || + layerControl.AppWorkspace.ActiveDocumentWorkspace.Document == null || + layerControl.AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count <= 1) + { + this.deleteLayerButton.Enabled = false; + } + else + { + this.deleteLayerButton.Enabled = true; + } + + // Find reasons to disable the Merge Layer Down button + if (layerControl.AppWorkspace.ActiveDocumentWorkspace == null || + layerControl.AppWorkspace.ActiveDocumentWorkspace.Document == null || + layerControl.AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex == 0 || + layerControl.AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count < 2) + { + this.mergeLayerDownButton.Enabled = false; + } + else + { + this.mergeLayerDownButton.Enabled = true; + } + } + + private void LayerControl_ClickOnLayer(object sender, EventArgs ce) + { + // TODO: whoa there, enough nesting? + int index = layerControl.AppWorkspace.ActiveDocumentWorkspace.Document.Layers.IndexOf(ce.Data); + DetermineButtonEnableStates(index); + } + + private void LayerControl_DoubleClickedOnLayer(object sender, EventArgs ce) + { + OnPropertiesButtonClick(); + this.OnRelinquishFocus(); + } + + private void LayerControl_KeyUp(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Delete && e.Modifiers == Keys.None) + { + this.OnDeleteLayerButtonClick(); + e.Handled = true; + return; + } + } + + private void OnToolStripButtonClick(object sender, EventArgs e) + { + SystemLayer.UI.SuspendControlPainting(this.layerControl); + + if (sender == addNewLayerButton) + { + OnNewLayerButtonClick(); + } + else if (sender == deleteLayerButton) + { + OnDeleteLayerButtonClick(); + } + else if (sender == duplicateLayerButton) + { + OnDuplicateLayerButtonClick(); + } + else if (sender == mergeLayerDownButton) + { + OnMergeLayerDownButtonClick(); + } + else if (sender == moveLayerUpButton) + { + OnMoveLayerUpButtonClick(); + } + else if (sender == moveLayerDownButton) + { + OnMoveLayerDownButtonClick(); + } + + SystemLayer.UI.ResumeControlPainting(this.layerControl); + this.layerControl.Invalidate(true); + + if (sender == propertiesButton) + { + OnPropertiesButtonClick(); + } + + DetermineButtonEnableStates(); + OnRelinquishFocus(); + } + } +} diff --git a/src/LayerForm.resx b/src/LayerForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/License.txt b/src/License.txt new file mode 100644 index 0000000..b6858f4 --- /dev/null +++ b/src/License.txt @@ -0,0 +1 @@ +See: Resources/Files/License.txt \ No newline at end of file diff --git a/src/LineCap2.cs b/src/LineCap2.cs new file mode 100644 index 0000000..79f6442 --- /dev/null +++ b/src/LineCap2.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum LineCap2 + { + Flat = 0, + Arrow = 1, + ArrowFilled = 2, + Rounded = 3 + } +} diff --git a/src/MainForm.cs b/src/MainForm.cs new file mode 100644 index 0000000..2d2e3fb --- /dev/null +++ b/src/MainForm.cs @@ -0,0 +1,1334 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.Effects; +using PaintDotNet.Menus; +using PaintDotNet.SystemLayer; +using PaintDotNet.Tools; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Net; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Security; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal sealed class MainForm + : PdnBaseForm + { + private AppWorkspace appWorkspace; + private Button defaultButton; + private FloatingToolForm[] floaters; + private System.Windows.Forms.Timer floaterOpacityTimer; + private System.Windows.Forms.Timer deferredInitializationTimer; + private System.ComponentModel.IContainer components; + private bool killAfterInit = false; + private SplashForm splashForm = null; + private SingleInstanceManager singleInstanceManager = null; + private List queuedInstanceMessages = new List(); + + public SingleInstanceManager SingleInstanceManager + { + get + { + return this.singleInstanceManager; + } + + set + { + if (this.singleInstanceManager != null) + { + this.singleInstanceManager.InstanceMessageReceived -= new EventHandler(SingleInstanceManager_InstanceMessageReceived); + this.singleInstanceManager.SetWindow(null); + } + + this.singleInstanceManager = value; + + if (this.singleInstanceManager != null) + { + this.singleInstanceManager.SetWindow(this); + this.singleInstanceManager.InstanceMessageReceived += new EventHandler(SingleInstanceManager_InstanceMessageReceived); + } + } + } + + private void SingleInstanceManager_InstanceMessageReceived(object sender, EventArgs e) + { + BeginInvoke(new Procedure(ProcessQueuedInstanceMessages), null); + } + + public MainForm() + : this(new string[0]) + { + } + + protected override void WndProc(ref Message m) + { + if (this.singleInstanceManager != null) + { + this.singleInstanceManager.FilterMessage(ref m); + } + + base.WndProc(ref m); + } + + private enum ArgumentAction + { + Open, + OpenUntitled, + Print, + NoOp + } + + private bool SplitMessage(string message, out ArgumentAction action, out string actionParm) + { + if (message.Length == 0) + { + action = ArgumentAction.NoOp; + actionParm = null; + return false; + } + + const string printPrefix = "print:"; + + if (message.IndexOf(printPrefix) == 0) + { + action = ArgumentAction.Print; + actionParm = message.Substring(printPrefix.Length); + return true; + } + + const string untitledPrefix = "untitled:"; + + if (message.IndexOf(untitledPrefix) == 0) + { + action = ArgumentAction.OpenUntitled; + actionParm = message.Substring(untitledPrefix.Length); + return true; + } + + action = ArgumentAction.Open; + actionParm = message; + return true; + } + + private bool ProcessMessage(string message) + { + if (IsDisposed) + { + return false; + } + + ArgumentAction action; + string actionParm; + bool result; + + result = SplitMessage(message, out action, out actionParm); + + if (!result) + { + return true; + } + + switch (action) + { + case ArgumentAction.NoOp: + result = true; + break; + + case ArgumentAction.Open: + Activate(); + + if (IsCurrentModalForm && Enabled) + { + result = this.appWorkspace.OpenFileInNewWorkspace(actionParm); + } + + break; + + case ArgumentAction.OpenUntitled: + Activate(); + + if (!string.IsNullOrEmpty(actionParm) && IsCurrentModalForm && Enabled) + { + result = this.appWorkspace.OpenFileInNewWorkspace(actionParm, false); + + if (result) + { + this.appWorkspace.ActiveDocumentWorkspace.SetDocumentSaveOptions(null, null, null); + this.appWorkspace.ActiveDocumentWorkspace.Document.Dirty = true; + } + } + + break; + + case ArgumentAction.Print: + Activate(); + + if (!string.IsNullOrEmpty(actionParm) && IsCurrentModalForm && Enabled) + { + result = this.appWorkspace.OpenFileInNewWorkspace(actionParm); + + if (result) + { + DocumentWorkspace dw = this.appWorkspace.ActiveDocumentWorkspace; + PrintAction pa = new PrintAction(); + dw.PerformAction(pa); + CloseWorkspaceAction cwa = new CloseWorkspaceAction(dw); + this.appWorkspace.PerformAction(cwa); + + if (this.appWorkspace.DocumentWorkspaces.Length == 0) + { + Startup.CloseApplication(); + } + } + } + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return result; + } + + private void ProcessQueuedInstanceMessages() + { + if (IsDisposed) + { + return; + } + + if (this.splashForm != null) + { + this.splashForm.Close(); + this.splashForm.Dispose(); + this.splashForm = null; + } + + if (IsHandleCreated && + !PdnInfo.IsExpired && + this.singleInstanceManager != null) + { + string[] messages1 = this.singleInstanceManager.GetPendingInstanceMessages(); + string[] messages2 = this.queuedInstanceMessages.ToArray(); + this.queuedInstanceMessages.Clear(); + + string[] messages = new string[messages1.Length + messages2.Length]; + for (int i = 0; i < messages1.Length; ++i) + { + messages[i] = messages1[i]; + } + + for (int i = 0; i < messages2.Length; ++i) + { + messages[i + messages1.Length] = messages2[i]; + } + + foreach (string message in messages) + { + bool result = ProcessMessage(message); + + if (!result) + { + break; + } + } + } + } + + private void Application_Idle(object sender, EventArgs e) + { + if (!this.IsDisposed && + (this.queuedInstanceMessages.Count > 0 || (this.singleInstanceManager != null && this.singleInstanceManager.AreMessagesPending))) + { + ProcessQueuedInstanceMessages(); + } + } + + public MainForm(string[] args) + { + bool canSetCurrentDir = true; + + this.StartPosition = FormStartPosition.WindowsDefaultLocation; + + bool splash = false; + List fileNames = new List(); + + // Parse command line arguments + foreach (string argument in args) + { + if (0 == string.Compare(argument, "/dontForceGC")) + { + Utility.AllowGCFullCollect = false; + } + else if (0 == string.Compare(argument, "/splash", true)) + { + splash = true; + } + else if (0 == string.Compare(argument, "/test", true)) + { + // This lets us use an alternate update manifest on the web server so that + // we can test manifests on a small scale before "deploying" them to everybody + PdnInfo.IsTestMode = true; + } + else if (0 == string.Compare(argument, "/profileStartupTimed", true)) + { + // profileStartupTimed and profileStartupWorkingSet compete, which + // ever is last in the args list wins. + PdnInfo.StartupTest = StartupTestType.Timed; + } + else if (0 == string.Compare(argument, "/profileStartupWorkingSet", true)) + { + // profileStartupTimed and profileStartupWorkingSet compete, which + // ever is last in the args list wins. + PdnInfo.StartupTest = StartupTestType.WorkingSet; + } + else if (argument.Length > 0 && argument[0] != '/') + { + try + { + string fullPath = Path.GetFullPath(argument); + fileNames.Add(fullPath); + } + + catch (Exception) + { + fileNames.Add(argument); + canSetCurrentDir = false; + } + + splash = true; + } + } + + if (canSetCurrentDir) + { + try + { + Environment.CurrentDirectory = PdnInfo.GetApplicationDir(); + } + + catch (Exception ex) + { + Tracing.Ping("Exception while trying to set Environment.CurrentDirectory: " + ex.ToString()); + } + } + + // make splash, if warranted + if (splash) + { + this.splashForm = new SplashForm(); + this.splashForm.TopMost = true; + this.splashForm.Show(); + this.splashForm.Update(); + } + + InitializeComponent(); + + this.Icon = PdnInfo.AppIcon; + + // Does not load window location/state + LoadSettings(); + + foreach (string fileName in fileNames) + { + this.queuedInstanceMessages.Add(fileName); + } + + // no file specified? create a blank image + if (fileNames.Count == 0) + { + MeasurementUnit units = Document.DefaultDpuUnit; + double dpu = Document.GetDefaultDpu(units); + Size newSize = this.appWorkspace.GetNewDocumentSize(); + this.appWorkspace.CreateBlankDocumentInNewWorkspace(newSize, units, dpu, true); + this.appWorkspace.ActiveDocumentWorkspace.IncrementJustPaintWhite(); + this.appWorkspace.ActiveDocumentWorkspace.Document.Dirty = false; + } + + LoadWindowState(); + + deferredInitializationTimer.Enabled = true; + + Application.Idle += new EventHandler(Application_Idle); + } + + protected override void OnShown(EventArgs e) + { + base.OnShown(e); + + if (PdnInfo.IsExpired) + { + foreach (Form form in Application.OpenForms) + { + form.Enabled = false; + } + + TaskButton checkForUpdatesTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuHelpCheckForUpdatesIcon.png").Reference, + PdnResources.GetString("ExpiredTaskDialog.CheckForUpdatesTB.ActionText"), + PdnResources.GetString("ExpiredTaskDialog.CheckForUpdatesTB.ExplanationText")); + + TaskButton goToWebSiteTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuHelpPdnWebsiteIcon.png").Reference, + PdnResources.GetString("ExpiredTaskDialog.GoToWebSiteTB.ActionText"), + PdnResources.GetString("ExpiredTaskDialog.GoToWebSiteTB.ExplanationText")); + + TaskButton doNotCheckForUpdatesTB = new TaskButton( + PdnResources.GetImageResource("Icons.CancelIcon.png").Reference, + PdnResources.GetString("ExpiredTaskDialog.DoNotCheckForUpdatesTB.ActionText"), + PdnResources.GetString("ExpiredTaskDialog.DoNotCheckForUpdatesTB.ExplanationText")); + + TaskButton[] taskButtons = + new TaskButton[] + { + checkForUpdatesTB, + goToWebSiteTB, + doNotCheckForUpdatesTB + }; + + TaskButton clickedTB = TaskDialog.Show( + this, + Icon, + PdnInfo.GetFullAppName(), + PdnResources.GetImageResource("Icons.WarningIcon.png").Reference, + true, + PdnResources.GetString("ExpiredTaskDialog.InfoText"), + taskButtons, + checkForUpdatesTB, + doNotCheckForUpdatesTB, + 450); + + if (clickedTB == checkForUpdatesTB) + { + this.appWorkspace.CheckForUpdates(); + } + else if (clickedTB == goToWebSiteTB) + { + PdnInfo.LaunchWebSite(this, InvariantStrings.ExpiredPage); + } + + Close(); + } + } + + private void LoadWindowState() + { + try + { + FormWindowState fws = (FormWindowState)Enum.Parse(typeof(FormWindowState), + Settings.CurrentUser.GetString(SettingNames.WindowState, WindowState.ToString()), true); + + // if the state was saved as 'minimized' then just ignore whatever was saved + + if (fws != FormWindowState.Minimized) + { + if (fws != FormWindowState.Maximized) + { + Rectangle newBounds = Rectangle.Empty; + + // Load the registry values into a rectangle so that we + // can update the settings all at once, instead of one + // at a time. This will make loading the size an all or + // none operation, with no rollback necessary + newBounds.Width = Settings.CurrentUser.GetInt32(SettingNames.Width, this.Width); + newBounds.Height = Settings.CurrentUser.GetInt32(SettingNames.Height, this.Height); + + int left = Settings.CurrentUser.GetInt32(SettingNames.Left, this.Left); + int top = Settings.CurrentUser.GetInt32(SettingNames.Top, this.Top); + newBounds.Location = new Point(left, top); + + this.Bounds = newBounds; + } + + this.WindowState = fws; + } + } + + catch + { + try + { + Settings.CurrentUser.Delete( + new string[] + { + SettingNames.Width, + SettingNames.Height, + SettingNames.WindowState, + SettingNames.Top, + SettingNames.Left + }); + } + + catch + { + // ignore errors + } + } + } + + private void LoadSettings() + { + try + { + PdnBaseForm.EnableOpacity = Settings.CurrentUser.GetBoolean(SettingNames.TranslucentWindows, true); + } + + catch (Exception ex) + { + Tracing.Ping("Exception in MainForm.LoadSettings:" + ex.ToString()); + + try + { + Settings.CurrentUser.Delete( + new string[] + { + SettingNames.TranslucentWindows + }); + } + + catch + { + } + } + } + + private void SaveSettings() + { + Settings.CurrentUser.SetInt32(SettingNames.Width, this.Width); + Settings.CurrentUser.SetInt32(SettingNames.Height, this.Height); + Settings.CurrentUser.SetInt32(SettingNames.Top, this.Top); + Settings.CurrentUser.SetInt32(SettingNames.Left, this.Left); + Settings.CurrentUser.SetString(SettingNames.WindowState, this.WindowState.ToString()); + + Settings.CurrentUser.SetBoolean(SettingNames.TranslucentWindows, PdnBaseForm.EnableOpacity); + + if (this.WindowState != FormWindowState.Minimized) + { + Settings.CurrentUser.SetBoolean(SettingNames.ToolsFormVisible, this.appWorkspace.Widgets.ToolsForm.Visible); + Settings.CurrentUser.SetBoolean(SettingNames.ColorsFormVisible, this.appWorkspace.Widgets.ColorsForm.Visible); + Settings.CurrentUser.SetBoolean(SettingNames.HistoryFormVisible, this.appWorkspace.Widgets.HistoryForm.Visible); + Settings.CurrentUser.SetBoolean(SettingNames.LayersFormVisible, this.appWorkspace.Widgets.LayerForm.Visible); + } + + SnapManager.Save(Settings.CurrentUser); + this.appWorkspace.SaveSettings(); + } + + + protected override void OnQueryEndSession(CancelEventArgs e) + { + if (IsCurrentModalForm) + { + OnClosing(e); + } + else + { + foreach (Form form in Application.OpenForms) + { + PdnBaseForm asPDF = form as PdnBaseForm; + + if (asPDF != null) + { + asPDF.Flash(); + } + } + + e.Cancel = true; + } + + base.OnQueryEndSession(e); + } + + protected override void OnClosing(CancelEventArgs e) + { + if (!e.Cancel) + { + if (this.appWorkspace != null) + { + CloseAllWorkspacesAction cawa = new CloseAllWorkspacesAction(); + this.appWorkspace.PerformAction(cawa); + e.Cancel = cawa.Cancelled; + } + } + + if (!e.Cancel) + { + SaveSettings(); + + if (this.floaters != null) + { + foreach (Form hideMe in this.floaters) + { + hideMe.Hide(); + } + } + + this.Hide(); + + if (this.queuedInstanceMessages != null) + { + this.queuedInstanceMessages.Clear(); + } + + SingleInstanceManager sim2 = this.singleInstanceManager; + SingleInstanceManager = null; + + if (sim2 != null) + { + sim2.Dispose(); + sim2 = null; + } + } + + base.OnClosing(e); + } + + protected override void OnClosed(EventArgs e) + { + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + appWorkspace.ActiveDocumentWorkspace.SetTool(null); + } + + base.OnClosed(e); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.singleInstanceManager != null) + { + SingleInstanceManager sim2 = this.singleInstanceManager; + SingleInstanceManager = null; + sim2.Dispose(); + sim2 = null; + } + + if (this.floaterOpacityTimer != null) + { + this.floaterOpacityTimer.Tick -= new System.EventHandler(this.FloaterOpacityTimer_Tick); + this.floaterOpacityTimer.Dispose(); + this.floaterOpacityTimer = null; + } + + if (this.components != null) + { + this.components.Dispose(); + this.components = null; + } + } + + try + { + base.Dispose(disposing); + } + + catch (RankException) + { + // System.Windows.Forms.PropertyStore + // Discard error - bug #2746 + } + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.defaultButton = new System.Windows.Forms.Button(); + this.appWorkspace = new PaintDotNet.AppWorkspace(); + this.floaterOpacityTimer = new System.Windows.Forms.Timer(this.components); + this.deferredInitializationTimer = new System.Windows.Forms.Timer(this.components); + this.SuspendLayout(); + // + // appWorkspace + // + this.appWorkspace.Dock = System.Windows.Forms.DockStyle.Fill; + this.appWorkspace.Location = new System.Drawing.Point(0, 0); + this.appWorkspace.Name = "appWorkspace"; + this.appWorkspace.Size = new System.Drawing.Size(752, 648); + this.appWorkspace.TabIndex = 2; + this.appWorkspace.ActiveDocumentWorkspaceChanging += new EventHandler(AppWorkspace_ActiveDocumentWorkspaceChanging); + this.appWorkspace.ActiveDocumentWorkspaceChanged += new EventHandler(AppWorkspace_ActiveDocumentWorkspaceChanged); + // + // floaterOpacityTimer + // + this.floaterOpacityTimer.Enabled = false; + this.floaterOpacityTimer.Interval = 25; + this.floaterOpacityTimer.Tick += new System.EventHandler(this.FloaterOpacityTimer_Tick); + // + // deferredInitializationTimer + // + this.deferredInitializationTimer.Interval = 250; + this.deferredInitializationTimer.Tick += new EventHandler(DeferredInitialization); + // + // defaultButton + // + this.defaultButton.Size = new System.Drawing.Size(1, 1); + this.defaultButton.Text = ""; + this.defaultButton.Location = new Point(-100, -100); + this.defaultButton.TabStop = false; + this.defaultButton.Click += new EventHandler(DefaultButton_Click); + // + // MainForm + // + + try + { + this.AllowDrop = true; + } + + catch (InvalidOperationException) + { + // Discard error. See bug #2605. + } + + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(950, 738); + this.Controls.Add(this.appWorkspace); + this.Controls.Add(this.defaultButton); + this.AcceptButton = this.defaultButton; + this.Name = "MainForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.WindowsDefaultLocation; + this.ForceActiveTitleBar = true; + this.KeyPreview = true; + this.Controls.SetChildIndex(this.appWorkspace, 0); + this.ResumeLayout(false); + this.PerformLayout(); + } + + private void AppWorkspace_ActiveDocumentWorkspaceChanging(object sender, EventArgs e) + { + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + this.appWorkspace.ActiveDocumentWorkspace.ScaleFactorChanged -= DocumentWorkspace_ScaleFactorChanged; + this.appWorkspace.ActiveDocumentWorkspace.DocumentChanged -= DocumentWorkspace_DocumentChanged; + this.appWorkspace.ActiveDocumentWorkspace.SaveOptionsChanged -= DocumentWorkspace_SaveOptionsChanged; + } + } + + private void AppWorkspace_ActiveDocumentWorkspaceChanged(object sender, EventArgs e) + { + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + this.appWorkspace.ActiveDocumentWorkspace.ScaleFactorChanged += DocumentWorkspace_ScaleFactorChanged; + this.appWorkspace.ActiveDocumentWorkspace.DocumentChanged += DocumentWorkspace_DocumentChanged; + this.appWorkspace.ActiveDocumentWorkspace.SaveOptionsChanged += DocumentWorkspace_SaveOptionsChanged; + } + + SetTitleText(); + } + + private void DocumentWorkspace_SaveOptionsChanged(object sender, EventArgs e) + { + SetTitleText(); + } + + private Keys CharToKeys(char c) + { + Keys keys = Keys.None; + c = Char.ToLower(c); + + if (c >= 'a' && c <= 'z') + { + keys = (Keys)((int)Keys.A + (int)c - (int)'a'); + } + + return keys; + } + + private Keys GetMenuCmdKey(string text) + { + Keys keys = Keys.None; + + for (int i = 0; i < text.Length - 1; ++i) + { + if (text[i] == '&') + { + keys = Keys.Alt | CharToKeys(text[i + 1]); + break; + } + } + + return keys; + } + + protected override void OnLoad(EventArgs e) + { + EnsureFormIsOnScreen(); + + if (killAfterInit) + { + Application.Exit(); + } + + this.floaters = new FloatingToolForm[] { + appWorkspace.Widgets.ToolsForm, + appWorkspace.Widgets.ColorsForm, + appWorkspace.Widgets.HistoryForm, + appWorkspace.Widgets.LayerForm + }; + + foreach (FloatingToolForm ftf in floaters) + { + ftf.Closing += this.HideInsteadOfCloseHandler; + } + + PositionFloatingForms(); + + base.OnLoad(e); + + switch (PdnInfo.StartupTest) + { + case StartupTestType.Timed: + Application.DoEvents(); + Application.Exit(); + break; + + case StartupTestType.WorkingSet: + const int waitPeriodForVadumpSnapshot = 20000; + Application.DoEvents(); + Thread.Sleep(waitPeriodForVadumpSnapshot); + Application.Exit(); + break; + } + } + + private void PositionFloatingForms() + { + this.appWorkspace.ResetFloatingForms(); + + try + { + SnapManager.Load(Settings.CurrentUser); + } + + catch + { + this.appWorkspace.ResetFloatingForms(); + } + + foreach (FloatingToolForm ftf in floaters) + { + this.AddOwnedForm(ftf); + } + + if (Settings.CurrentUser.GetBoolean(SettingNames.ToolsFormVisible, true)) + { + this.appWorkspace.Widgets.ToolsForm.Show(); + } + + if (Settings.CurrentUser.GetBoolean(SettingNames.ColorsFormVisible, true)) + { + this.appWorkspace.Widgets.ColorsForm.Show(); + } + + if (Settings.CurrentUser.GetBoolean(SettingNames.HistoryFormVisible, true)) + { + this.appWorkspace.Widgets.HistoryForm.Show(); + } + + if (Settings.CurrentUser.GetBoolean(SettingNames.LayersFormVisible, true)) + { + this.appWorkspace.Widgets.LayerForm.Show(); + } + + // If the floating form is off screen somehow, reset it + // We've been getting a lot of reports where people say their Colors window has disappeared + Screen[] allScreens = Screen.AllScreens; + + foreach (FloatingToolForm ftf in this.floaters) + { + if (!ftf.Visible) + { + continue; + } + + bool reset = false; + + try + { + bool foundAScreen = false; + + foreach (Screen screen in allScreens) + { + Rectangle intersect = Rectangle.Intersect(screen.Bounds, ftf.Bounds); + + if (intersect.Width > 0 && intersect.Height > 0) + { + foundAScreen = true; + break; + } + } + + if (!foundAScreen) + { + reset = true; + } + } + + catch (Exception) + { + reset = true; + } + + if (reset) + { + this.appWorkspace.ResetFloatingForm(ftf); + } + } + + this.floaterOpacityTimer.Enabled = true; + } + + protected override void OnResize(EventArgs e) + { + if (floaterOpacityTimer != null) + { + if (WindowState == FormWindowState.Minimized) + { + if (this.floaterOpacityTimer.Enabled) + { + this.floaterOpacityTimer.Enabled = false; + } + } + else + { + if (!this.floaterOpacityTimer.Enabled) + { + this.floaterOpacityTimer.Enabled = true; + } + + this.FloaterOpacityTimer_Tick(this, EventArgs.Empty); + } + } + + base.OnResize (e); + } + + private void DocumentWorkspace_DocumentChanged(object sender, System.EventArgs e) + { + SetTitleText(); + OnResize(EventArgs.Empty); + } + + private void SetTitleText() + { + if (this.appWorkspace == null) + { + return; + } + + if (this.appWorkspace.ActiveDocumentWorkspace == null) + { + this.Text = PdnInfo.GetAppName(); + } + else + { + string appTitle = PdnInfo.GetAppName(); + string ratio = string.Empty; + string title = string.Empty; + string friendlyName = this.appWorkspace.ActiveDocumentWorkspace.GetFriendlyName(); + string text; + + if (this.WindowState != FormWindowState.Minimized) + { + string format = PdnResources.GetString("MainForm.Title.Format.Normal"); + text = string.Format(format, friendlyName, appWorkspace.ActiveDocumentWorkspace.ScaleFactor, appTitle); + } + else + { + string format = PdnResources.GetString("MainForm.Title.Format.Minimized"); + text = string.Format(format, friendlyName, appTitle); + } + + if (appWorkspace.ActiveDocumentWorkspace.Document != null) + { + title = text; + } + + this.Text = title; + } + } + + // For the menus where we dynamically enable menu items (e.g. Copy only enabled when there's a selection), + // we have to make sure to re-enable all the items when the menu goes way. + // This is important for cases where, for example: Edit menu is opened, "Deselect" is disabled because + // there is no selection. User then clicks on Select All. The menu then goes away. However, since Deselect + // was disabled, the Ctrl+D shortcut will not be honored even though there is a selection. + // So the disabling of menu items should only be temporary for the duration of the menu's visibility. + private void OnMenuDropDownClosed(object sender, System.EventArgs e) + { + ToolStripMenuItem menu = (ToolStripMenuItem)sender; + + foreach (ToolStripItem tsi in menu.DropDownItems) + { + tsi.Enabled = true; + } + } + + private void HideInsteadOfCloseHandler(object sender, CancelEventArgs e) + { + e.Cancel = true; + ((Form)sender).Hide(); + } + + // TODO: refactor into FloatingToolForm class somehow + private void FloaterOpacityTimer_Tick(object sender, System.EventArgs e) + { + if (this.WindowState == FormWindowState.Minimized || + this.floaters == null || + !PdnBaseForm.EnableOpacity || + this.appWorkspace.ActiveDocumentWorkspace == null) + { + return; + } + + // Here's the behavior we want for our floaters: + // 1. If the mouse is within a floaters rectangle, it should transition to fully opaque + // 2. If the mouse is outside the floater's rectangle, it should transition to partially + // opaque + // 3. However, if the floater is outside where the document is visible on screen, it + // should always be fully opaque. + Rectangle screenDocRect; + + try + { + screenDocRect = this.appWorkspace.ActiveDocumentWorkspace.VisibleDocumentBounds; + } + + catch (ObjectDisposedException) + { + return; // do nothing, we are probably in the process of shutting down the app + } + + for (int i = 0; i < floaters.Length; ++i) + { + FloatingToolForm ftf = floaters[i]; + + Rectangle intersect = Rectangle.Intersect(screenDocRect, ftf.Bounds); + double opacity = -1.0; + + try + { + if (intersect.Width == 0 || + intersect.Height == 0 || + (ftf.Bounds.Contains(Control.MousePosition) && + !appWorkspace.ActiveDocumentWorkspace.IsMouseCaptured()) || + Utility.DoesControlHaveMouseCaptured(ftf)) + { + opacity = Math.Min(1.0, ftf.Opacity + 0.125); + } + else + { + opacity = Math.Max(0.75, ftf.Opacity - 0.0625); + } + + if (opacity != ftf.Opacity) + { + ftf.Opacity = opacity; + } + } + + catch (System.ComponentModel.Win32Exception) + { + // We just eat the exception. Chris Strahl was having some problem where opacity was 0.7 + // and we were trying to set it to 0.7 and it said "the parameter is incorrect" + // ... which is stupid. Bad NVIDIA drivers for his GeForce Go? + } + } + } + + protected override void OnDragEnter(DragEventArgs drgevent) + { + if (Enabled && drgevent.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] files = (string[])drgevent.Data.GetData(DataFormats.FileDrop); + + foreach (string file in files) + { + try + { + FileAttributes fa = File.GetAttributes(file); + + if ((fa & FileAttributes.Directory) == 0) + { + drgevent.Effect = DragDropEffects.Copy; + } + } + + catch + { + } + } + } + + base.OnDragEnter(drgevent); + } + + private string[] PruneDirectories(string[] fileNames) + { + List result = new List(); + + foreach (string fileName in fileNames) + { + try + { + FileAttributes fa = File.GetAttributes(fileName); + + if ((fa & FileAttributes.Directory) == 0) + { + result.Add(fileName); + } + } + + catch + { + } + } + + return result.ToArray(); + } + + protected override void OnDragDrop(DragEventArgs drgevent) + { + Activate(); + + if (!IsCurrentModalForm || !Enabled) + { + // do nothing + } + else if (drgevent.Data.GetDataPresent(DataFormats.FileDrop)) + { + string[] allFiles = (string[])drgevent.Data.GetData(DataFormats.FileDrop); + + if (allFiles == null) + { + return; + } + + string[] files = PruneDirectories(allFiles); + + bool importAsLayers = true; + + if (files.Length == 0) + { + return; + } + else + { + Icon formIcon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.DragDrop.OpenOrImport.FormIcon.png").Reference); + string title = PdnResources.GetString("DragDrop.OpenOrImport.Title"); + string infoText = PdnResources.GetString("DragDrop.OpenOrImport.InfoText"); + + TaskButton openTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuFileOpenIcon.png").Reference, + PdnResources.GetString("DragDrop.OpenOrImport.OpenButton.ActionText"), + PdnResources.GetString("DragDrop.OpenOrImport.OpenButton.ExplanationText")); + + string importLayersExplanation; + if (this.appWorkspace.DocumentWorkspaces.Length == 0) + { + importLayersExplanation = PdnResources.GetString("DragDrop.OpenOrImport.ImportLayers.ExplanationText.NoImagesYet"); + } + else + { + importLayersExplanation = PdnResources.GetString("DragDrop.OpenOrImport.ImportLayers.ExplanationText"); + } + + TaskButton importLayersTB = new TaskButton( + PdnResources.GetImageResource("Icons.MenuLayersImportFromFileIcon.png").Reference, + PdnResources.GetString("DragDrop.OpenOrImport.ImportLayers.ActionText"), + importLayersExplanation); + + TaskButton clickedTB = TaskDialog.Show( + this, + formIcon, + title, + null, + false, + infoText, + new TaskButton[] { openTB, importLayersTB, TaskButton.Cancel }, + null, + TaskButton.Cancel); + + if (clickedTB == openTB) + { + importAsLayers = false; + } + else if (clickedTB == importLayersTB) + { + importAsLayers = true; + } + else + { + return; + } + } + + if (!importAsLayers) + { + // open files into new tabs + this.appWorkspace.OpenFilesInNewWorkspace(files); + } + else + { + // no image open? we will have to create one + if (this.appWorkspace.ActiveDocumentWorkspace == null) + { + Size newSize = this.appWorkspace.GetNewDocumentSize(); + + this.appWorkspace.CreateBlankDocumentInNewWorkspace( + newSize, + Document.DefaultDpuUnit, + Document.GetDefaultDpu(Document.DefaultDpuUnit), + false); + } + + ImportFromFileAction action = new ImportFromFileAction(); + HistoryMemento ha = action.ImportMultipleFiles(this.appWorkspace.ActiveDocumentWorkspace, files); + + if (ha != null) + { + this.appWorkspace.ActiveDocumentWorkspace.History.PushNewMemento(ha); + } + } + } + + base.OnDragDrop(drgevent); + } + + private void DocumentWorkspace_ScaleFactorChanged(object sender, EventArgs e) + { + SetTitleText(); + } + + protected override void OnSizeChanged(EventArgs e) + { + base.OnSizeChanged(e); + SetTitleText(); + } + + private void DeferredInitialization(object sender, EventArgs e) + { + this.deferredInitializationTimer.Enabled = false; + this.deferredInitializationTimer.Tick -= new EventHandler(DeferredInitialization); + this.deferredInitializationTimer.Dispose(); + this.deferredInitializationTimer = null; + + // TODO + this.appWorkspace.ToolBar.MainMenu.PopulateEffects(); + } + + protected override void OnHelpRequested(HelpEventArgs hevent) + { + // F1 is already handled by the Menu->Help menu item. No need to process it twice. + hevent.Handled = true; + + base.OnHelpRequested(hevent); + } + + private void DefaultButton_Click(object sender, EventArgs e) + { + // Since defaultButton is the AcceptButton, hitting Enter will get 'eaten' by this button + // So we have to give the Enter key to the Tool + if (this.appWorkspace.ActiveDocumentWorkspace != null) + { + this.appWorkspace.ActiveDocumentWorkspace.Focus(); + + if (this.appWorkspace.ActiveDocumentWorkspace.Tool != null) + { + this.appWorkspace.ActiveDocumentWorkspace.Tool.PerformKeyPress(new KeyPressEventArgs('\r')); + this.appWorkspace.ActiveDocumentWorkspace.Tool.PerformKeyPress(Keys.Enter); + } + } + } + +#if DEBUG + static MainForm() + { + new Thread(FocusPrintThread).Start(); + } + + private static string GetControlName(Control control) + { + if (control == null) + { + return "null"; + } + + string name = control.Name + "(" + control.GetType().Name + ")"; + + if (control.Parent != null) + { + name += " <- " + GetControlName(control.Parent); + } + + return name; + } + + private static void PrintFocus() + { + Control c = Utility.FindFocus(); + Tracing.Ping("Focused: " + GetControlName(c)); + } + + private static void FocusPrintThread() + { + Thread.CurrentThread.IsBackground = true; + + while (true) + { + try + { + FormCollection forms = Application.OpenForms; + Form form; + if (forms.Count > 0) + { + form = forms[0]; + form.BeginInvoke(new Procedure(PrintFocus)); + } + } + + catch + { + } + + Thread.Sleep(1000); + } + } +#endif + + } +} diff --git a/src/MainForm.resx b/src/MainForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Manifests/asInvoker.xml b/src/Manifests/asInvoker.xml new file mode 100644 index 0000000..3653259 --- /dev/null +++ b/src/Manifests/asInvoker.xml @@ -0,0 +1,16 @@ + + + + + true + + + + + + + + + + \ No newline at end of file diff --git a/src/Manifests/embedManifest.bat b/src/Manifests/embedManifest.bat new file mode 100644 index 0000000..bbc3a50 --- /dev/null +++ b/src/Manifests/embedManifest.bat @@ -0,0 +1 @@ +mt.exe -nologo -manifest %2 %3 %4 "-outputresource:%1" \ No newline at end of file diff --git a/src/Manifests/requireAdministrator.xml b/src/Manifests/requireAdministrator.xml new file mode 100644 index 0000000..da51fa8 --- /dev/null +++ b/src/Manifests/requireAdministrator.xml @@ -0,0 +1,16 @@ + + + + + true + + + + + + + + + + \ No newline at end of file diff --git a/src/Menus/AdjustmentsMenu.cs b/src/Menus/AdjustmentsMenu.cs new file mode 100644 index 0000000..72d3e38 --- /dev/null +++ b/src/Menus/AdjustmentsMenu.cs @@ -0,0 +1,100 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Effects; +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class AdjustmentsMenu + : EffectMenuBase + { + public AdjustmentsMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.Name = "Menu.Layers.Adjustments"; + this.Text = PdnResources.GetString("Menu.Layers.Adjustments.Text"); + } + + protected override bool EnableEffectShortcuts + { + get + { + return true; + } + } + + protected override bool EnableRepeatEffectMenuItem + { + get + { + return false; + } + } + + protected override Keys GetEffectShortcutKeys(Effect effect) + { + Keys keys; + + if (effect is DesaturateEffect) + { + keys = Keys.Control | Keys.Shift | Keys.G; + } + else if (effect is AutoLevelEffect) + { + keys = Keys.Control | Keys.Shift | Keys.L; + } + else if (effect is InvertColorsEffect) + { + keys = Keys.Control | Keys.Shift | Keys.I; + } + else if (effect is HueAndSaturationAdjustment) + { + keys = Keys.Control | Keys.Shift | Keys.U; + } + else if (effect is SepiaEffect) + { + keys = Keys.Control | Keys.Shift | Keys.E; + } + else if (effect is BrightnessAndContrastAdjustment) + { + keys = Keys.Control | Keys.Shift | Keys.C; + } + else if (effect is LevelsEffect) + { + keys = Keys.Control | Keys.L; + } + else if (effect is CurvesEffect) + { + keys = Keys.Control | Keys.Shift | Keys.M; + } + else if (effect is PosterizeAdjustment) + { + keys = Keys.Control | Keys.Shift | Keys.P; + } + else + { + keys = Keys.None; + } + + return keys; + } + + protected override bool FilterEffects(Effect effect) + { + return (effect.Category == EffectCategory.Adjustment); + } + } +} diff --git a/src/Menus/CheckForUpdatesMenuItem.cs b/src/Menus/CheckForUpdatesMenuItem.cs new file mode 100644 index 0000000..509a022 --- /dev/null +++ b/src/Menus/CheckForUpdatesMenuItem.cs @@ -0,0 +1,268 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.SystemLayer; +using PaintDotNet.Updates; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class CheckForUpdatesMenuItem + : PdnMenuItem + { + private StateMachineExecutor stateMachineExecutor; + private UpdatesStateMachine updatesStateMachine; + private UpdatesDialog updatesDialog; + private bool calledFinish = false; + private System.Windows.Forms.Timer retryDialogTimer = null; + + protected override void OnAppWorkspaceChanged() + { + if (this.updatesStateMachine == null && + !PdnInfo.IsExpired && + (Security.IsAdministrator || Security.CanElevateToAdministrator)) + { + StartUpdates(); + } + + base.OnAppWorkspaceChanged(); + } + + private void StartUpdates() + { + if (!AppWorkspace.IsHandleCreated) + { + // can't use AppWorkspace as a sync context until it has a handle + AppWorkspace.HandleCreated += new EventHandler(AppWorkspace_HandleCreated); + } + else + { + InitUpdates(); + this.stateMachineExecutor.Start(); + } + } + + private void AppWorkspace_HandleCreated(object sender, EventArgs e) + { + AppWorkspace.HandleCreated -= new EventHandler(AppWorkspace_HandleCreated); + StartUpdates(); + } + + private void InitUpdates() + { + this.updatesStateMachine = new UpdatesStateMachine(); + this.updatesStateMachine.UIContext = AppWorkspace; + + this.stateMachineExecutor = new StateMachineExecutor(this.updatesStateMachine); + this.stateMachineExecutor.SyncContext = AppWorkspace; + + this.stateMachineExecutor.StateMachineFinished += OnStateMachineFinished; + this.stateMachineExecutor.StateBegin += OnStateBegin; + this.stateMachineExecutor.StateWaitingForInput += OnStateWaitingForInput; + } + + private void DisposeUpdates() + { + if (this.stateMachineExecutor != null) + { + this.stateMachineExecutor.StateMachineFinished -= OnStateMachineFinished; + this.stateMachineExecutor.StateBegin -= OnStateBegin; + this.stateMachineExecutor.StateWaitingForInput -= OnStateWaitingForInput; + this.stateMachineExecutor.Dispose(); + this.stateMachineExecutor = null; + } + + this.updatesStateMachine = null; + } + + private void OnStateBegin(object sender, EventArgs e) + { + if (e.Data is Updates.UpdateAvailableState && this.updatesDialog == null) + { + bool showDialogNow = true; + + // If no other modal window is on top of us, then go ahead and present + // the updates dialog. Otherwise, set a timer to check every few seconds + // and only when there's no other dialog sitting on top of us will we + // present the dialog. + + Form ourForm = AppWorkspace.FindForm(); + PdnBaseForm asPBF = ourForm as PdnBaseForm; + + if (asPBF != null) + { + if (!asPBF.IsCurrentModalForm) + { + showDialogNow = false; + } + } + + if (showDialogNow) + { + ShowUpdatesDialog(); + } + else + { + if (this.retryDialogTimer != null) + { + this.retryDialogTimer.Enabled = false; + this.retryDialogTimer.Dispose(); + this.retryDialogTimer = null; + } + + this.retryDialogTimer = new System.Windows.Forms.Timer(); + this.retryDialogTimer.Interval = 3000; + + this.retryDialogTimer.Tick += + delegate(object sender2, EventArgs e2) + { + bool done = false; + + if (IsDisposed) + { + done = true; + } + + Form ourForm2 = AppWorkspace.FindForm(); + PdnBaseForm asPBF2 = ourForm2 as PdnBaseForm; + + if (asPBF2 == null) + { + done = true; + } + else + { + if (this.updatesDialog != null) + { + // Updates dialog is already visible. + done = true; + } + else if (asPBF2.IsCurrentModalForm && asPBF2.Enabled) + { + ShowUpdatesDialog(); + done = true; + } + } + + if (done && this.retryDialogTimer != null) + { + this.retryDialogTimer.Enabled = false; + this.retryDialogTimer.Dispose(); + this.retryDialogTimer = null; + } + }; + + this.retryDialogTimer.Enabled = true; + } + } + else if (e.Data is Updates.ReadyToCheckState) + { + if (this.updatesDialog == null) + { + DisposeUpdates(); + } + } + } + + private void OnStateWaitingForInput(object sender, EventArgs e) + { + Updates.InstallingState installingState = e.Data as Updates.InstallingState; + + if (installingState != null) + { + installingState.Finish(AppWorkspace); + this.calledFinish = true; + } + } + + private void OnStateMachineFinished(object sender, EventArgs e) + { + DisposeUpdates(); + } + + private void ShowUpdatesDialog() + { + if (this.retryDialogTimer != null) + { + this.retryDialogTimer.Enabled = false; + this.retryDialogTimer.Dispose(); + this.retryDialogTimer = null; + } + + this.updatesDialog = new UpdatesDialog(); + this.updatesDialog.UpdatesStateMachine = this.stateMachineExecutor; + + if (!this.stateMachineExecutor.IsStarted) + { + //this.stateMachineExecutor.LowPriorityExecution = true; + this.stateMachineExecutor.Start(); + } + + updatesDialog.ShowDialog(AppWorkspace); + DialogResult dr = updatesDialog.DialogResult; + + this.updatesDialog.Dispose(); + this.updatesDialog = null; + + if (this.stateMachineExecutor != null) + { + if (dr == DialogResult.Yes && + this.stateMachineExecutor.CurrentState is Updates.ReadyToInstallState) + { + this.stateMachineExecutor.ProcessInput(Updates.UpdatesAction.Continue); + + while (!this.calledFinish) + { + Application.DoEvents(); + Thread.Sleep(10); + } + } + } + } + + public CheckForUpdatesMenuItem() + { + this.Name = "CheckForUpdates"; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + DisposeUpdates(); + } + + base.Dispose(disposing); + } + + protected override void OnClick(EventArgs e) + { + if (!Security.IsAdministrator && !Security.CanElevateToAdministrator) + { + Utility.ShowNonAdminErrorBox(AppWorkspace); + } + else + { + if (this.updatesStateMachine == null) + { + InitUpdates(); + } + + ShowUpdatesDialog(); + } + + base.OnClick(e); + } + } +} diff --git a/src/Menus/EditMenu.cs b/src/Menus/EditMenu.cs new file mode 100644 index 0000000..6fd1c55 --- /dev/null +++ b/src/Menus/EditMenu.cs @@ -0,0 +1,370 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryFunctions; +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class EditMenu + : PdnMenuItem + { + private PdnMenuItem menuEditUndo; + private PdnMenuItem menuEditRedo; + private ToolStripSeparator menuEditSeparator1; + private PdnMenuItem menuEditCut; + private PdnMenuItem menuEditCopy; + private PdnMenuItem menuEditPaste; + private PdnMenuItem menuEditPasteInToNewLayer; + private PdnMenuItem menuEditPasteInToNewImage; + private ToolStripSeparator menuEditSeparator2; + private PdnMenuItem menuEditEraseSelection; + private PdnMenuItem menuEditFillSelection; + private PdnMenuItem menuEditInvertSelection; + private PdnMenuItem menuEditSelectAll; + private PdnMenuItem menuEditDeselect; + + private bool OnBackspaceTyped(Keys keys) + { + if (AppWorkspace.ActiveDocumentWorkspace != null && + !AppWorkspace.ActiveDocumentWorkspace.Selection.IsEmpty) + { + this.menuEditFillSelection.PerformClick(); + return true; + } + else + { + return false; + } + } + + public EditMenu() + { + PdnBaseForm.RegisterFormHotKey(Keys.Back, OnBackspaceTyped); + PdnBaseForm.RegisterFormHotKey(Keys.Shift | Keys.Delete, OnLeftHandedCutHotKey); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Insert, OnLeftHandedCopyHotKey); + PdnBaseForm.RegisterFormHotKey(Keys.Shift | Keys.Insert, OnLeftHandedPasteHotKey); + InitializeComponent(); + } + + private bool OnLeftHandedCutHotKey(Keys keys) + { + this.menuEditCut.PerformClick(); + return true; + } + + private bool OnLeftHandedCopyHotKey(Keys keys) + { + this.menuEditCopy.PerformClick(); + return true; + } + + private bool OnLeftHandedPasteHotKey(Keys keys) + { + this.menuEditPaste.PerformClick(); + return true; + } + + private void InitializeComponent() + { + this.menuEditUndo = new PdnMenuItem(); + this.menuEditRedo = new PdnMenuItem(); + this.menuEditSeparator1 = new ToolStripSeparator(); + this.menuEditCut = new PdnMenuItem(); + this.menuEditCopy = new PdnMenuItem(); + this.menuEditPaste = new PdnMenuItem(); + this.menuEditPasteInToNewLayer = new PdnMenuItem(); + this.menuEditPasteInToNewImage = new PdnMenuItem(); + this.menuEditSeparator2 = new ToolStripSeparator(); + this.menuEditEraseSelection = new PdnMenuItem(); + this.menuEditFillSelection = new PdnMenuItem(); + this.menuEditInvertSelection = new PdnMenuItem(); + this.menuEditSelectAll = new PdnMenuItem(); + this.menuEditDeselect = new PdnMenuItem(); + // + // EditMenu + // + this.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuEditUndo, + this.menuEditRedo, + this.menuEditSeparator1, + this.menuEditCut, + this.menuEditCopy, + this.menuEditPaste, + this.menuEditPasteInToNewLayer, + this.menuEditPasteInToNewImage, + this.menuEditSeparator2, + this.menuEditEraseSelection, + this.menuEditFillSelection, + this.menuEditInvertSelection, + this.menuEditSelectAll, + this.menuEditDeselect + }); + this.Name = "Menu.Edit"; + this.Text = PdnResources.GetString("Menu.Edit.Text"); + // + // menuEditUndo + // + this.menuEditUndo.Name = "Undo"; + this.menuEditUndo.ShortcutKeys = Keys.Control | Keys.Z; + this.menuEditUndo.Click += new System.EventHandler(this.MenuEditUndo_Click); + // + // menuEditRedo + // + this.menuEditRedo.Name = "Redo"; + this.menuEditRedo.ShortcutKeys = Keys.Control | Keys.Y; + this.menuEditRedo.Click += new System.EventHandler(this.MenuEditRedo_Click); + // + // menuEditCut + // + this.menuEditCut.Name = "Cut"; + this.menuEditCut.ShortcutKeys = Keys.Control | Keys.X; + this.menuEditCut.Click += new System.EventHandler(this.MenuEditCut_Click); + // + // menuEditCopy + // + this.menuEditCopy.Name = "Copy"; + this.menuEditCopy.ShortcutKeys = Keys.Control | Keys.C; + this.menuEditCopy.Click += new System.EventHandler(this.MenuEditCopy_Click); + // + // menuEditPaste + // + this.menuEditPaste.Name = "Paste"; + this.menuEditPaste.ShortcutKeys = Keys.Control | Keys.V; + this.menuEditPaste.Click += new System.EventHandler(this.MenuEditPaste_Click); + // + // menuEditPasteInToNewLayer + // + this.menuEditPasteInToNewLayer.Name = "PasteInToNewLayer"; + this.menuEditPasteInToNewLayer.ShortcutKeys = Keys.Control | Keys.Shift | Keys.V; + this.menuEditPasteInToNewLayer.Click += new System.EventHandler(this.MenuEditPasteInToNewLayer_Click); + // + // menuEditPasteInToNewImage + // + this.menuEditPasteInToNewImage.Name = "PasteInToNewImage"; + this.menuEditPasteInToNewImage.ShortcutKeys = Keys.Control | Keys.Alt | Keys.V; + this.menuEditPasteInToNewImage.Click += new EventHandler(MenuEditPasteInToNewImage_Click); + // + // menuEditEraseSelection + // + this.menuEditEraseSelection.Name = "EraseSelection"; + this.menuEditEraseSelection.ShortcutKeys = Keys.Delete; + this.menuEditEraseSelection.Click += new System.EventHandler(this.MenuEditClearSelection_Click); + // + // menuEditFillSelection + // + this.menuEditFillSelection.Name = "FillSelection"; + this.menuEditFillSelection.ShortcutKeyDisplayString = PdnResources.GetString("Menu.Edit.FillSelection.ShortcutKeysDisplayString"); + this.menuEditFillSelection.Click += new EventHandler(MenuEditFillSelection_Click); + // + // menuEditInvertSelection + // + this.menuEditInvertSelection.Name = "InvertSelection"; + this.menuEditInvertSelection.Click += new System.EventHandler(this.MenuEditInvertSelection_Click); + this.menuEditInvertSelection.ShortcutKeys = Keys.Control | Keys.I; + // + // menuEditSelectAll + // + this.menuEditSelectAll.Name = "SelectAll"; + this.menuEditSelectAll.ShortcutKeys = Keys.Control | Keys.A; + this.menuEditSelectAll.Click += new System.EventHandler(this.MenuEditSelectAll_Click); + // + // menuEditDeselect + // + this.menuEditDeselect.Name = "Deselect"; + this.menuEditDeselect.ShortcutKeys = Keys.Control | Keys.D; + this.menuEditDeselect.Click += new System.EventHandler(this.MenuEditDeselect_Click); + } + + protected override void OnDropDownOpening(EventArgs e) + { + bool selection; + bool bitmapLayer; + + if (AppWorkspace.ActiveDocumentWorkspace == null) + { + selection = false; + bitmapLayer = false; + this.menuEditSelectAll.Enabled = false; + } + else + { + selection = !AppWorkspace.ActiveDocumentWorkspace.Selection.IsEmpty; + bitmapLayer = AppWorkspace.ActiveDocumentWorkspace.ActiveLayer is BitmapLayer; + this.menuEditSelectAll.Enabled = true; + } + + this.menuEditCopy.Enabled = selection; + this.menuEditCut.Enabled = selection && bitmapLayer; + this.menuEditEraseSelection.Enabled = selection; + this.menuEditFillSelection.Enabled = selection; + this.menuEditInvertSelection.Enabled = selection; + this.menuEditDeselect.Enabled = selection; + + // find out if there's anything on the clipboard that we can use + bool isClipImageAvailable = Utility.IsClipboardImageAvailable(); + + this.menuEditPaste.Enabled = isClipImageAvailable && (AppWorkspace.ActiveDocumentWorkspace != null); + + if (!this.menuEditPaste.Enabled) + { + if (AppWorkspace.ActiveDocumentWorkspace != null && + AppWorkspace.ActiveDocumentWorkspace.Tool != null) + { + bool canHandle; + + try + { + IDataObject pasted = Clipboard.GetDataObject(); + AppWorkspace.ActiveDocumentWorkspace.Tool.PerformPasteQuery(pasted, out canHandle); + } + + catch (ExternalException) + { + canHandle = false; + } + + if (canHandle) + { + this.menuEditPaste.Enabled = true; + } + } + } + + this.menuEditPasteInToNewLayer.Enabled = isClipImageAvailable && (AppWorkspace.ActiveDocumentWorkspace != null); + this.menuEditPasteInToNewImage.Enabled = isClipImageAvailable; + + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + this.menuEditUndo.Enabled = (AppWorkspace.ActiveDocumentWorkspace.History.UndoStack.Count > 1); // top of stack is always assumed to be a "NullHistoryMemento," which is not undoable! thus we don't count it + this.menuEditRedo.Enabled = (AppWorkspace.ActiveDocumentWorkspace.History.RedoStack.Count > 0); + } + else + { + this.menuEditUndo.Enabled = false; + this.menuEditRedo.Enabled = false; + } + + base.OnDropDownOpening(e); + } + + private void MenuEditUndo_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null && + !AppWorkspace.ActiveDocumentWorkspace.IsMouseCaptured()) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new HistoryUndoAction()); + } + } + + private void MenuEditRedo_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null && + !AppWorkspace.ActiveDocumentWorkspace.IsMouseCaptured()) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new HistoryRedoAction()); + } + } + + private void MenuEditCopy_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + new CopyToClipboardAction(AppWorkspace.ActiveDocumentWorkspace).PerformAction(); + } + } + + private void MenuEditCut_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + CutAction cutAction = new CutAction(); + cutAction.PerformAction(AppWorkspace.ActiveDocumentWorkspace); + } + } + + private void MenuEditInvertSelection_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null && + !AppWorkspace.ActiveDocumentWorkspace.Selection.IsEmpty) + { + HistoryFunctionResult result = AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new InvertSelectionFunction()); + + // Make sure that the selection info shows up in the status bar, and not the tool's help text + if (result == HistoryFunctionResult.Success) + { + AppWorkspace.ActiveDocumentWorkspace.Selection.PerformChanging(); + AppWorkspace.ActiveDocumentWorkspace.Selection.PerformChanged(); + } + } + } + + private void MenuEditClearSelection_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new EraseSelectionFunction()); + } + } + + private void MenuEditFillSelection_Click(object sender, EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new FillSelectionFunction(AppWorkspace.AppEnvironment.PrimaryColor)); + } + } + + private void MenuEditSelectAll_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new SelectAllFunction()); + + // Make sure that the selection info shows up in the status bar, and not the tool's help text + AppWorkspace.ActiveDocumentWorkspace.Selection.PerformChanging(); + AppWorkspace.ActiveDocumentWorkspace.Selection.PerformChanged(); + } + } + + private void MenuEditDeselect_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new DeselectFunction()); + } + } + + private void MenuEditPaste_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + new PasteAction(AppWorkspace.ActiveDocumentWorkspace).PerformAction(); + } + } + + private void MenuEditPasteInToNewLayer_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + new PasteInToNewLayerAction(AppWorkspace.ActiveDocumentWorkspace).PerformAction(); + } + } + + private void MenuEditPasteInToNewImage_Click(object sender, EventArgs e) + { + AppWorkspace.PerformAction(new PasteInToNewImageAction()); + } + } +} diff --git a/src/Menus/EffectMenuBase.cs b/src/Menus/EffectMenuBase.cs new file mode 100644 index 0000000..37c78ad --- /dev/null +++ b/src/Menus/EffectMenuBase.cs @@ -0,0 +1,1130 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.Effects; +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal abstract class EffectMenuBase + : PdnMenuItem + { + private const int tilesPerCpu = 75; + private int renderingThreadCount = Math.Max(2, SystemLayer.Processor.LogicalCpuCount); + private const int effectRefreshInterval = 15; + private PdnRegion[] progressRegions; + private int progressRegionsStartIndex; + + private PdnMenuItem sentinel; + private bool menuPopulated = false; + private EffectsCollection effects; + + private Effect lastEffect = null; + private EffectConfigToken lastEffectToken = null; + private Dictionary effectTokens = new Dictionary(); + + private System.Windows.Forms.Timer invalidateTimer; + private System.ComponentModel.Container components = null; + + protected abstract bool EnableEffectShortcuts + { + get; + } + + protected abstract bool EnableRepeatEffectMenuItem + { + get; + } + + protected abstract bool FilterEffects(Effect effect); + + private bool IsBuiltInEffect(Effect effect) + { + if (effect == null) + { + return true; + } + + Type effectType = effect.GetType(); + Type effectBaseType = typeof(Effect); + + // Built-in effects only live in PaintDotNet.Effects.dll + + if (effectType.Assembly == effectBaseType.Assembly) + { + return true; + } + else + { + return false; + } + } + + private void HandleEffectException(AppWorkspace appWorkspace, Effect effect, Exception ex) + { + try + { + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar(); + } + + catch (Exception) + { + } + + // Figure out if it's a built-in effect, or a plug-in + bool builtIn = IsBuiltInEffect(effect); + + if (builtIn) + { + // For built-in effects, tear down Paint.NET which will result in a crash log + throw new ApplicationException("Effect threw an exception", ex); + } + else + { + Icon formIcon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.BugWarning.png").Reference); + + string formTitle = PdnResources.GetString("Effect.PluginErrorDialog.Title"); + + Image taskImage = null; + + string introText = PdnResources.GetString("Effect.PluginErrorDialog.IntroText"); + + TaskButton restartTB = new TaskButton( + PdnResources.GetImageResource("Icons.RightArrowBlue.png").Reference, + PdnResources.GetString("Effect.PluginErrorDialog.RestartTB.ActionText"), + PdnResources.GetString("Effect.PluginErrorDialog.RestartTB.ExplanationText")); + + TaskButton doNotRestartTB = new TaskButton( + PdnResources.GetImageResource("Icons.WarningIcon.png").Reference, + PdnResources.GetString("Effect.PluginErrorDialog.DoNotRestartTB.ActionText"), + PdnResources.GetString("Effect.PluginErrorDialog.DoNotRestartTB.ExplanationText")); + + string auxButtonText = PdnResources.GetString("Effect.PluginErrorDialog.AuxButton1.Text"); + + EventHandler auxButtonClickHandler = + delegate(object sender, EventArgs e) + { + using (PdnBaseForm textBoxForm = new PdnBaseForm()) + { + textBoxForm.Name = "EffectCrash"; + + TextBox exceptionBox = new TextBox(); + + textBoxForm.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.WarningIcon.png").Reference); + textBoxForm.Text = PdnResources.GetString("Effect.PluginErrorDialog.Title"); + + exceptionBox.Dock = DockStyle.Fill; + exceptionBox.ReadOnly = true; + exceptionBox.Multiline = true; + + string exceptionText = AppWorkspace.GetLocalizedEffectErrorMessage(effect.GetType().Assembly, effect.GetType(), ex); + + exceptionBox.Font = new Font(FontFamily.GenericMonospace, exceptionBox.Font.Size); + exceptionBox.Text = exceptionText; + exceptionBox.ScrollBars = ScrollBars.Vertical; + + textBoxForm.StartPosition = FormStartPosition.CenterParent; + textBoxForm.ShowInTaskbar = false; + textBoxForm.MinimizeBox = false; + textBoxForm.Controls.Add(exceptionBox); + textBoxForm.Width = UI.ScaleWidth(700); + + textBoxForm.ShowDialog(); + } + }; + + TaskButton clickedTB = TaskDialog.Show( + appWorkspace, + formIcon, + formTitle, + taskImage, + true, + introText, + new TaskButton[] { restartTB, doNotRestartTB }, + restartTB, + doNotRestartTB, + TaskDialog.DefaultPixelWidth96Dpi * 2, + auxButtonText, + auxButtonClickHandler); + + if (clickedTB == restartTB) + { + // Next, apply restart logic + CloseAllWorkspacesAction cawa = new CloseAllWorkspacesAction(); + cawa.PerformAction(appWorkspace); + + if (!cawa.Cancelled) + { + SystemLayer.Shell.RestartApplication(); + Startup.CloseApplication(); + } + } + } + } + + public EffectMenuBase() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.sentinel = new PdnMenuItem(); + // + // sentinel + // + this.sentinel.Name = null; + // + // components + // + this.components = new System.ComponentModel.Container(); + // + // invalidateTimer + // + this.invalidateTimer = new System.Windows.Forms.Timer(this.components); + this.invalidateTimer.Enabled = false; + this.invalidateTimer.Tick += InvalidateTimer_Tick; + this.invalidateTimer.Interval = effectRefreshInterval; + // + // EffectMenuBase + // + this.DropDownItems.Add(sentinel); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.components != null) + { + this.components.Dispose(); + this.components = null; + } + } + + base.Dispose(disposing); + } + + protected override void OnDropDownOpening(EventArgs e) + { + if (!this.menuPopulated) + { + PopulateMenu(); + } + + bool enabled = (AppWorkspace.ActiveDocumentWorkspace != null); + + foreach (ToolStripItem item in this.DropDownItems) + { + item.Enabled = enabled; + } + + base.OnDropDownOpening(e); + } + + public EffectsCollection Effects + { + get + { + if (this.effects == null) + { + this.effects = GatherEffects(); + } + + return this.effects; + } + } + + public void PopulateEffects() + { + PopulateMenu(false); + } + + private void PopulateMenu(bool forceRepopulate) + { + if (forceRepopulate) + { + this.menuPopulated = false; + } + + PopulateMenu(); + } + + private void PopulateMenu() + { + this.DropDownItems.Clear(); + + if (EnableRepeatEffectMenuItem && this.lastEffect != null) + { + string repeatFormat = PdnResources.GetString("Effects.RepeatMenuItem.Format"); + string menuName = string.Format(repeatFormat, lastEffect.Name); + PdnMenuItem pmi = new PdnMenuItem(menuName, this.lastEffect.Image, RepeatEffectMenuItem_Click); + pmi.Name = "RepeatEffect(" + this.lastEffect.GetType().FullName + ")"; + pmi.ShortcutKeys = Keys.Control | Keys.F; + this.DropDownItems.Add(pmi); + + ToolStripSeparator tss = new ToolStripSeparator(); + this.DropDownItems.Add(tss); + } + + AddEffectsToMenu(); + + Triple[] errors = this.Effects.GetLoaderExceptions(); + + for (int i = 0; i < errors.Length; ++i) + { + AppWorkspace.ReportEffectLoadError(errors[i]); + } + } + + protected virtual Keys GetEffectShortcutKeys(Effect effect) + { + return Keys.None; + } + + private void AddEffectToMenu(Effect effect, bool withShortcut) + { + if (!FilterEffects(effect)) + { + return; + } + + string name = effect.Name; + + if (effect.CheckForEffectFlags(EffectFlags.Configurable)) + { + string configurableFormat = PdnResources.GetString("Effects.Name.Format.Configurable"); + name = string.Format(configurableFormat, name); + } + + PdnMenuItem mi = new PdnMenuItem(name, effect.Image, EffectMenuItem_Click); + + if (withShortcut) + { + mi.ShortcutKeys = GetEffectShortcutKeys(effect); + } + else + { + mi.ShortcutKeys = Keys.None; + } + + mi.Tag = (object)effect.GetType(); + mi.Name = "Effect(" + effect.GetType().FullName + ")"; + + PdnMenuItem addEffectHere = this; + + if (effect.SubMenuName != null) + { + PdnMenuItem subMenu = null; + + // search for this subMenu + foreach (ToolStripItem sub in this.DropDownItems) + { + PdnMenuItem subpmi = sub as PdnMenuItem; + + if (subpmi != null) + { + if (subpmi.Text == effect.SubMenuName) + { + subMenu = subpmi; + break; + } + } + } + + if (subMenu == null) + { + subMenu = new PdnMenuItem(effect.SubMenuName, null, null); + this.DropDownItems.Add(subMenu); + } + + addEffectHere = subMenu; + } + + addEffectHere.DropDownItems.Add(mi); + } + + private void AddEffectsToMenu() + { + // Fill the menu with the effect names, and "..." if it is configurable + EffectsCollection effectsCollection = this.Effects; + Type[] effectTypes = effectsCollection.Effects; + bool withShortcuts = EnableEffectShortcuts; + + List newEffects = new List(); + foreach (Type type in effectsCollection.Effects) + { + try + { + ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes); + Effect effect = (Effect)ci.Invoke(null); + + if (FilterEffects(effect)) + { + newEffects.Add(effect); + } + } + + catch (Exception ex) + { + // We don't want a DLL that can't be figured out to cause the app to crash + //continue; + AppWorkspace.ReportEffectLoadError(Triple.Create(type.Assembly, type, ex)); + } + } + + newEffects.Sort( + delegate(Effect lhs, Effect rhs) + { + return string.Compare(lhs.Name, rhs.Name, true); + }); + + List subMenuNames = new List(); + + foreach (Effect effect in newEffects) + { + if (!string.IsNullOrEmpty(effect.SubMenuName)) + { + subMenuNames.Add(effect.SubMenuName); + } + } + + subMenuNames.Sort( + delegate(string lhs, string rhs) + { + return string.Compare(lhs, rhs, true); + }); + + string lastSubMenuName = null; + foreach (string subMenuName in subMenuNames) + { + if (subMenuName == lastSubMenuName) + { + // skip duplicate names + continue; + } + + PdnMenuItem subMenu = new PdnMenuItem(subMenuName, null, null); + DropDownItems.Add(subMenu); + lastSubMenuName = subMenuName; + } + + foreach (Effect effect in newEffects) + { + AddEffectToMenu(effect, withShortcuts); + } + } + + private static EffectsCollection GatherEffects() + { + List assemblies = new List(); + + // PaintDotNet.Effects.dll + assemblies.Add(Assembly.GetAssembly(typeof(Effect))); + + // TARGETDIR\Effects\*.dll + string homeDir = PdnInfo.GetApplicationDir(); + string effectsDir = Path.Combine(homeDir, InvariantStrings.EffectsSubDir); + bool dirExists; + + try + { + dirExists = Directory.Exists(effectsDir); + } + + catch + { + dirExists = false; + } + + if (dirExists) + { + string fileSpec = "*" + InvariantStrings.DllExtension; + string[] filePaths = Directory.GetFiles(effectsDir, fileSpec); + + foreach (string filePath in filePaths) + { + Assembly pluginAssembly = null; + + try + { + pluginAssembly = Assembly.LoadFrom(filePath); + assemblies.Add(pluginAssembly); + } + + catch (Exception ex) + { + Tracing.Ping("Exception while loading " + filePath + ": " + ex.ToString()); + } + } + } + + EffectsCollection ec = new EffectsCollection(assemblies); + return ec; + } + + private void RepeatEffectMenuItem_Click(object sender, EventArgs e) + { + Exception exception = null; + Effect effect = null; + DocumentWorkspace activeDW = AppWorkspace.ActiveDocumentWorkspace; + + if (activeDW != null) + { + using (new PushNullToolMode(activeDW)) + { + Surface copy = activeDW.BorrowScratchSurface(this.GetType() + ".RepeatEffectMenuItem_Click() utilizing scratch for rendering"); + + try + { + using (new WaitCursorChanger(AppWorkspace)) + { + copy.CopySurface(((BitmapLayer)activeDW.ActiveLayer).Surface); + } + + PdnRegion selectedRegion = activeDW.Selection.CreateRegion(); + + EffectEnvironmentParameters eep = new EffectEnvironmentParameters( + AppWorkspace.AppEnvironment.PrimaryColor, + AppWorkspace.AppEnvironment.SecondaryColor, + AppWorkspace.AppEnvironment.PenInfo.Width, + selectedRegion, + copy); + + effect = (Effect)Activator.CreateInstance(this.lastEffect.GetType()); + effect.EnvironmentParameters = eep; + + EffectConfigToken token; + + if (this.lastEffectToken == null) + { + token = null; + } + else + { + token = (EffectConfigToken)this.lastEffectToken.Clone(); + } + + DoEffect(effect, token, selectedRegion, selectedRegion, copy, out exception); + } + + finally + { + activeDW.ReturnScratchSurface(copy); + } + } + } + + if (exception != null) + { + HandleEffectException(AppWorkspace, effect, exception); + } + } + + private void EffectMenuItem_Click(object sender, EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace == null) + { + return; + } + + PdnMenuItem pmi = (PdnMenuItem)sender; + Type effectType = (Type)pmi.Tag; + + RunEffect(effectType); + } + + public void RunEffect(Type effectType) + { + bool oldDirtyValue = AppWorkspace.ActiveDocumentWorkspace.Document.Dirty; + bool resetDirtyValue = false; + + AppWorkspace.Update(); // make sure the window is done 'closing' + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + DocumentWorkspace activeDW = AppWorkspace.ActiveDocumentWorkspace; + + PdnRegion selectedRegion; + + if (activeDW.Selection.IsEmpty) + { + selectedRegion = new PdnRegion(activeDW.Document.Bounds); + } + else + { + selectedRegion = activeDW.Selection.CreateRegion(); + } + + Exception exception = null; + Effect effect = null; + BitmapLayer layer = (BitmapLayer)activeDW.ActiveLayer; + + using (new PushNullToolMode(activeDW)) + { + try + { + effect = (Effect)Activator.CreateInstance(effectType); + + string name = effect.Name; + EffectConfigToken newLastToken = null; + + if (!(effect.CheckForEffectFlags(EffectFlags.Configurable))) + { + Surface copy = activeDW.BorrowScratchSurface(this.GetType() + ".RunEffect() using scratch surface for non-configurable rendering"); + + try + { + using (new WaitCursorChanger(AppWorkspace)) + { + copy.CopySurface(layer.Surface); + } + + EffectEnvironmentParameters eep = new EffectEnvironmentParameters( + AppWorkspace.AppEnvironment.PrimaryColor, + AppWorkspace.AppEnvironment.SecondaryColor, + AppWorkspace.AppEnvironment.PenInfo.Width, + selectedRegion, + copy); + + effect.EnvironmentParameters = eep; + + DoEffect(effect, null, selectedRegion, selectedRegion, copy, out exception); + } + + finally + { + activeDW.ReturnScratchSurface(copy); + } + } + else + { + PdnRegion previewRegion = (PdnRegion)selectedRegion.Clone(); + previewRegion.Intersect(RectangleF.Inflate(activeDW.VisibleDocumentRectangleF, 1, 1)); + + Surface originalSurface = activeDW.BorrowScratchSurface(this.GetType() + ".RunEffect() using scratch surface for rendering during configuration"); + + try + { + using (new WaitCursorChanger(AppWorkspace)) + { + originalSurface.CopySurface(layer.Surface); + } + + EffectEnvironmentParameters eep = new EffectEnvironmentParameters( + AppWorkspace.AppEnvironment.PrimaryColor, + AppWorkspace.AppEnvironment.SecondaryColor, + AppWorkspace.AppEnvironment.PenInfo.Width, + selectedRegion, + originalSurface); + + effect.EnvironmentParameters = eep; + + // + IDisposable resumeTUFn = AppWorkspace.SuspendThumbnailUpdates(); + // + + using (EffectConfigDialog configDialog = effect.CreateConfigDialog()) + { + configDialog.Opacity = 0.9; + configDialog.Effect = effect; + configDialog.EffectSourceSurface = originalSurface; + configDialog.Selection = selectedRegion; + + BackgroundEffectRenderer ber = null; + + EventHandler eh = + delegate(object sender, EventArgs e) + { + EffectConfigDialog ecf = (EffectConfigDialog)sender; + + if (ber != null) + { + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBarAsync(); + + try + { + ber.Start(); + } + + catch (Exception ex) + { + exception = ex; + ecf.Close(); + } + } + }; + + configDialog.EffectTokenChanged += eh; + + if (this.effectTokens.ContainsKey(effectType)) + { + EffectConfigToken oldToken = (EffectConfigToken)effectTokens[effectType].Clone(); + configDialog.EffectToken = oldToken; + } + + ber = new BackgroundEffectRenderer( + effect, + configDialog.EffectToken, + new RenderArgs(layer.Surface), + new RenderArgs(originalSurface), + previewRegion, + tilesPerCpu * renderingThreadCount, + renderingThreadCount); + + ber.RenderedTile += new RenderedTileEventHandler(RenderedTileHandler); + ber.StartingRendering += new EventHandler(StartingRenderingHandler); + ber.FinishedRendering += new EventHandler(FinishedRenderingHandler); + + invalidateTimer.Enabled = true; + + DialogResult dr; + + try + { + dr = Utility.ShowDialog(configDialog, AppWorkspace); + } + + catch (Exception ex) + { + dr = DialogResult.None; + exception = ex; + } + + invalidateTimer.Enabled = false; + + this.InvalidateTimer_Tick(invalidateTimer, EventArgs.Empty); + + if (dr == DialogResult.OK) + { + this.effectTokens[effectType] = (EffectConfigToken)configDialog.EffectToken.Clone(); + } + + using (new WaitCursorChanger(AppWorkspace)) + { + try + { + ber.Abort(); + ber.Join(); + } + + catch (Exception ex) + { + exception = ex; + } + + ber.Dispose(); + ber = null; + + if (dr != DialogResult.OK) + { + ((BitmapLayer)activeDW.ActiveLayer).Surface.CopySurface(originalSurface); + activeDW.ActiveLayer.Invalidate(); + } + + configDialog.EffectTokenChanged -= eh; + configDialog.Hide(); + AppWorkspace.Update(); + previewRegion.Dispose(); + } + + // + resumeTUFn.Dispose(); + resumeTUFn = null; + // + + if (dr == DialogResult.OK) + { + PdnRegion remainingToRender = selectedRegion.Clone(); + PdnRegion alreadyRendered = PdnRegion.CreateEmpty(); + + for (int i = 0; i < this.progressRegions.Length; ++i) + { + if (this.progressRegions[i] == null) + { + break; + } + else + { + remainingToRender.Exclude(this.progressRegions[i]); + alreadyRendered.Union(this.progressRegions[i]); + } + } + + activeDW.ActiveLayer.Invalidate(alreadyRendered); + newLastToken = (EffectConfigToken)configDialog.EffectToken.Clone(); + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + DoEffect(effect, newLastToken, selectedRegion, remainingToRender, originalSurface, out exception); + } + else // if (dr == DialogResult.Cancel) + { + using (new WaitCursorChanger(AppWorkspace)) + { + activeDW.ActiveLayer.Invalidate(); + Utility.GCFullCollect(); + } + + resetDirtyValue = true; + return; + } + } + } + + catch (Exception ex) + { + exception = ex; + } + + finally + { + activeDW.ReturnScratchSurface(originalSurface); + } + } + + // if it was from the Effects menu, save it as the "Repeat ...." item + if (effect.Category == EffectCategory.Effect) + { + this.lastEffect = effect; + + if (newLastToken == null) + { + this.lastEffectToken = null; + } + else + { + this.lastEffectToken = (EffectConfigToken)newLastToken.Clone(); + } + + PopulateMenu(true); + } + } + + catch (Exception ex) + { + exception = ex; + } + + finally + { + selectedRegion.Dispose(); + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBar(); + AppWorkspace.ActiveDocumentWorkspace.EnableOutlineAnimation = true; + + if (this.progressRegions != null) + { + for (int i = 0; i < this.progressRegions.Length; ++i) + { + if (this.progressRegions[i] != null) + { + this.progressRegions[i].Dispose(); + this.progressRegions[i] = null; + } + } + } + + if (resetDirtyValue) + { + AppWorkspace.ActiveDocumentWorkspace.Document.Dirty = oldDirtyValue; + } + + if (exception != null) + { + HandleEffectException(AppWorkspace, effect, exception); + } + } + } + } + + private void RenderedTileHandler(object sender, RenderedTileEventArgs e) + { + if (this.progressRegions[e.TileNumber] == null) + { + this.progressRegions[e.TileNumber] = e.RenderedRegion; + } + } + + private void InvalidateTimer_Tick(object sender, System.EventArgs e) + { + if (AppWorkspace.FindForm().WindowState == FormWindowState.Minimized) + { + return; + } + + if (this.progressRegions == null) + { + return; + } + + lock (this.progressRegions) + { + int min = this.progressRegionsStartIndex; + int max; + + for (max = min; max < progressRegions.Length; ++max) + { + if (this.progressRegions[max] == null) + { + break; + } + } + + if (min != max) + { + using (PdnRegion updateRegion = PdnRegion.CreateEmpty()) + { + for (int i = min; i < max; ++i) + { + updateRegion.Union(this.progressRegions[i]); + } + + using (PdnRegion simplified = Utility.SimplifyAndInflateRegion(updateRegion)) + { + AppWorkspace.ActiveDocumentWorkspace.ActiveLayer.Invalidate(simplified); + } + + this.progressRegionsStartIndex = max; + } + } + + double progress = 100.0 * (double)max / (double)progressRegions.Length; + AppWorkspace.Widgets.StatusBarProgress.SetProgressStatusBar(progress); + } + } + + private void FinishedRenderingHandler(object sender, EventArgs e) + { + if (AppWorkspace.InvokeRequired) + { + AppWorkspace.BeginInvoke(new EventHandler(FinishedRenderingHandler), new object[] { sender, e }); + } + else + { + AppWorkspace.ActiveDocumentWorkspace.EnableOutlineAnimation = true; + } + } + + private void StartingRenderingHandler(object sender, EventArgs e) + { + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBarAsync(); + AppWorkspace.ActiveDocumentWorkspace.EnableOutlineAnimation = false; + + if (this.progressRegions == null) + { + this.progressRegions = new PdnRegion[tilesPerCpu * renderingThreadCount]; + } + + lock (this.progressRegions) + { + for (int i = 0; i < progressRegions.Length; ++i) + { + progressRegions[i] = null; + } + + this.progressRegionsStartIndex = 0; + } + } + + private bool DoEffect(Effect effect, EffectConfigToken token, PdnRegion selectedRegion, + PdnRegion regionToRender, Surface originalSurface, out Exception exception) + { + exception = null; + bool oldDirtyValue = AppWorkspace.ActiveDocumentWorkspace.Document.Dirty; + bool resetDirtyValue = false; + + bool returnVal = false; + AppWorkspace.ActiveDocumentWorkspace.EnableOutlineAnimation = false; + + try + { + using (ProgressDialog aed = new ProgressDialog()) + { + if (effect.Image != null) + { + aed.Icon = Utility.ImageToIcon(effect.Image, Utility.TransparentKey); + } + + aed.Opacity = 0.9; + aed.Value = 0; + aed.Text = effect.Name; + aed.Description = string.Format(PdnResources.GetString("Effects.ApplyingDialog.Description"), effect.Name); + + invalidateTimer.Enabled = true; + + using (new WaitCursorChanger(AppWorkspace)) + { + HistoryMemento ha = null; + DialogResult result = DialogResult.None; + + AppWorkspace.Widgets.StatusBarProgress.ResetProgressStatusBar(); + AppWorkspace.Widgets.LayerControl.SuspendLayerPreviewUpdates(); + + try + { + ManualResetEvent saveEvent = new ManualResetEvent(false); + BitmapHistoryMemento bha = null; + + // perf bug #1445: save this data in a background thread + PdnRegion selectedRegionCopy = selectedRegion.Clone(); + PaintDotNet.Threading.ThreadPool.Global.QueueUserWorkItem( + delegate(object context) + { + try + { + ImageResource image; + + if (effect.Image == null) + { + image = null; + } + else + { + image = ImageResource.FromImage(effect.Image); + } + + bha = new BitmapHistoryMemento(effect.Name, image, this.AppWorkspace.ActiveDocumentWorkspace, + this.AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex, selectedRegionCopy, originalSurface); + } + + finally + { + saveEvent.Set(); + selectedRegionCopy.Dispose(); + selectedRegionCopy = null; + } + }); + + BackgroundEffectRenderer ber = new BackgroundEffectRenderer( + effect, + token, + new RenderArgs(((BitmapLayer)AppWorkspace.ActiveDocumentWorkspace.ActiveLayer).Surface), + new RenderArgs(originalSurface), + regionToRender, + tilesPerCpu * renderingThreadCount, + renderingThreadCount); + + ber.RenderedTile += new RenderedTileEventHandler(aed.RenderedTileHandler); + ber.RenderedTile += new RenderedTileEventHandler(RenderedTileHandler); + ber.StartingRendering += new EventHandler(StartingRenderingHandler); + ber.FinishedRendering += new EventHandler(aed.FinishedRenderingHandler); + ber.FinishedRendering += new EventHandler(FinishedRenderingHandler); + ber.Start(); + + result = Utility.ShowDialog(aed, AppWorkspace); + + if (result == DialogResult.Cancel) + { + resetDirtyValue = true; + + using (new WaitCursorChanger(AppWorkspace)) + { + try + { + ber.Abort(); + ber.Join(); + } + + catch (Exception ex) + { + exception = ex; + } + + ((BitmapLayer)AppWorkspace.ActiveDocumentWorkspace.ActiveLayer).Surface.CopySurface(originalSurface); + } + } + + invalidateTimer.Enabled = false; + + try + { + ber.Join(); + } + + catch (Exception ex) + { + exception = ex; + } + + ber.Dispose(); + + saveEvent.WaitOne(); + saveEvent.Close(); + saveEvent = null; + + ha = bha; + } + + catch (Exception) + { + using (new WaitCursorChanger(AppWorkspace)) + { + ((BitmapLayer)AppWorkspace.ActiveDocumentWorkspace.ActiveLayer).Surface.CopySurface(originalSurface); + ha = null; + } + } + + finally + { + AppWorkspace.Widgets.LayerControl.ResumeLayerPreviewUpdates(); + } + + using (PdnRegion simplifiedRenderRegion = Utility.SimplifyAndInflateRegion(selectedRegion)) + { + using (new WaitCursorChanger(AppWorkspace)) + { + AppWorkspace.ActiveDocumentWorkspace.ActiveLayer.Invalidate(simplifiedRenderRegion); + } + } + + using (new WaitCursorChanger(AppWorkspace)) + { + if (result == DialogResult.OK) + { + if (ha != null) + { + AppWorkspace.ActiveDocumentWorkspace.History.PushNewMemento(ha); + } + + AppWorkspace.Update(); + returnVal = true; + } + else + { + Utility.GCFullCollect(); + } + } + } // using + } // using + } + + finally + { + AppWorkspace.ActiveDocumentWorkspace.EnableOutlineAnimation = true; + + if (resetDirtyValue) + { + AppWorkspace.ActiveDocumentWorkspace.Document.Dirty = oldDirtyValue; + } + } + + AppWorkspace.Widgets.StatusBarProgress.EraseProgressStatusBarAsync(); + return returnVal; + } + } +} diff --git a/src/Menus/EffectsMenu.cs b/src/Menus/EffectsMenu.cs new file mode 100644 index 0000000..c1d0bd1 --- /dev/null +++ b/src/Menus/EffectsMenu.cs @@ -0,0 +1,51 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Effects; +using System; +using System.Collections.Generic; + +namespace PaintDotNet.Menus +{ + internal sealed class EffectsMenu + : EffectMenuBase + { + public EffectsMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.Name = "Menu.Effects"; + this.Text = PdnResources.GetString("Menu.Effects.Text"); + } + + protected override bool EnableEffectShortcuts + { + get + { + return false; + } + } + + protected override bool EnableRepeatEffectMenuItem + { + get + { + return true; + } + } + + protected override bool FilterEffects(Effect effect) + { + return (effect.Category == EffectCategory.Effect); + } + } +} diff --git a/src/Menus/FileMenu.cs b/src/Menus/FileMenu.cs new file mode 100644 index 0000000..584fd93 --- /dev/null +++ b/src/Menus/FileMenu.cs @@ -0,0 +1,515 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class FileMenu + : PdnMenuItem + { + private PdnMenuItem menuFileNew; + private PdnMenuItem menuFileOpen; + private PdnMenuItem menuFileOpenRecent; + private PdnMenuItem menuFileOpenRecentSentinel; + private PdnMenuItem menuFileAcquire; + private PdnMenuItem menuFileAcquireFromScannerOrCamera; + private PdnMenuItem menuFileClose; + private ToolStripSeparator menuFileSeparator1; + private PdnMenuItem menuFileSave; + private PdnMenuItem menuFileSaveAs; + private ToolStripSeparator menuFileSeparator2; + private PdnMenuItem menuFilePrint; + private ToolStripSeparator menuFileSeparator3; + private PdnMenuItem menuFileViewPluginLoadErrors; + private ToolStripSeparator menuFileSeparator4; + private PdnMenuItem menuFileExit; + + private bool OnCtrlF4Typed(Keys keys) + { + this.menuFileClose.PerformClick(); + return true; + } + + public FileMenu() + { + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.F4, OnCtrlF4Typed); + InitializeComponent(); + } + + private void InitializeComponent() + { + this.menuFileNew = new PdnMenuItem(); + this.menuFileOpen = new PdnMenuItem(); + this.menuFileOpenRecent = new PdnMenuItem(); + this.menuFileOpenRecentSentinel = new PdnMenuItem(); + this.menuFileAcquire = new PdnMenuItem(); + this.menuFileAcquireFromScannerOrCamera = new PdnMenuItem(); + this.menuFileClose = new PdnMenuItem(); + this.menuFileSeparator1 = new ToolStripSeparator(); + this.menuFileSave = new PdnMenuItem(); + this.menuFileSaveAs = new PdnMenuItem(); + this.menuFileSeparator2 = new ToolStripSeparator(); + this.menuFilePrint = new PdnMenuItem(); + this.menuFileSeparator3 = new ToolStripSeparator(); + this.menuFileViewPluginLoadErrors = new PdnMenuItem(); + this.menuFileSeparator4 = new ToolStripSeparator(); + this.menuFileExit = new PdnMenuItem(); + // + // FileMenu + // + this.DropDownItems.AddRange(GetMenuItemsToAdd(true)); + this.Name = "Menu.File"; + this.Text = PdnResources.GetString("Menu.File.Text"); + // + // menuFileNew + // + this.menuFileNew.Name = "New"; + this.menuFileNew.ShortcutKeys = Keys.Control | Keys.N; + this.menuFileNew.Click += new System.EventHandler(this.MenuFileNew_Click); + // + // menuFileOpen + // + this.menuFileOpen.Name = "Open"; + this.menuFileOpen.ShortcutKeys = Keys.Control | Keys.O; + this.menuFileOpen.Click += new System.EventHandler(this.MenuFileOpen_Click); + // + // menuFileOpenRecent + // + this.menuFileOpenRecent.Name = "OpenRecent"; + this.menuFileOpenRecent.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuFileOpenRecentSentinel + }); + this.menuFileOpenRecent.DropDownOpening += new System.EventHandler(this.MenuFileOpenRecent_DropDownOpening); + // + // menuFileOpenRecentSentinel + // + this.menuFileOpenRecentSentinel.Text = "sentinel"; + // + // menuFileAcquire + // + this.menuFileAcquire.Name = "Acquire"; + this.menuFileAcquire.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuFileAcquireFromScannerOrCamera + }); + this.menuFileAcquire.DropDownOpening += new System.EventHandler(this.MenuFileAcquire_DropDownOpening); + // + // menuFileAcquireFromScannerOrCamera + // + this.menuFileAcquireFromScannerOrCamera.Name = "FromScannerOrCamera"; + this.menuFileAcquireFromScannerOrCamera.Click += new System.EventHandler(this.MenuFileAcquireFromScannerOrCamera_Click); + // + // menuFileClose + // + this.menuFileClose.Name = "Close"; + this.menuFileClose.Click += new EventHandler(MenuFileClose_Click); + this.menuFileClose.ShortcutKeys = Keys.Control | Keys.W; + // + // menuFileSave + // + this.menuFileSave.Name = "Save"; + this.menuFileSave.ShortcutKeys = Keys.Control | Keys.S; + this.menuFileSave.Click += new System.EventHandler(this.MenuFileSave_Click); + // + // menuFileSaveAs + // + this.menuFileSaveAs.Name = "SaveAs"; + this.menuFileSaveAs.ShortcutKeys = Keys.Control | Keys.Shift | Keys.S; + this.menuFileSaveAs.Click += new System.EventHandler(this.MenuFileSaveAs_Click); + // + // menuFilePrint + // + this.menuFilePrint.Name = "Print"; + this.menuFilePrint.ShortcutKeys = Keys.Control | Keys.P; + this.menuFilePrint.Click += new System.EventHandler(this.MenuFilePrint_Click); + // + // menuFileViewPluginLoadErrors + // + this.menuFileViewPluginLoadErrors.Name = "ViewPluginLoadErrors"; + this.menuFileViewPluginLoadErrors.Click += new EventHandler(MenuFileViewPluginLoadErrors_Click); + // + // menuFileExit + // + this.menuFileExit.Name = "Exit"; + this.menuFileExit.Click += new System.EventHandler(this.MenuFileExit_Click); + } + + private ToolStripItem[] GetMenuItemsToAdd(bool includeLoadErrors) + { + List items = new List(); + + items.Add(this.menuFileNew); + items.Add(this.menuFileOpen); + items.Add(this.menuFileOpenRecent); + items.Add(this.menuFileAcquire); + items.Add(this.menuFileClose); + items.Add(this.menuFileSeparator1); + items.Add(this.menuFileSave); + items.Add(this.menuFileSaveAs); + items.Add(this.menuFileSeparator2); + items.Add(this.menuFilePrint); + items.Add(this.menuFileSeparator3); + + if (includeLoadErrors) + { + items.Add(this.menuFileViewPluginLoadErrors); + items.Add(this.menuFileSeparator4); + } + + items.Add(this.menuFileExit); + + return items.ToArray(); + } + + private List> RemoveDuplicates(IList> allErrors) + { + // Exception has reference identity, but we want to collate based on the message contents + + Set> internedList = new Set>(); + List> noDupesList = new List>(); + + for (int i = 0; i < allErrors.Count; ++i) + { + Triple interned = Triple.Create( + allErrors[i].First, allErrors[i].Second, string.Intern(allErrors[i].Third.ToString())); + + if (!internedList.Contains(interned)) + { + internedList.Add(interned); + noDupesList.Add(allErrors[i]); + } + } + + return noDupesList; + } + + private void MenuFileViewPluginLoadErrors_Click(object sender, EventArgs e) + { + IList> allErrors = AppWorkspace.GetEffectLoadErrors(); + IList> errors = RemoveDuplicates(allErrors); + + using (Form errorsDialog = new Form()) + { + errorsDialog.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileViewPluginLoadErrorsIcon.png").Reference); + errorsDialog.Text = PdnResources.GetString("Effects.PluginLoadErrorsDialog.Text"); + + Label messageLabel = new Label(); + messageLabel.Name = "messageLabel"; + messageLabel.Text = PdnResources.GetString("Effects.PluginLoadErrorsDialog.Message.Text"); + + TextBox errorsBox = new TextBox(); + errorsBox.Font = new Font(FontFamily.GenericMonospace, errorsBox.Font.Size); + errorsBox.ReadOnly = true; + errorsBox.Multiline = true; + errorsBox.ScrollBars = ScrollBars.Vertical; + + StringBuilder allErrorsText = new StringBuilder(); + string headerTextFormat = PdnResources.GetString("EffectErrorMessage.HeaderFormat"); + + for (int i = 0; i < errors.Count; ++i) + { + Assembly assembly = errors[i].First; + Type type = errors[i].Second; + Exception exception = errors[i].Third; + + string headerText = string.Format(headerTextFormat, i + 1, errors.Count); + string errorText = AppWorkspace.GetLocalizedEffectErrorMessage(assembly, type, exception); + + allErrorsText.Append(headerText); + allErrorsText.Append(Environment.NewLine); + allErrorsText.Append(errorText); + + if (i != errors.Count - 1) + { + allErrorsText.Append(Environment.NewLine); + } + } + + errorsBox.Text = allErrorsText.ToString(); + + errorsDialog.Layout += + delegate(object sender2, LayoutEventArgs e2) + { + int hMargin = UI.ScaleWidth(8); + int vMargin = UI.ScaleHeight(8); + int insetWidth = errorsDialog.ClientSize.Width - (hMargin * 2); + + messageLabel.Location = new Point(hMargin, vMargin); + messageLabel.Width = insetWidth; + messageLabel.Size = messageLabel.GetPreferredSize(new Size(messageLabel.Width, 1)); + + errorsBox.Location = new Point(hMargin, messageLabel.Bottom + vMargin); + errorsBox.Width = insetWidth; + errorsBox.Height = errorsDialog.ClientSize.Height - vMargin - errorsBox.Top; + }; + + errorsDialog.StartPosition = FormStartPosition.CenterParent; + errorsDialog.ShowInTaskbar = false; + errorsDialog.MinimizeBox = false; + errorsDialog.Width *= 2; + errorsDialog.Size = UI.ScaleSize(errorsDialog.Size); + errorsDialog.Controls.Add(messageLabel); + errorsDialog.Controls.Add(errorsBox); + + errorsDialog.ShowDialog(AppWorkspace); + } + } + + protected override void OnDropDownOpening(EventArgs e) + { + this.DropDownItems.Clear(); + + IList> pluginLoadErrors = AppWorkspace.GetEffectLoadErrors(); + + this.DropDownItems.AddRange(GetMenuItemsToAdd(pluginLoadErrors.Count > 0)); + + this.menuFileNew.Enabled = true; + this.menuFileOpen.Enabled = true; + this.menuFileOpenRecent.Enabled = true; + this.menuFileOpenRecentSentinel.Enabled = true; + this.menuFileAcquire.Enabled = true; + this.menuFileAcquireFromScannerOrCamera.Enabled = true; + this.menuFileExit.Enabled = true; + + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + this.menuFileSave.Enabled = true; + this.menuFileSaveAs.Enabled = true; + this.menuFileClose.Enabled = true; + this.menuFilePrint.Enabled = true; + } + else + { + this.menuFileSave.Enabled = false; + this.menuFileSaveAs.Enabled = false; + this.menuFileClose.Enabled = false; + this.menuFilePrint.Enabled = false; + } + + base.OnDropDownOpening(e); + } + + private void MenuFileOpen_Click(object sender, System.EventArgs e) + { + AppWorkspace.PerformAction(new OpenFileAction()); + } + + private void DoExit() + { + Startup.CloseApplication(); + } + + private void MenuFileExit_Click(object sender, System.EventArgs e) + { + DoExit(); + } + + private void MenuFileClose_Click(object sender, EventArgs e) + { + if (this.AppWorkspace.DocumentWorkspaces.Length > 0) + { + this.AppWorkspace.PerformAction(new CloseWorkspaceAction()); + } + else + { + DoExit(); + } + } + + private void MenuFileSaveAs_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.DoSaveAs(); + } + } + + private void MenuFileSave_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.DoSave(); + } + } + + private void MenuFileAcquire_DropDownOpening(object sender, System.EventArgs e) + { + // We only disable the scanner menu item if we know for sure a scanner is not available + // If WIA isn't available we leave the menu item enabled. That way we can give an + // informative error message when the user clicks on it and say "scanning requires XP SP1" + // Otherwise the user is confused and will make scathing posts on our forum. + bool scannerEnabled = true; + + if (ScanningAndPrinting.IsComponentAvailable) + { + if (!ScanningAndPrinting.CanScan) + { + scannerEnabled = false; + } + } + + menuFileAcquireFromScannerOrCamera.Enabled = scannerEnabled; + } + + private void MenuFilePrint_Click(object sender, System.EventArgs e) + { + if (this.AppWorkspace.ActiveDocumentWorkspace != null) + { + this.AppWorkspace.ActiveDocumentWorkspace.PerformAction(new PrintAction()); + } + } + + private void MenuFileOpenInNewWindow_Click(object sender, System.EventArgs e) + { + string fileName; + string startingDir = Path.GetDirectoryName(AppWorkspace.ActiveDocumentWorkspace.FilePath); + DialogResult result = DocumentWorkspace.ChooseFile(AppWorkspace, out fileName, startingDir); + + if (result == DialogResult.OK) + { + Startup.StartNewInstance(AppWorkspace, fileName); + } + } + + private void MenuFileNewWindow_Click(object sender, System.EventArgs e) + { + Startup.StartNewInstance(AppWorkspace, null); + } + + private void MenuFileOpenRecent_DropDownOpening(object sender, System.EventArgs e) + { + AppWorkspace.MostRecentFiles.LoadMruList(); + MostRecentFile[] filesReverse = AppWorkspace.MostRecentFiles.GetFileList(); + MostRecentFile[] files = new MostRecentFile[filesReverse.Length]; + int i; + + for (i = 0; i < filesReverse.Length; ++i) + { + files[files.Length - i - 1] = filesReverse[i]; + } + + foreach (ToolStripItem mi in menuFileOpenRecent.DropDownItems) + { + mi.Click -= new EventHandler(MenuFileOpenRecentFile_Click); + } + + menuFileOpenRecent.DropDownItems.Clear(); + + i = 0; + + foreach (MostRecentFile mrf in files) + { + string menuName; + + if (i < 9) + { + menuName = "&"; + } + else + { + menuName = ""; + } + + menuName += (1 + i).ToString() + " " + Path.GetFileName(mrf.FileName); + ToolStripMenuItem mi = new ToolStripMenuItem(menuName); + mi.Click += new EventHandler(MenuFileOpenRecentFile_Click); + mi.ImageScaling = ToolStripItemImageScaling.None; + mi.Image = (Image)mrf.Thumb.Clone(); + menuFileOpenRecent.DropDownItems.Add(mi); + ++i; + } + + if (menuFileOpenRecent.DropDownItems.Count == 0) + { + ToolStripMenuItem none = new ToolStripMenuItem(PdnResources.GetString("Menu.File.OpenRecent.None")); + none.Enabled = false; + menuFileOpenRecent.DropDownItems.Add(none); + } + else + { + ToolStripSeparator separator = new ToolStripSeparator(); + menuFileOpenRecent.DropDownItems.Add(separator); + + ToolStripMenuItem clearList = new ToolStripMenuItem(); + clearList.Text = PdnResources.GetString("Menu.File.OpenRecent.ClearThisList"); + menuFileOpenRecent.DropDownItems.Add(clearList); + Image deleteIcon = PdnResources.GetImageResource("Icons.MenuEditEraseSelectionIcon.png").Reference; + clearList.ImageTransparentColor = Utility.TransparentKey; + clearList.ImageAlign = ContentAlignment.MiddleCenter; + clearList.ImageScaling = ToolStripItemImageScaling.None; + int iconSize = AppWorkspace.MostRecentFiles.IconSize; + Bitmap bitmap = new Bitmap(iconSize + 2, iconSize + 2); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.Clear(clearList.ImageTransparentColor); + + Point offset = new Point((bitmap.Width - deleteIcon.Width) / 2, + (bitmap.Height - deleteIcon.Height) / 2); + + g.CompositingMode = CompositingMode.SourceCopy; + g.DrawImage(deleteIcon, offset.X, offset.Y, deleteIcon.Width, deleteIcon.Height); + } + + clearList.Image = bitmap; + clearList.Click += new EventHandler(ClearList_Click); + } + } + + private void MenuFileOpenRecentFile_Click(object sender, System.EventArgs e) + { + try + { + ToolStripMenuItem mi = (ToolStripMenuItem)sender; + int spaceIndex = mi.Text.IndexOf(" "); + string indexString = mi.Text.Substring(1, spaceIndex - 1); + int index = int.Parse(indexString) - 1; + MostRecentFile[] recentFiles = AppWorkspace.MostRecentFiles.GetFileList(); + string fileName = recentFiles[recentFiles.Length - index - 1].FileName; + AppWorkspace.OpenFileInNewWorkspace(fileName); + } + + catch (Exception) + { + } + } + + private void MenuFileNew_Click(object sender, System.EventArgs e) + { + AppWorkspace.PerformAction(new NewImageAction()); + } + + private void MenuFileAcquireFromScannerOrCamera_Click(object sender, System.EventArgs e) + { + AppWorkspace.PerformAction(new AcquireFromScannerOrCameraAction()); + } + + private void ClearList_Click(object sender, EventArgs e) + { + AppWorkspace.PerformAction(new ClearMruListAction()); + } + } +} diff --git a/src/Menus/HelpMenu.cs b/src/Menus/HelpMenu.cs new file mode 100644 index 0000000..6568ded --- /dev/null +++ b/src/Menus/HelpMenu.cs @@ -0,0 +1,382 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +//#define CRASH + +using PaintDotNet.Actions; +using PaintDotNet.SystemLayer; +using PaintDotNet.Updates; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class HelpMenu + : PdnMenuItem + { + private PdnMenuItem menuHelpHelpTopics; + private ToolStripSeparator menuHelpSeparator1; + private PdnMenuItem menuHelpPdnWebsite; + private PdnMenuItem menuHelpPdnSearch; + private PdnMenuItem menuHelpDonate; + private PdnMenuItem menuHelpForum; + private PdnMenuItem menuHelpTutorials; + private PdnMenuItem menuHelpPlugins; + private PdnMenuItem menuHelpSendFeedback; + private ToolStripSeparator menuHelpSeparator2; + private PdnMenuItem menuHelpLanguage; + private PdnMenuItem menuHelpLanguageSentinel; + private CheckForUpdatesMenuItem menuHelpCheckForUpdates; + private ToolStripSeparator menuHelpSeparator3; +#if CRASH + private PdnMenuItem menuHelpCrash; +#endif + private PdnMenuItem menuHelpAbout; + + public void CheckForUpdates() + { + this.menuHelpCheckForUpdates.PerformClick(); + } + + public HelpMenu() + { +#if CRASH + if (PdnInfo.IsFinalBuild) + { + throw new Exception("Do not leave CRASH defined for an actual release build!"); + } +#endif + + InitializeComponent(); + } + + protected override void OnAppWorkspaceChanged() + { + this.menuHelpCheckForUpdates.AppWorkspace = this.AppWorkspace; + base.OnAppWorkspaceChanged(); + } + + private void InitializeComponent() + { + this.menuHelpHelpTopics = new PdnMenuItem(); + this.menuHelpSeparator1 = new ToolStripSeparator(); + this.menuHelpPdnWebsite = new PdnMenuItem(); + this.menuHelpPdnSearch = new PdnMenuItem(); + this.menuHelpDonate = new PdnMenuItem(); + this.menuHelpForum = new PdnMenuItem(); + this.menuHelpTutorials = new PdnMenuItem(); + this.menuHelpPlugins = new PdnMenuItem(); + this.menuHelpSendFeedback = new PdnMenuItem(); + this.menuHelpSeparator2 = new ToolStripSeparator(); + this.menuHelpLanguage = new PdnMenuItem(); + this.menuHelpLanguageSentinel = new PdnMenuItem(); + this.menuHelpCheckForUpdates = new CheckForUpdatesMenuItem(); + this.menuHelpSeparator3 = new ToolStripSeparator(); +#if CRASH + this.menuHelpCrash = new PdnMenuItem(); +#endif + this.menuHelpAbout = new PdnMenuItem(); + // + // HelpMenu + // + this.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuHelpHelpTopics, + this.menuHelpSeparator1, + this.menuHelpPdnWebsite, + this.menuHelpPdnSearch, + this.menuHelpDonate, + this.menuHelpForum, + this.menuHelpTutorials, + this.menuHelpPlugins, + this.menuHelpSendFeedback, + this.menuHelpSeparator2, + this.menuHelpLanguage, + this.menuHelpCheckForUpdates, + this.menuHelpSeparator3, +#if CRASH + this.menuHelpCrash, +#endif + this.menuHelpAbout + }); + this.Name = "Menu.Help"; + this.Text = PdnResources.GetString("Menu.Help.Text"); + // + // menuHelpHelpTopics + // + this.menuHelpHelpTopics.Name = "HelpTopics"; + this.menuHelpHelpTopics.ShortcutKeys = Keys.F1; + this.menuHelpHelpTopics.Click += new System.EventHandler(this.MenuHelpHelpTopics_Click); + // + // menuHelpPdnWebsite + // + this.menuHelpPdnWebsite.Name = "PdnWebsite"; + this.menuHelpPdnWebsite.Click += new EventHandler(MenuHelpPdnWebsite_Click); + // + // menuHelpPdnSearch + // + this.menuHelpPdnSearch.Name = "PdnSearch"; + this.menuHelpPdnSearch.Click += new EventHandler(MenuHelpPdnSearchEngine_Click); + this.menuHelpPdnSearch.ShortcutKeys = Keys.Control | Keys.E; + // + // menuHelpDonate + // + this.menuHelpDonate.Name = "Donate"; + this.menuHelpDonate.Click += new EventHandler(MenuHelpDonate_Click); + this.menuHelpDonate.Font = Utility.CreateFont(this.menuHelpDonate.Font.Name, this.menuHelpDonate.Font.Size, this.menuHelpDonate.Font.Style | FontStyle.Italic); + // + // menuHelpForum + // + this.menuHelpForum.Name = "Forum"; + this.menuHelpForum.Click += new EventHandler(MenuHelpForum_Click); + // + // menuHelpTutorials + // + this.menuHelpTutorials.Name = "Tutorials"; + this.menuHelpTutorials.Click += new EventHandler(MenuHelpTutorials_Click); + // + // menuHelpPlugins + // + this.menuHelpPlugins.Name = "Plugins"; + this.menuHelpPlugins.Click += new EventHandler(MenuHelpPlugins_Click); + // + // menuHelpSendFeedback + // + this.menuHelpSendFeedback.Name = "SendFeedback"; + this.menuHelpSendFeedback.Click += new EventHandler(MenuHelpSendFeedback_Click); + // + // menuHelpLanguage + // + this.menuHelpLanguage.Name = "Language"; + this.menuHelpLanguage.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuHelpLanguageSentinel + }); + this.menuHelpLanguage.DropDownOpening += new EventHandler(MenuHelpLanguage_DropDownOpening); + // + // menuHelpLanguageSentinel + // + this.menuHelpLanguageSentinel.Text = "(sentinel)"; + // + // menuHelpCheckForUpdates + // + // (left blank on purpose) + +#if CRASH + // + // menuHelpCrash + this.menuHelpCrash.Name = "Crash"; + this.menuHelpCrash.Text = "Crash!"; + this.menuHelpCrash.ShortcutKeys = Keys.Control | Keys.Alt | Keys.X; + this.menuHelpCrash.Click += delegate { ((Control)null).Dispose(); }; +#endif + + // + // menuHelpAbout + // + this.menuHelpAbout.Name = "About"; + this.menuHelpAbout.Click += new System.EventHandler(this.MenuHelpAbout_Click); + } + + private void MenuHelpPdnSearchEngine_Click(object sender, EventArgs e) + { + PdnInfo.LaunchWebSite(AppWorkspace, InvariantStrings.SearchEngineHelpMenu); + } + + private void MenuHelpDonate_Click(object sender, EventArgs e) + { + PdnInfo.LaunchWebSite(AppWorkspace, InvariantStrings.DonatePageHelpMenu); + } + + private void MenuHelpPdnWebsite_Click(object sender, EventArgs e) + { + PdnInfo.LaunchWebSite(AppWorkspace, InvariantStrings.WebsitePageHelpMenu); + } + + private void MenuHelpForum_Click(object sender, EventArgs e) + { + PdnInfo.LaunchWebSite(AppWorkspace, InvariantStrings.ForumPageHelpPage); + } + + private void MenuHelpTutorials_Click(object sender, EventArgs e) + { + PdnInfo.LaunchWebSite(AppWorkspace, InvariantStrings.TutorialsPageHelpPage); + } + + private void MenuHelpPlugins_Click(object sender, EventArgs e) + { + PdnInfo.LaunchWebSite(AppWorkspace, InvariantStrings.PluginsPageHelpPage); + } + + private void MenuHelpAbout_Click(object sender, System.EventArgs e) + { + using (AboutDialog af = new AboutDialog()) + { + af.ShowDialog(AppWorkspace); + } + } + + private void MenuHelpHelpTopics_Click(object sender, System.EventArgs e) + { + Utility.ShowHelp(AppWorkspace); + } + + private class MenuTitleAndLocale + { + public string title; + public string locale; + + public MenuTitleAndLocale(string title, string locale) + { + this.title = title; + this.locale = locale; + } + } + + private string GetCultureInfoName(CultureInfo ci) + { + CultureInfo en_US = new CultureInfo("en-US"); + + // For "English (United States)" we'd rather just display "English" + if (ci.Equals(en_US)) + { + return GetCultureInfoName(ci.Parent); + } + else + { + return ci.NativeName; + } + } + + private void MenuHelpLanguage_DropDownOpening(object sender, EventArgs e) + { + this.menuHelpLanguage.DropDownItems.Clear(); + + string[] locales = PdnResources.GetInstalledLocales(); + + MenuTitleAndLocale[] mtals = new MenuTitleAndLocale[locales.Length]; + + for (int i = 0; i < locales.Length; ++i) + { + string locale = locales[i]; + CultureInfo ci = new CultureInfo(locale); + mtals[i] = new MenuTitleAndLocale(ci.DisplayName, locale); + } + + Array.Sort( + mtals, + delegate(MenuTitleAndLocale x, MenuTitleAndLocale y) + { + return string.Compare(x.title, y.title, StringComparison.InvariantCultureIgnoreCase); + }); + + foreach (MenuTitleAndLocale mtal in mtals) + { + ToolStripMenuItem menuItem = new ToolStripMenuItem(); + menuItem.Text = GetCultureInfoName(new CultureInfo(mtal.locale)); + menuItem.Tag = mtal.locale; + menuItem.Click += new EventHandler(LanguageMenuItem_Click); + + if (0 == string.Compare(mtal.locale, CultureInfo.CurrentUICulture.Name, StringComparison.InvariantCultureIgnoreCase)) + { + menuItem.Checked = true; + } + + this.menuHelpLanguage.DropDownItems.Add(menuItem); + } + } + + private void LanguageMenuItem_Click(object sender, EventArgs e) + { + // Save off the old locale name in case they decide to cancel + string oldLocaleName = PdnResources.Culture.Name; + + // Now, apply the chosen language so that the confirmation buttons show up in the right language + ToolStripMenuItem miwt = (ToolStripMenuItem)sender; + string newLocaleName = (string)miwt.Tag; + PdnResources.SetNewCulture(newLocaleName); + + // Load the text and buttons in the new language + Icon formIcon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuHelpLanguageIcon.png").Reference); + string title = PdnResources.GetString("ConfirmLanguageDialog.Title"); + Image taskImage = null; + string introText = PdnResources.GetString("ConfirmLanguageDialog.IntroText"); + + Image restartImage = PdnResources.GetImageResource("Icons.RightArrowBlue.png").Reference; + string explanationTextFormat = PdnResources.GetString("ConfirmLanguageDialog.RestartTB.ExplanationText.Format"); + CultureInfo newCI = new CultureInfo(newLocaleName); + + // We prefer to show "English (United States)" as just "English" + CultureInfo en_US = new CultureInfo("en-US"); + + if (newCI.Equals(en_US)) + { + newCI = newCI.Parent; + } + + string languageName = newCI.NativeName; + string explanationText = string.Format(explanationTextFormat, languageName); + + TaskButton restartTB = new TaskButton( + restartImage, + PdnResources.GetString("ConfirmLanguageDialog.RestartTB.ActionText"), + explanationText); + + Image cancelImage = PdnResources.GetImageResource("Icons.CancelIcon.png").Reference; + TaskButton cancelTB = new TaskButton( + cancelImage, + PdnResources.GetString("ConfirmLanguageDialog.CancelTB.ActionText"), + PdnResources.GetString("ConfirmLanguageDialog.CancelTB.ExplanationText")); + + int width96dpi = (TaskDialog.DefaultPixelWidth96Dpi * 5) / 4; + + TaskButton clickedTB = TaskDialog.Show( + AppWorkspace, + formIcon, + title, + taskImage, + true, + introText, + new TaskButton[] { restartTB, cancelTB }, + restartTB, + cancelTB, + width96dpi); + + if (clickedTB == restartTB) + { + // Next, apply restart logic + CloseAllWorkspacesAction cawa = new CloseAllWorkspacesAction(); + cawa.PerformAction(AppWorkspace); + + if (!cawa.Cancelled) + { + SystemLayer.Shell.RestartApplication(); + Startup.CloseApplication(); + } + } + else + { + // Revert to the old language + PdnResources.SetNewCulture(oldLocaleName); + } + } + + private void MenuHelpSendFeedback_Click(object sender, EventArgs e) + { + AppWorkspace.PerformAction(new SendFeedbackAction()); + } + } +} diff --git a/src/Menus/ImageMenu.cs b/src/Menus/ImageMenu.cs new file mode 100644 index 0000000..3b2f004 --- /dev/null +++ b/src/Menus/ImageMenu.cs @@ -0,0 +1,238 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryFunctions; +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class ImageMenu + : PdnMenuItem + { + private PdnMenuItem menuImageCrop; + private PdnMenuItem menuImageResize; + private PdnMenuItem menuImageCanvasSize; + private ToolStripSeparator menuImageSeparator1; + private PdnMenuItem menuImageFlipHorizontal; + private PdnMenuItem menuImageFlipVertical; + private ToolStripSeparator menuImageSeparator2; + private PdnMenuItem menuImageRotate90CW; + private PdnMenuItem menuImageRotate90CCW; + private PdnMenuItem menuImageRotate180; + private ToolStripSeparator menuImageSeparator3; + private PdnMenuItem menuImageFlatten; + + public ImageMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.menuImageCrop = new PdnMenuItem(); + this.menuImageResize = new PdnMenuItem(); + this.menuImageCanvasSize = new PdnMenuItem(); + this.menuImageSeparator1 = new ToolStripSeparator(); + this.menuImageFlipHorizontal = new PdnMenuItem(); + this.menuImageFlipVertical = new PdnMenuItem(); + this.menuImageSeparator2 = new ToolStripSeparator(); + this.menuImageRotate90CW = new PdnMenuItem(); + this.menuImageRotate90CCW = new PdnMenuItem(); + this.menuImageRotate180 = new PdnMenuItem(); + this.menuImageSeparator3 = new ToolStripSeparator(); + this.menuImageFlatten = new PdnMenuItem(); + // + // ImageMenu + // + this.DropDownItems.AddRange( + new System.Windows.Forms.ToolStripItem[] + { + this.menuImageCrop, + this.menuImageResize, + this.menuImageCanvasSize, + this.menuImageSeparator1, + this.menuImageFlipHorizontal, + this.menuImageFlipVertical, + this.menuImageSeparator2, + this.menuImageRotate90CW, + this.menuImageRotate90CCW, + this.menuImageRotate180, + this.menuImageSeparator3, + this.menuImageFlatten + }); + this.Name = "Menu.Image"; + this.Text = PdnResources.GetString("Menu.Image.Text"); + // + // menuImageCrop + // + this.menuImageCrop.Name = "Crop"; + this.menuImageCrop.Click += new System.EventHandler(this.MenuImageCrop_Click); + this.menuImageCrop.ShortcutKeys = Keys.Control | Keys.Shift | Keys.X; + // + // menuImageResize + // + this.menuImageResize.Name = "Resize"; + this.menuImageResize.ShortcutKeys = Keys.Control | Keys.R; + this.menuImageResize.Click += new System.EventHandler(this.MenuImageResize_Click); + // + // menuImageCanvasSize + // + this.menuImageCanvasSize.Name = "CanvasSize"; + this.menuImageCanvasSize.ShortcutKeys = Keys.Control | Keys.Shift | Keys.R; + this.menuImageCanvasSize.Click += new System.EventHandler(this.MenuImageCanvasSize_Click); + // + // menuImageFlipHorizontal + // + this.menuImageFlipHorizontal.Name = "FlipHorizontal"; + this.menuImageFlipHorizontal.Click += new System.EventHandler(this.MenuImageFlipHorizontal_Click); + // + // menuImageFlipVertical + // + this.menuImageFlipVertical.Name = "FlipVertical"; + this.menuImageFlipVertical.Click += new System.EventHandler(this.MenuImageFlipVertical_Click); + // + // menuImageRotate90CW + // + this.menuImageRotate90CW.Name = "Rotate90CW"; + this.menuImageRotate90CW.ShortcutKeys = Keys.Control | Keys.H; + this.menuImageRotate90CW.Click += new System.EventHandler(this.MenuImageRotate90CW_Click); + // + // menuImageRotate90CCW + // + this.menuImageRotate90CCW.Name = "Rotate90CCW"; + this.menuImageRotate90CCW.ShortcutKeys = Keys.Control | Keys.G; + this.menuImageRotate90CCW.Click += new System.EventHandler(this.MenuImageRotate90CCW_Click); + // + // menuImageRotate180 + // + this.menuImageRotate180.Name = "Rotate180"; + this.menuImageRotate180.Click += new System.EventHandler(this.MenuImageRotate180_Click); + this.menuImageRotate180.ShortcutKeys = Keys.Control | Keys.J; + // + // menuImageFlatten + // + this.menuImageFlatten.Name = "Flatten"; + this.menuImageFlatten.ShortcutKeys = Keys.Control | Keys.Shift | Keys.F; + this.menuImageFlatten.Click += new System.EventHandler(this.MenuImageFlatten_Click); + } + + protected override void OnDropDownOpening(EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace == null) + { + this.menuImageCrop.Enabled = false; + this.menuImageResize.Enabled = false; + this.menuImageCanvasSize.Enabled = false; + this.menuImageFlipHorizontal.Enabled = false; + this.menuImageFlipVertical.Enabled = false; + this.menuImageRotate90CW.Enabled = false; + this.menuImageRotate90CCW.Enabled = false; + this.menuImageRotate180.Enabled = false; + this.menuImageFlatten.Enabled = false; + } + else + { + this.menuImageCrop.Enabled = !AppWorkspace.ActiveDocumentWorkspace.Selection.IsEmpty; + this.menuImageResize.Enabled = true; + this.menuImageCanvasSize.Enabled = true; + this.menuImageFlipHorizontal.Enabled = true; + this.menuImageFlipVertical.Enabled = true; + this.menuImageRotate90CW.Enabled = true; + this.menuImageRotate90CCW.Enabled = true; + this.menuImageRotate180.Enabled = true; + this.menuImageFlatten.Enabled = (AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count > 1); + } + + base.OnDropDownOpening(e); + } + + private void MenuImageCrop_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + if (!AppWorkspace.ActiveDocumentWorkspace.Selection.IsEmpty) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new CropToSelectionFunction()); + } + } + } + + private void MenuImageResize_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new ResizeAction()); + } + } + + private void MenuImageCanvasSize_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new CanvasSizeAction()); + } + } + + private void MenuImageFlipHorizontal_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new FlipDocumentHorizontalFunction()); + } + } + + private void MenuImageFlipVertical_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new FlipDocumentVerticalFunction()); + } + } + + private void MenuImageRotate90CW_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + HistoryFunction da = new RotateDocumentFunction(RotateType.Clockwise90); + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(da); + } + } + + private void MenuImageRotate90CCW_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + HistoryFunction da = new RotateDocumentFunction(RotateType.CounterClockwise90); + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(da); + } + } + + private void MenuImageRotate180_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + HistoryFunction da = new RotateDocumentFunction(RotateType.Rotate180); + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(da); + } + } + + private void MenuImageFlatten_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + if (AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count > 1) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new FlattenFunction()); + } + } + } + } +} diff --git a/src/Menus/LayersMenu.cs b/src/Menus/LayersMenu.cs new file mode 100644 index 0000000..6fb151d --- /dev/null +++ b/src/Menus/LayersMenu.cs @@ -0,0 +1,246 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.Effects; +using PaintDotNet.HistoryFunctions; +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class LayersMenu + : PdnMenuItem + { + private PdnMenuItem menuLayersAddNewLayer; + private PdnMenuItem menuLayersDeleteLayer; + private PdnMenuItem menuLayersDuplicateLayer; + private PdnMenuItem menuLayersMergeLayerDown; + private PdnMenuItem menuLayersImportFromFile; + private ToolStripSeparator menuLayersSeparator1; + private PdnMenuItem menuLayersFlipHorizontal; + private PdnMenuItem menuLayersFlipVertical; + private PdnMenuItem menuLayersRotateZoom; + private ToolStripSeparator menuLayersSeparator2; + private PdnMenuItem menuLayersLayerProperties; + + public LayersMenu() + { + InitializeComponent(); + + // Fill in Rotate/Zoom menu item + string rzName = RotateZoomEffect.StaticName; + Keys rzShortcut = RotateZoomEffect.StaticShortcutKeys; + ImageResource rzImage = RotateZoomEffect.StaticImage; + string rzNameFormatString = PdnResources.GetString("Effects.Name.Format.Configurable"); + string rzMenuName = string.Format(rzNameFormatString, rzName); + + this.menuLayersRotateZoom.Text = rzMenuName; + this.menuLayersRotateZoom.SetIcon(rzImage); + this.menuLayersRotateZoom.ShortcutKeys = rzShortcut; + } + + private void InitializeComponent() + { + this.menuLayersAddNewLayer = new PdnMenuItem(); + this.menuLayersDeleteLayer = new PdnMenuItem(); + this.menuLayersDuplicateLayer = new PdnMenuItem(); + this.menuLayersMergeLayerDown = new PdnMenuItem(); + this.menuLayersImportFromFile = new PdnMenuItem(); + this.menuLayersSeparator1 = new ToolStripSeparator(); + this.menuLayersFlipHorizontal = new PdnMenuItem(); + this.menuLayersFlipVertical = new PdnMenuItem(); + this.menuLayersRotateZoom = new PdnMenuItem(); + this.menuLayersSeparator2 = new ToolStripSeparator(); + this.menuLayersLayerProperties = new PdnMenuItem(); + // + // LayersMenu + // + this.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuLayersAddNewLayer, + this.menuLayersDeleteLayer, + this.menuLayersDuplicateLayer, + this.menuLayersMergeLayerDown, + this.menuLayersImportFromFile, + this.menuLayersSeparator1, + this.menuLayersFlipHorizontal, + this.menuLayersFlipVertical, + this.menuLayersRotateZoom, + this.menuLayersSeparator2, + this.menuLayersLayerProperties + }); + this.Name = "Menu.Layers"; + this.Text = PdnResources.GetString("Menu.Layers.Text"); + // + // menuLayersAddNewLayer + // + this.menuLayersAddNewLayer.Name = "AddNewLayer"; + this.menuLayersAddNewLayer.ShortcutKeys = Keys.Control | Keys.Shift | Keys.N; + this.menuLayersAddNewLayer.Click += new System.EventHandler(this.MenuLayersAddNewLayer_Click); + // + // menuLayersDeleteLayer + // + this.menuLayersDeleteLayer.Name = "DeleteLayer"; + this.menuLayersDeleteLayer.ShortcutKeys = Keys.Control | Keys.Shift | Keys.Delete; + this.menuLayersDeleteLayer.Click += new System.EventHandler(this.MenuLayersDeleteLayer_Click); + // + // menuLayersDuplicateLayer + // + this.menuLayersDuplicateLayer.Name = "DuplicateLayer"; + this.menuLayersDuplicateLayer.ShortcutKeys = Keys.Control | Keys.Shift | Keys.D; + this.menuLayersDuplicateLayer.Click += new System.EventHandler(this.MenuLayersDuplicateLayer_Click); + // + // menuLayersMergeDown + // + this.menuLayersMergeLayerDown.Name = "MergeLayerDown"; + this.menuLayersMergeLayerDown.ShortcutKeys = Keys.Control | Keys.M; + this.menuLayersMergeLayerDown.Click += new EventHandler(MenuLayersMergeDown_Click); + // + // menuLayersImportFromFile + // + this.menuLayersImportFromFile.Name = "ImportFromFile"; + this.menuLayersImportFromFile.Click += new System.EventHandler(this.MenuLayersImportFromFile_Click); + // + // menuLayersFlipHorizontal + // + this.menuLayersFlipHorizontal.Name = "FlipHorizontal"; + this.menuLayersFlipHorizontal.Click += new System.EventHandler(this.MenuLayersFlipHorizontal_Click); + // + // menuLayersFlipVertical + // + this.menuLayersFlipVertical.Name = "FlipVertical"; + this.menuLayersFlipVertical.Click += new System.EventHandler(this.MenuLayersFlipVertical_Click); + // + // menuLayersRotateZoom + // + this.menuLayersRotateZoom.Name = "RotateZoom"; + this.menuLayersRotateZoom.Click += new EventHandler(MenuLayersRotateZoom_Click); + // + // menuLayersLayerProperties + // + this.menuLayersLayerProperties.Name = "LayerProperties"; + this.menuLayersLayerProperties.ShortcutKeys = Keys.F4; + this.menuLayersLayerProperties.Click += new System.EventHandler(this.MenuLayersLayerProperties_Click); + } + + protected override void OnDropDownOpening(EventArgs e) + { + bool enabled = (AppWorkspace.ActiveDocumentWorkspace != null); + + this.menuLayersAddNewLayer.Enabled = enabled; + + if (AppWorkspace.ActiveDocumentWorkspace != null && + AppWorkspace.ActiveDocumentWorkspace.Document != null && + AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count > 1) + { + this.menuLayersDeleteLayer.Enabled = true; + } + else + { + this.menuLayersDeleteLayer.Enabled = false; + } + + this.menuLayersDuplicateLayer.Enabled = enabled; + + bool enableMergeDown = (AppWorkspace.ActiveDocumentWorkspace != null && + AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex > 0); + + this.menuLayersMergeLayerDown.Enabled = enableMergeDown; + + this.menuLayersImportFromFile.Enabled = enabled; + this.menuLayersFlipHorizontal.Enabled = enabled; + this.menuLayersFlipVertical.Enabled = enabled; + this.menuLayersRotateZoom.Enabled = enabled; + this.menuLayersLayerProperties.Enabled = enabled; + + base.OnDropDownOpening(e); + } + + private void MenuLayersAddNewLayer_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction(new AddNewBlankLayerFunction()); + } + } + + private void MenuLayersDuplicateLayer_Click(object sender, System.EventArgs e) + { + AppWorkspace.Widgets.LayerForm.PerformDuplicateLayerClick(); + } + + private void MenuLayersMergeDown_Click(object sender, EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null && + AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex > 0) + { + // TODO: keep this in sync with AppWorkspace. not appropriate to refactor into an Action for a 'dot' release + int newLayerIndex = Utility.Clamp( + AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex - 1, + 0, + AppWorkspace.ActiveDocumentWorkspace.Document.Layers.Count - 1); + + AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction( + new MergeLayerDownFunction(AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex)); + + AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex = newLayerIndex; + } + } + + private void MenuLayersDeleteLayer_Click(object sender, System.EventArgs e) + { + AppWorkspace.Widgets.LayerForm.PerformDeleteLayerClick(); + } + + private void MenuLayersFlipHorizontal_Click(object sender, System.EventArgs e) + { + if (this.AppWorkspace.ActiveDocumentWorkspace != null) + { + this.AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction( + new FlipLayerHorizontalFunction(this.AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex)); + } + } + + private void MenuLayersFlipVertical_Click(object sender, System.EventArgs e) + { + if (this.AppWorkspace.ActiveDocumentWorkspace != null) + { + this.AppWorkspace.ActiveDocumentWorkspace.ExecuteFunction( + new FlipLayerVerticalFunction(this.AppWorkspace.ActiveDocumentWorkspace.ActiveLayerIndex)); + } + } + + private void MenuLayersLayerProperties_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.Widgets.LayerForm.PerformPropertiesClick(); + } + } + + private void MenuLayersImportFromFile_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new ImportFromFileAction()); + } + } + + private void MenuLayersRotateZoom_Click(object sender, EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.RunEffect(typeof(RotateZoomEffect)); + } + } + } +} \ No newline at end of file diff --git a/src/Menus/PdnMainMenu.cs b/src/Menus/PdnMainMenu.cs new file mode 100644 index 0000000..5d8a11a --- /dev/null +++ b/src/Menus/PdnMainMenu.cs @@ -0,0 +1,107 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class PdnMainMenu + : MenuStripEx + { + private FileMenu fileMenu; + private EditMenu editMenu; + private ViewMenu viewMenu; + private ImageMenu imageMenu; + private LayersMenu layersMenu; + private AdjustmentsMenu adjustmentsMenu; + private EffectsMenu effectsMenu; + private WindowMenu windowMenu; + private HelpMenu helpMenu; + private AppWorkspace appWorkspace; + + public void CheckForUpdates() + { + this.helpMenu.CheckForUpdates(); + } + + public void RunEffect(Type effectType) + { + // TODO: this is kind of a hack + this.adjustmentsMenu.RunEffect(effectType); + } + + public AppWorkspace AppWorkspace + { + get + { + return this.appWorkspace; + } + + set + { + this.appWorkspace = value; + this.fileMenu.AppWorkspace = value; + this.editMenu.AppWorkspace = value; + this.viewMenu.AppWorkspace = value; + this.imageMenu.AppWorkspace = value; + this.layersMenu.AppWorkspace = value; + this.adjustmentsMenu.AppWorkspace = value; + this.effectsMenu.AppWorkspace = value; + this.windowMenu.AppWorkspace = value; + this.helpMenu.AppWorkspace = value; + } + } + + public void PopulateEffects() + { + this.adjustmentsMenu.PopulateEffects(); + this.effectsMenu.PopulateEffects(); + } + + public PdnMainMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.fileMenu = new FileMenu(); + this.editMenu = new EditMenu(); + this.viewMenu = new ViewMenu(); + this.imageMenu = new ImageMenu(); + this.adjustmentsMenu = new AdjustmentsMenu(); + this.effectsMenu = new EffectsMenu(); + this.layersMenu = new LayersMenu(); + this.windowMenu = new WindowMenu(); + this.helpMenu = new HelpMenu(); + SuspendLayout(); + // + // PdnMainMenu + // + this.Name = "PdnMainMenu"; + this.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow; + this.Items.AddRange( + new ToolStripItem[] + { + this.fileMenu, + this.editMenu, + this.viewMenu, + this.imageMenu, + this.layersMenu, + this.adjustmentsMenu, + this.effectsMenu, + this.windowMenu, + this.helpMenu + }); + ResumeLayout(); + } + } +} diff --git a/src/Menus/ToolsMenu.cs b/src/Menus/ToolsMenu.cs new file mode 100644 index 0000000..1d074f9 --- /dev/null +++ b/src/Menus/ToolsMenu.cs @@ -0,0 +1,138 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ +#if false + internal sealed class ToolsMenu + : PdnMenuItem + { + private bool toolsListInit = false; + private PdnMenuItem menuToolsAntialiasing; + private PdnMenuItem menuToolsAlphaBlending; + private ToolStripSeparator menuToolsSeperator; + + public ToolsMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.menuToolsAntialiasing = new PdnMenuItem(); + this.menuToolsAlphaBlending = new PdnMenuItem(); + this.menuToolsSeperator = new ToolStripSeparator(); + // + // ToolsMenu + // + this.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuToolsAntialiasing, + this.menuToolsAlphaBlending, + this.menuToolsSeperator + }); + this.Name = "Menu.Tools"; + this.Text = PdnResources.GetString("Menu.Tools.Text"); + // + // menuToolsAntiAliasing + // + this.menuToolsAntialiasing.Name = "AntiAliasing"; + this.menuToolsAntialiasing.Click += new System.EventHandler(MenuToolsAntiAliasing_Click); + // + // menuToolsAlphaBlending + // + this.menuToolsAlphaBlending.Name = "AlphaBlending"; + this.menuToolsAlphaBlending.Click += new EventHandler(MenuToolsAlphaBlending_Click); + } + + protected override void OnDropDownOpening(EventArgs e) + { + if (!this.toolsListInit) + { + this.DropDownItems.Clear(); + this.DropDownItems.Add(this.menuToolsAntialiasing); + this.DropDownItems.Add(this.menuToolsAlphaBlending); + this.DropDownItems.Add(this.menuToolsSeperator); + + foreach (ToolInfo toolInfo in DocumentWorkspace.ToolInfos) + { + PdnMenuItem mi = new PdnMenuItem(toolInfo.Name, null, this.menuTools_ClickHandler); + mi.SetIcon(toolInfo.Image); + mi.Tag = toolInfo; + this.DropDownItems.Add(mi); + } + + this.toolsListInit = true; + } + + Type currentToolType; + + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + currentToolType = AppWorkspace.ActiveDocumentWorkspace.GetToolType(); + } + else + { + currentToolType = null; + } + + foreach (ToolStripItem tsi in this.DropDownItems) + { + PdnMenuItem mi = tsi as PdnMenuItem; + + if (mi != null) + { + ToolInfo toolInfo = mi.Tag as ToolInfo; + + if (toolInfo != null) + { + if (toolInfo.ToolType == currentToolType) + { + mi.Checked = true; + } + else + { + mi.Checked = false; + } + } + } + } + + this.menuToolsAntialiasing.Checked = AppWorkspace.AppEnvironment.AntiAliasing; + this.menuToolsAlphaBlending.Checked = AppWorkspace.AppEnvironment.AlphaBlending; + + base.OnDropDownOpening(e); + } + + private void menuTools_ClickHandler(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + PdnMenuItem mi = (PdnMenuItem)sender; + ToolInfo toolInfo = (ToolInfo)mi.Tag; + AppWorkspace.ActiveDocumentWorkspace.SetToolFromType(toolInfo.ToolType); + } + } + + private void MenuToolsAntiAliasing_Click(object sender, System.EventArgs e) + { + AppWorkspace.AppEnvironment.AntiAliasing = !AppWorkspace.AppEnvironment.AntiAliasing; + } + + private void MenuToolsAlphaBlending_Click(object sender, EventArgs e) + { + AppWorkspace.AppEnvironment.AlphaBlending = !AppWorkspace.AppEnvironment.AlphaBlending; + } + } +#endif +} diff --git a/src/Menus/ViewMenu.cs b/src/Menus/ViewMenu.cs new file mode 100644 index 0000000..0c48a66 --- /dev/null +++ b/src/Menus/ViewMenu.cs @@ -0,0 +1,286 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using System; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class ViewMenu + : PdnMenuItem + { + private PdnMenuItem menuViewZoomIn; + private PdnMenuItem menuViewZoomOut; + private PdnMenuItem menuViewZoomToWindow; + private PdnMenuItem menuViewZoomToSelection; + private PdnMenuItem menuViewActualSize; + private ToolStripSeparator menuViewSeparator1; + private PdnMenuItem menuViewGrid; + private PdnMenuItem menuViewRulers; + private ToolStripSeparator menuViewSeparator2; + private PdnMenuItem menuViewPixels; + private PdnMenuItem menuViewInches; + private PdnMenuItem menuViewCentimeters; + + private bool OnOemPlusShortcut(Keys keys) + { + this.menuViewZoomIn.PerformClick(); + return true; + } + + private bool OnOemMinusShortcut(Keys keys) + { + this.menuViewZoomOut.PerformClick(); + return true; + } + + private bool OnCtrlAltZero(Keys keys) + { + this.menuViewActualSize.PerformClick(); + return true; + } + + public ViewMenu() + { + InitializeComponent(); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.OemMinus, OnOemMinusShortcut); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Oemplus, OnOemPlusShortcut); + PdnBaseForm.RegisterFormHotKey(Keys.Control | Keys.Alt | Keys.D0, OnCtrlAltZero); + } + + private void InitializeComponent() + { + this.menuViewZoomIn = new PdnMenuItem(); + this.menuViewZoomOut = new PdnMenuItem(); + this.menuViewZoomToWindow = new PdnMenuItem(); + this.menuViewZoomToSelection = new PdnMenuItem(); + this.menuViewActualSize = new PdnMenuItem(); + this.menuViewSeparator1 = new ToolStripSeparator(); + this.menuViewGrid = new PdnMenuItem(); + this.menuViewRulers = new PdnMenuItem(); + this.menuViewSeparator2 = new ToolStripSeparator(); + this.menuViewPixels = new PdnMenuItem(); + this.menuViewInches = new PdnMenuItem(); + this.menuViewCentimeters = new PdnMenuItem(); + // + // menuView + // + this.DropDownItems.AddRange( + new System.Windows.Forms.ToolStripItem[] + { + this.menuViewZoomIn, + this.menuViewZoomOut, + this.menuViewZoomToWindow, + this.menuViewZoomToSelection, + this.menuViewActualSize, + this.menuViewSeparator1, + this.menuViewGrid, + this.menuViewRulers, + this.menuViewSeparator2, + this.menuViewPixels, + this.menuViewInches, + this.menuViewCentimeters, + }); + this.Name = "Menu.View"; + this.Text = PdnResources.GetString("Menu.View.Text"); + // + // menuViewZoomIn + // + this.menuViewZoomIn.Name = "ZoomIn"; + this.menuViewZoomIn.ShortcutKeys = Keys.Control | Keys.Add; + this.menuViewZoomIn.ShortcutKeyDisplayString = PdnResources.GetString("Menu.View.ZoomIn.ShortcutKeyDisplayString"); + this.menuViewZoomIn.Click += new System.EventHandler(this.MenuViewZoomIn_Click); + // + // menuViewZoomOut + // + this.menuViewZoomOut.Name = "ZoomOut"; + this.menuViewZoomOut.ShortcutKeys = Keys.Control | Keys.Subtract; + this.menuViewZoomOut.ShortcutKeyDisplayString = PdnResources.GetString("Menu.View.ZoomOut.ShortcutKeyDisplayString"); + this.menuViewZoomOut.Click += new System.EventHandler(this.MenuViewZoomOut_Click); + // + // menuViewZoomToWindow + // + this.menuViewZoomToWindow.Name = "ZoomToWindow"; + this.menuViewZoomToWindow.ShortcutKeys = Keys.Control | Keys.B; + this.menuViewZoomToWindow.Click += new System.EventHandler(this.MenuViewZoomToWindow_Click); + // + // menuViewZoomToSelection + // + this.menuViewZoomToSelection.Name = "ZoomToSelection"; + this.menuViewZoomToSelection.ShortcutKeys = Keys.Control | Keys.Shift | Keys.B; + this.menuViewZoomToSelection.Click += new System.EventHandler(this.MenuViewZoomToSelection_Click); + // + // menuViewActualSize + // + this.menuViewActualSize.Name = "ActualSize"; + this.menuViewActualSize.ShortcutKeys = Keys.Control | Keys.Shift | Keys.A; + this.menuViewActualSize.Click += new System.EventHandler(this.MenuViewActualSize_Click); + // + // menuViewGrid + // + this.menuViewGrid.Name = "Grid"; + this.menuViewGrid.Click += new System.EventHandler(this.MenuViewGrid_Click); + // + // menuViewRulers + // + this.menuViewRulers.Name = "Rulers"; + this.menuViewRulers.Click += new System.EventHandler(this.MenuViewRulers_Click); + // + // menuViewPixels + // + this.menuViewPixels.Name = "Pixels"; + this.menuViewPixels.Click += new EventHandler(MenuViewPixels_Click); + this.menuViewPixels.Text = PdnResources.GetString("MeasurementUnit.Pixel.Plural"); + // + // menuViewInches + // + this.menuViewInches.Name = "Inches"; + this.menuViewInches.Text = PdnResources.GetString("MeasurementUnit.Inch.Plural"); + this.menuViewInches.Click += new EventHandler(MenuViewInches_Click); + // + // menuViewCentimeters + // + this.menuViewCentimeters.Name = "Centimeters"; + this.menuViewCentimeters.Click += new EventHandler(MenuViewCentimeters_Click); + this.menuViewCentimeters.Text = PdnResources.GetString("MeasurementUnit.Centimeter.Plural"); + } + + protected override void OnDropDownOpening(EventArgs e) + { + this.menuViewPixels.Checked = false; + this.menuViewInches.Checked = false; + this.menuViewCentimeters.Checked = false; + + switch (AppWorkspace.Units) + { + case MeasurementUnit.Pixel: + this.menuViewPixels.Checked = true; + break; + + case MeasurementUnit.Inch: + this.menuViewInches.Checked = true; + break; + + case MeasurementUnit.Centimeter: + this.menuViewCentimeters.Checked = true; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + this.menuViewZoomIn.Enabled = true; + this.menuViewZoomOut.Enabled = true; + this.menuViewZoomToWindow.Enabled = true; + this.menuViewZoomToSelection.Enabled = !AppWorkspace.ActiveDocumentWorkspace.Selection.IsEmpty; + this.menuViewActualSize.Enabled = true; + this.menuViewGrid.Enabled = true; + this.menuViewRulers.Enabled = true; + this.menuViewPixels.Enabled = true; + this.menuViewInches.Enabled = true; + this.menuViewCentimeters.Enabled = true; + + this.menuViewZoomToWindow.Checked = (AppWorkspace.ActiveDocumentWorkspace.ZoomBasis == ZoomBasis.FitToWindow); + this.menuViewGrid.Checked = AppWorkspace.ActiveDocumentWorkspace.DrawGrid; + this.menuViewRulers.Checked = AppWorkspace.ActiveDocumentWorkspace.RulersEnabled; + } + else + { + this.menuViewZoomIn.Enabled = false; + this.menuViewZoomOut.Enabled = false; + this.menuViewZoomToWindow.Enabled = false; + this.menuViewZoomToSelection.Enabled = false; + this.menuViewActualSize.Enabled = false; + this.menuViewGrid.Enabled = false; + this.menuViewRulers.Enabled = false; + this.menuViewPixels.Enabled = true; + this.menuViewInches.Enabled = true; + this.menuViewCentimeters.Enabled = true; + } + + base.OnDropDownOpening(e); + } + + private void MenuViewZoomIn_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new ZoomInAction()); + } + } + + private void MenuViewZoomOut_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new ZoomOutAction()); + } + } + + private void MenuViewZoomToWindow_Click(object sender, EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new ZoomToWindowAction()); + } + } + + private void MenuViewZoomToSelection_Click(object sender, EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.PerformAction(new ZoomToSelectionAction()); + } + } + + private void MenuViewPixels_Click(object sender, EventArgs e) + { + AppWorkspace.Units = MeasurementUnit.Pixel; + } + + private void MenuViewInches_Click(object sender, EventArgs e) + { + AppWorkspace.Units = MeasurementUnit.Inch; + } + + private void MenuViewCentimeters_Click(object sender, EventArgs e) + { + AppWorkspace.Units = MeasurementUnit.Centimeter; + } + + private void MenuViewActualSize_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.ZoomBasis = ZoomBasis.ScaleFactor; + AppWorkspace.ActiveDocumentWorkspace.ScaleFactor = ScaleFactor.OneToOne; + } + } + + private void MenuViewRulers_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.RulersEnabled = !AppWorkspace.ActiveDocumentWorkspace.RulersEnabled; + } + } + + private void MenuViewGrid_Click(object sender, System.EventArgs e) + { + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.DrawGrid = !AppWorkspace.ActiveDocumentWorkspace.DrawGrid; + } + } + } +} diff --git a/src/Menus/WindowMenu.cs b/src/Menus/WindowMenu.cs new file mode 100644 index 0000000..2f738f6 --- /dev/null +++ b/src/Menus/WindowMenu.cs @@ -0,0 +1,208 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Menus +{ + internal sealed class WindowMenu + : PdnMenuItem + { + private PdnMenuItem menuWindowResetWindowLocations; + private ToolStripSeparator menuWindowSeperator1; + private PdnMenuItem menuWindowTranslucent; + private ToolStripSeparator menuWindowSeperator2; + private PdnMenuItem menuWindowTools; + private PdnMenuItem menuWindowHistory; + private PdnMenuItem menuWindowLayers; + private PdnMenuItem menuWindowColors; + private ToolStripSeparator menuWindowSeparator3; + private PdnMenuItem menuWindowOpenMdiList; + private PdnMenuItem menuWindowNextTab; + private PdnMenuItem menuWindowPreviousTab; + + public WindowMenu() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + this.menuWindowResetWindowLocations = new PdnMenuItem(); + this.menuWindowSeperator1 = new ToolStripSeparator(); + this.menuWindowTranslucent = new PdnMenuItem(); + this.menuWindowSeperator2 = new ToolStripSeparator(); + this.menuWindowTools = new PdnMenuItem(); + this.menuWindowHistory = new PdnMenuItem(); + this.menuWindowLayers = new PdnMenuItem(); + this.menuWindowColors = new PdnMenuItem(); + this.menuWindowSeparator3 = new ToolStripSeparator(); + this.menuWindowOpenMdiList = new PdnMenuItem(); + this.menuWindowNextTab = new PdnMenuItem(); + this.menuWindowPreviousTab = new PdnMenuItem(); + // + // WindowMenu + // + this.DropDownItems.AddRange( + new ToolStripItem[] + { + this.menuWindowResetWindowLocations, + this.menuWindowSeperator1, + this.menuWindowTranslucent, + this.menuWindowSeperator2, + this.menuWindowTools, + this.menuWindowHistory, + this.menuWindowLayers, + this.menuWindowColors, + this.menuWindowSeparator3, + this.menuWindowOpenMdiList, + this.menuWindowNextTab, + this.menuWindowPreviousTab + }); + this.Name = "Menu.Window"; + this.Text = PdnResources.GetString("Menu.Window.Text"); + // + // menuWindowResetWindowLocations + // + this.menuWindowResetWindowLocations.Name = "ResetWindowLocations"; + this.menuWindowResetWindowLocations.Click += new System.EventHandler(this.MenuWindowResetWindowLocations_Click); + // + // menuWindowTranslucent + // + this.menuWindowTranslucent.Name = "Translucent"; + this.menuWindowTranslucent.Click += new System.EventHandler(this.MenuWindowTranslucent_Click); + // + // menuWindowTools + // + this.menuWindowTools.Name = "Tools"; + this.menuWindowTools.ShortcutKeys = Keys.F5; + this.menuWindowTools.Click += new System.EventHandler(this.MenuWindowTools_Click); + // + // menuWindowHistory + // + this.menuWindowHistory.Name = "History"; + this.menuWindowHistory.ShortcutKeys = Keys.F6; + this.menuWindowHistory.Click += new System.EventHandler(this.MenuWindowHistory_Click); + // + // menuWindowLayers + // + this.menuWindowLayers.Name = "Layers"; + this.menuWindowLayers.ShortcutKeys = Keys.F7; + this.menuWindowLayers.Click += new System.EventHandler(this.MenuWindowLayers_Click); + // + // menuWindowColors + // + this.menuWindowColors.Name = "Colors"; + this.menuWindowColors.ShortcutKeys = Keys.F8; + this.menuWindowColors.Click += new System.EventHandler(this.MenuWindowColors_Click); + // + // menuWindowOpenMdiList + // + this.menuWindowOpenMdiList.Name = "OpenMdiList"; + this.menuWindowOpenMdiList.ShortcutKeys = Keys.Control | Keys.Q; + this.menuWindowOpenMdiList.Click += new EventHandler(MenuWindowOpenMdiList_Click); + // + // menuWindowNextTab + // + this.menuWindowNextTab.Name = "NextTab"; + this.menuWindowNextTab.ShortcutKeys = Keys.Control | Keys.Tab; + this.menuWindowNextTab.Click += new EventHandler(MenuWindowNextTab_Click); + // + // menuWindowPreviousTab + // + this.menuWindowPreviousTab.Name = "PreviousTab"; + this.menuWindowPreviousTab.ShortcutKeys = Keys.Control | Keys.Shift | Keys.Tab; + this.menuWindowPreviousTab.Click += new EventHandler(MenuWindowPreviousTab_Click); + } + + private void MenuWindowOpenMdiList_Click(object sender, EventArgs e) + { + this.AppWorkspace.ToolBar.ShowDocumentList(); + } + + private void MenuWindowPreviousTab_Click(object sender, EventArgs e) + { + this.AppWorkspace.ToolBar.DocumentStrip.PreviousTab(); + } + + private void MenuWindowNextTab_Click(object sender, EventArgs e) + { + this.AppWorkspace.ToolBar.DocumentStrip.NextTab(); + } + + protected override void OnDropDownOpening(EventArgs e) + { + this.menuWindowTranslucent.Checked = PdnBaseForm.EnableOpacity; + this.menuWindowTools.Checked = AppWorkspace.Widgets.ToolsForm.Visible; + this.menuWindowHistory.Checked = AppWorkspace.Widgets.HistoryForm.Visible; + this.menuWindowLayers.Checked = AppWorkspace.Widgets.LayerForm.Visible; + this.menuWindowColors.Checked = AppWorkspace.Widgets.ColorsForm.Visible; + + if (UserSessions.IsRemote) + { + this.menuWindowTranslucent.Enabled = false; + this.menuWindowTranslucent.Checked = false; + } + + this.menuWindowOpenMdiList.Enabled = (AppWorkspace.DocumentWorkspaces.Length > 0); + + bool pluralDocuments = (AppWorkspace.DocumentWorkspaces.Length > 1); + this.menuWindowNextTab.Enabled = pluralDocuments; + this.menuWindowPreviousTab.Enabled = pluralDocuments; + + base.OnDropDownOpening(e); + } + + private void ToggleFormVisibility(FloatingToolForm ftf) + { + ftf.Visible = !ftf.Visible; + + if (AppWorkspace.ActiveDocumentWorkspace != null) + { + AppWorkspace.ActiveDocumentWorkspace.Focus(); + } + } + + private void MenuWindowTools_Click(object sender, System.EventArgs e) + { + ToggleFormVisibility(AppWorkspace.Widgets.ToolsForm); + } + + private void MenuWindowHistory_Click(object sender, System.EventArgs e) + { + ToggleFormVisibility(AppWorkspace.Widgets.HistoryForm); + } + + private void MenuWindowLayers_Click(object sender, System.EventArgs e) + { + ToggleFormVisibility(AppWorkspace.Widgets.LayerForm); + } + + private void MenuWindowColors_Click(object sender, System.EventArgs e) + { + ToggleFormVisibility(AppWorkspace.Widgets.ColorsForm); + } + + private void MenuWindowResetWindowLocations_Click(object sender, System.EventArgs e) + { + AppWorkspace.ResetFloatingForms(); + AppWorkspace.Widgets.ToolsForm.Visible = true; + AppWorkspace.Widgets.HistoryForm.Visible = true; + AppWorkspace.Widgets.LayerForm.Visible = true; + AppWorkspace.Widgets.ColorsForm.Visible = true; + } + + private void MenuWindowTranslucent_Click(object sender, System.EventArgs e) + { + PdnBaseForm.EnableOpacity = !PdnBaseForm.EnableOpacity; + } + } +} diff --git a/src/Microsoft.Ink/Microsoft.Ink.dll b/src/Microsoft.Ink/Microsoft.Ink.dll new file mode 100644 index 0000000..c372e42 Binary files /dev/null and b/src/Microsoft.Ink/Microsoft.Ink.dll differ diff --git a/src/Microsoft.Ink/Microsoft.Ink.resources.dll b/src/Microsoft.Ink/Microsoft.Ink.resources.dll new file mode 100644 index 0000000..8eb076b Binary files /dev/null and b/src/Microsoft.Ink/Microsoft.Ink.resources.dll differ diff --git a/src/Microsoft.Ink/Microsoft.Ink.xml b/src/Microsoft.Ink/Microsoft.Ink.xml new file mode 100644 index 0000000..2dd0a97 --- /dev/null +++ b/src/Microsoft.Ink/Microsoft.Ink.xml @@ -0,0 +1,6995 @@ + + + + + + + + + + Defines values that specify the behavior mode of the InkOverlay object and the InkPicture control. + + + Specifies the object or control is in collection mode. + + + Specifies the object or control is in deletion mode. + + + Specifies the object or control is in selection and editing mode. + + + Defines values that specify the way in which ink is erased from the InkOverlay object or the InkPicture control + + + Specifies ink is erased by a stroke. + + + Specifies ink is erased by a point. + + + Defines values that specify where to attach the new InkOverlay object, behind or in front of the active layer. + + + Attaches the new InkOverlay object behind the active window. + + + Attaches the new InkOverlay object in front of the active window. + + + Defines values that specify which part of a selection, if any, was hit during a hit test. + + + Specifies no part of the selection was hit. + + + Specifies the northwest corner sizing handle was hit. + + + Specifies the southeast corner sizing handle was hit. + + + Specifies the northeast corner sizing handle was hit. + + + Specifies the southwest corner sizing handle was hit. + + + Specifies the east side sizing handle was hit. + + + Specifies the west side sizing handle was hit. + + + Specifies the north side sizing handle was hit. + + + Specifies the south side sizing handle was hit. + + + Specifies the selection itself was hit (no selection handle was hit). + + + Represents the method that handles the Painting event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlayPaintingEventArgs object that contains the event data. + + + Provides data for the Painting events of Painting objects and Painting controls, which occur when ink is about to be painted. + + + Initializes a new instance of the InkOverlayPaintingEventArgs class + + + The Graphics object that the InkOverlay object is rendering to + The clip Rectangle to paint within + Set to true if the paint operation should occur; otherwise false + + + Frees the resources of the current InkOverlayPaintingEventArgs object before it is reclaimed by the garbage collector. + + + Releases resources used by the InkOverlayPaintingEventArgs object. + + + Releases the unmanaged resources used by the InkOverlayPaintingEventArgs object and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Gets the Graphics object that is used to paint the item. + + + Gets the Rectangle structure that represents the rectangle in which to paint. + + + Represents the method that handles the Painted event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the PaintEventArgs object that contains the event data. + + + Represents the method that handles the SelectionChanging event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlaySelectionChangingEventArgs object that contains the event data. + + + Provides data for the SelectionChanging events of SelectionChanging objects and SelectionChanging controls, which occur when the selection of ink is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property (for Selection or Selection). + + + Initializes a new instance of the InkOverlaySelectionChangingEventArgs class. + + + The Strokes collection that will become the Selection property of the InkOverlay object + + + Gets the new Strokes collection for a InkOverlaySelectionChangingEventArgs event. + + + Represents the method that handles the SelectionChanged event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the EventArgs object that contains the event data. + + + Represents the method that handles the SelectionMoving event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlaySelectionMovingEventArgs object that contains the event data. + + + Provides data for the SelectionMoving events of SelectionMoving objects and SelectionMoving controls, which occur when selected strokes are about to be moved. + + + Initializes a new instance of the InkOverlaySelectionMovingEventArgs class. + + + The new position to which the selected Strokes collection will move. + + + Gets the rectangle to which the selection is moved after the SelectionMoving event. + + + Represents the method that handles the SelectionResizing event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlaySelectionResizingEventArgs object that contains the event data. + + + Provides data for the SelectionResizing events of SelectionResizing objects and SelectionResizing controls, which occur when selected stroke are about to be resized. + + + Initializes a new instance of the InkOverlaySelectionResizingEventArgs class. + + + The size to which the selected Strokes collection will change. + + + Gets the bounding rectangle of the selection after the SelectionResizing event, as a Rectangle structure. + + + Represents the method that handles the SelectionMoved event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlaySelectionMovedEventArgs object that contains the event data. + + + Provides data for the SelectionMoved events of SelectionMoved objects and SelectionMoved controls, which occur when selected strokes have been moved. + + + Initializes a new instance of the InkOverlaySelectionMovedEventArgs class + + + The old bounding Rectangle, or previous position, of the Strokes collection that was moved. + + + Gets the bounding rectangle of the selected Strokes collection as it existed before the SelectionMoved event fired. + + + Represents the method that handles the SelectionResized event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlaySelectionResizedEventArgs object that contains the event data. + + + Provides data for the SelectionResizing events of SelectionResized objects and SelectionResized controls, which occur when selected stroke have been resized. + + + Initializes a new instance of the InkOverlaySelectionResizedEventArgs class. + + + The original bounding Rectangle of the selected Strokes collection, before the resize operation + + + Gets the bounding rectangle of the selected Strokes collection as it existed before the SelectionResized event fired. + + + Represents the method that handles the StrokesDeleting event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the InkOverlayStrokesDeletingEventArgs object that contains the event data. + + + Provides data for the StrokesDeleting events of StrokesDeleting objects and StrokesDeleting controls, which occur when stroke are about to be deleted. + + + Initializes a new instance of the InkOverlayStrokesDeletingEventArgs class. + + + The Strokes collection to be deleted + + + Gets the Strokes collection deleted when the StrokesDeleting event fires. + + + Represents the method that handles the StrokesDeleted event of an InkOverlay object. + [in] Specifies the source InkOverlay of this event. + [in] Specifies the EventArgs object that contains the event data. + + + Represents an object that is useful for annotation scenarios where users are not concerned with performing recognition on ink but instead are interested in the size, shape, color, and position of the ink. + + + + + + + + + Not implemented. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Frees the resources of the current InkOverlay object before it is reclaimed by the garbage collector. + + + Releases resources used by the InkOverlay object. + + + Releases the unmanaged resources used by the InkOverlay object and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Initializes a new instance of the InkOverlay class + + + + + Initializes a new instance of the InkOverlay class and associates a window handle with it + The handle of the window to which the InkOverlay object is attached. + + + Initializes a new instance of the InkOverlay class + The control to which the InkOverlay object is attached. + + + Initializes a new instance of the InkOverlay class, attaches it to the specified window handle, and determines whether to use the mouse for input. + The handle of the window to which the InkOverlay object is attached. + Set to true to use the mouse for tablet input; otherwise false. + + + Initializes a new instance of the InkOverlay class + The control to which the InkOverlay object is attached. + Set to true to use the mouse for tablet input; otherwise false. + + + Initializes a new instance of the InkOverlay class and attaches it to a specified window handle on a specified Tablet object + The handle of the window to which the InkOverlay object is attached. + The Tablet object to which the new window for the InkOverlay object is attached. + + + Initializes a new instance of the InkOverlay class + The control to which the InkOverlay object is attached. + The Tablet object to which the new window for the InkOverlay object is attached. + + + + + + + + + + + + + + + + + + + Sets whether or not the InkOverlay object has interest in a known application gesture. + A member of the ApplicationGesture enumeration, which indicates the gesture to set the status of + Whether or not the InkOverlay object has interest in a known application gesture. + + + + + Returns a value that indicates whether the InkOverlay object has interest in a particular application gesture. + A member of the ApplicationGesture enumeration that represents the gesture to query about. + Whether the InkOverlay object has interest in a particular application gesture. + + + + + Returns a value that indicates which part of a selection, if any, was hit during a hit test. + The x-position, in pixels, of the hit test + The y-position, in pixels, of the hit test + A member of the SelectionHitResult enumeration, which specifies which part of a selection, if any, was hit during a hit test + + + + + Sets the window rectangle, in pixels, within which ink is drawn. + + + + + Gets the window rectangle, in pixels, within which ink is drawn. + The rectangle within which ink is drawn. + + + + + Sets the InkOverlay object to collect ink from any tablet attached to the Tablet PC. + + + Sets the InkOverlay object to collect ink from any tablet attached to the Tablet PC. + The Boolean value that indicates whether to use the mouse as an input device + + + Sets the InkOverlay object to collect ink from only one tablet attached to the Tablet PC + The Tablet object on which ink is collected. + + + Sets a rectangle in which to redraw the ink within the InkOverlay object + The rectangle in which to redraw the ink, in pixel coordinates + + + Allows derived classes to modify the default behavior of the Painting event. + + + The InkOverlayPaintingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Painted event. + + + The PaintEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionChanging event. + + + The InkOverlaySelectionChangingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionChanged event. + + + The EventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionMoving event. + + + The InkOverlaySelectionMovingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionResizing event. + + + The InkOverlaySelectionResizingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionMoved event. + + + The InkOverlaySelectionMovedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionResized event. + + + The InkOverlaySelectionResizedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the StrokesDeleting event. + + + The InkOverlayStrokesDeletingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the StrokesDeleted event. + + + The EventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseDown event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseMove event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseUp event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseWheel event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the DoubleClick event. + The CancelEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Stroke event. + + + The InkCollectorStrokeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorDown event. + + + The InkCollectorCursorDownEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the NewPackets event. + + + The InkCollectorNewPacketsEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the NewInAirPackets event. + + + The InkCollectorNewInAirPacketsEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorButtonDown event. + + + The InkCollectorCursorButtonDownEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorButtonUp event. + + + The InkCollectorCursorButtonUpEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorInRange event. + + + The InkCollectorCursorInRangeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorOutOfRange event. + + + The InkCollectorCursorOutOfRangeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SystemGesture event. + + + The InkCollectorSystemGestureEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Gesture event. + + + The InkCollectorGestureEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the TabletAdded event. + + + The InkCollectorTabletAddedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the TabletRemoved event. + + + The InkCollectorTabletRemovedEventArgs object that contains the event data + + + + + + + + + + + + + Occurs when the cursor tip contacts the digitizing tablet surface + + + Occurs when the mouse pointer is over the InkOverlay and a mouse button is pressed + + + Occurs when the mouse pointer is moved over the InkOverlay + + + Occurs when the mouse pointer is over the InkOverlay and a mouse button is released + + + Occurs when the mouse wheel moves while the InkOverlay object has focus + + + Occurs when the InkOverlay object is double-clicked + + + Occurs when the user finishes drawing a new stroke on any tablet + + + Occurs when the InkOverlay receives packets + + + Occurs when an in-air packet is seen, which happens when a user moves a pen near the tablet and the cursor is within the InkOverlay object's window or the user moves a mouse within the InkOverlay object's associated window + + + Occurs when a cursor enters the physical detection range (proximity) of the tablet context + + + Occurs when a cursor leaves the physical detection range (proximity) of the tablet context + + + Occurs when the InkOverlay detects a cursor button that is down + + + Occurs when the InkOverlay detects a cursor button that is up + + + Occurs when a Tablet is added to the system + + + Occurs when a Tablet is removed from the system + + + Occurs when a system gesture is recognized + + + Occurs before the InkOverlay object redraws itself + + + Occurs when the InkOverlay object has completed redrawing itself + + + Occurs when the selection of ink within the control is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the selection of ink within the control has changed, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the position of the current selection is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the size of the current selection is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the position of the current selection has changed, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the size of the current selection has changed, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs before strokes are deleted from the Ink property + + + Occurs after strokes have been deleted from the Ink property + + + Occurs when an application-specific gesture is recognized + + + Gets or sets the handle of the window to which the InkOverlay object is attached. + + + + + Gets or sets the control to which the InkOverlay object is attached. + + + + + Gets or sets a value that specifies whether the InkOverlay object repaints the ink when the window is invalidated. + + + + + Gets or sets a value that indicates whether ink is rendered as it is drawn. + + + Gets a value that specifies whether ink is currently being drawn on an InkOverlay object. + + + + + Gets or sets the default DrawingAttributes object, which specifies the drawing attributes that are used when drawing and displaying ink. + + + + + Gets or sets the Renderer object that is used to draw ink. + + + Gets the Cursors collection that is available for use in the inking region + + + + + Gets or sets the Ink object that is associated with the InkOverlay object. + + + + + Gets or sets a value that specifies whether the InkOverlay object collects pen input. + + + + + Gets or sets the collection mode that determines whether ink, gesture, or both are recognized as the user writes. + + + Gets or sets interest in aspects of the packet associated with ink drawn on the InkOverlay object. + + + + + Gets the tablet device that the InkOverlay object is currently using to collect input. + + + + + Gets or sets the cursor that appears when the mouse pointer is over the InkPicture control. + + + Gets or sets a value that specifies whether ink is rendered as just one color when the system is in High Contrast mode. + + + Gets or sets a value that specifies whether all selection user interface (UI) are drawn in high contrast when the system is in High Contrast mode. + + + Gets or sets the margins along the x-axis, in pixels. + + + Gets or sets the margins along the y-axis, in pixels. + + + Gets or sets the value that specifies whether the InkOverlay object is attached behind or in front of the known window. + + + + + Gets or sets a value that indicates whether the InkOverlay is in ink mode, deletion mode, or selecting/editing mode. + + + + + Gets or sets a value that indicates whether ink is erased by stroke or by point. + + + + + Gets or sets a value that specifies the width of the eraser pen tip. + + + + + Gets or sets the Strokes collection that is currently selected inside the InkOverlay control. + + + + + The InkPicture control provides the ability to place an image in an application and enable users to add ink on top of it + + + + + + + + + Not implemented. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Releases the unmanaged resources used by the InkPicture control and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Initializes a new instance of the InkPicture control. + + + + + Allows derived classes to modify the default behavior of the HandleCreated event. + The EventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the HandleDestroyed event. + The EventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseDown event + The MouseEventArgs object that contains the event data + + + Sets whether or not the InkPicture object has interest in a known application gesture. + A member of the ApplicationGesture enumeration, which indicates the gesture to set the status of + Whether or not the InkPicture object has interest in a known application gesture. + + + + + Returns a value that indicates whether the InkPicture control has interest in a particular application gesture. + A member of the ApplicationGesture enumeration that represents the gesture to query about. + Whether the InkPicture control has interest in a particular application gesture. + + + + + Allows derived classes to modify the default behavior of the KeyDown event. + The KeyEventArgs object that contains the event data + + + Returns a value that indicates which part of a selection, if any, was hit during a hit test. + The x-position, in pixels, of the hit test + The y-position, in pixels, of the hit test + A member of the SelectionHitResult enumeration, which specifies which part of a selection, if any, was hit during a hit test + + + + + Sets the window rectangle, in pixels, within which ink is drawn. + + + + + Gets the window rectangle, in pixels, within which ink is drawn. + The rectangle within which ink is drawn. + + + + + Sets the InkPicture control to collect ink from any tablet attached to the Tablet PC. + + + Sets the InkPicture control to collect ink from any tablet attached to the Tablet PC. + The Boolean value that indicates whether to use the mouse as an input device + + + Sets the InkPicture control to collect ink from only one tablet attached to the Tablet PC + The Tablet object on which ink is collected. + + + Allows derived classes to modify the default behavior of the Painting event. + + + The InkOverlayPaintingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Painted event. + + + The PaintEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionChanging event. + + + The InkOverlaySelectionChangingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionChanged event. + + + The EventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionMoving event. + + + The InkOverlaySelectionMovingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionResizing event. + + + The InkOverlaySelectionResizingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionMoved event. + + + The InkOverlaySelectionMovedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SelectionResized event. + + + The InkOverlaySelectionResizedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the StrokesDeleting event. + + + The InkOverlayStrokesDeletingEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the StrokesDeleted event. + + + The EventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Stroke event. + + + The InkCollectorStrokeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorDown. + + + The InkCollectorCursorDownEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the NewPackets event. + + + The InkCollectorNewPacketsEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the NewInAirPackets. + + + The InkCollectorNewInAirPacketsEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorButtonDown event. + + + The InkCollectorCursorButtonDownEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorButtonUp event. + + + The InkCollectorCursorButtonUpEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorInRange. + + + The InkCollectorCursorInRangeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorOutOfRange. + + + The InkCollectorCursorOutOfRangeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SystemGesture event. + + + The InkCollectorSystemGestureEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Gesture event. + + + The InkCollectorGestureEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the TabletAdded event. + + + The InkCollectorTabletAddedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the TabletRemoved event. + + + The InkCollectorTabletRemovedEventArgs object that contains the event data + + + + + + + + + + + + + Occurs when the cursor tip contacts the digitizing tablet surface. + + + Occurs when the user finishes drawing a new stroke on any tablet + + + Occurs when the InkPicture receives packets + + + Occurs when an in-air packet is seen, which happens when a user moves a pen near the tablet and the cursor is within the InkPicture or the user moves a mouse within the InkPicture + + + Occurs when a cursor enters the physical detection range (proximity) of the tablet context. + + + Occurs when a cursor leaves the physical detection range (proximity) of the tablet context + + + Occurs when the InkPicture detects a cursor button that is down + + + Occurs when the InkPicture detects a cursor button that is up. + + + Occurs when a Tablet is added to the system + + + Occurs when a Tablet is removed from the system + + + Occurs when a system gesture is recognized + + + Occurs before the InkPicture object redraws itself + + + Occurs when the InkPicture object has completed redrawing itself + + + Occurs when the selection of ink within the control is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the selection of ink within the control has changed, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the position of the current selection is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the size of the current selection is about to change, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the position of the current selection has changed, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs when the size of the current selection has changed, such as through alterations to the user interface, cut-and-paste procedures, or the Selection property + + + Occurs before strokes are deleted from the Ink property + + + Occurs after strokes have been deleted from the Ink property + + + Occurs when an application-specific gesture is recognized + + + Gets a value that indicates whether the InkPicture control is being disposed of. + + + Gets or sets a value that specifies whether the InkPicture control repaints the ink when the window is invalidated. + + + + + Gets or sets a value that indicates whether ink is rendered as it is drawn. + + + Gets a value that specifies whether ink is currently being drawn on an InkPicture control. + + + + + Gets or sets the default DrawingAttributes object, which specifies the drawing attributes that are used when drawing and displaying ink. + + + + + Gets or sets the Renderer object that is used to draw ink. + + + Gets the Cursors collection that is available for use in the inking region + + + + + Gets or sets the Ink object that is associated with the InkPicture object. + + + + + Gets or sets a value that specifies whether the InkPicture control collects pen input. + + + + + Gets or sets the collection mode that determines whether ink, gesture, or both are recognized as the user writes. + + + Gets or sets interest in aspects of the packet associated with ink drawn on the InkPicture object. + + + + + Gets the tablet device that the InkPicture control is currently using to collect input. + + + + + Gets or sets the cursor that appears when the mouse pointer is over the InkPicture control. + + + Gets or sets a value indicating whether the user can give the focus to this control by pressing the TAB key. + + + Gets or sets a value that specifies whether ink is rendered as just one color when the system is in High Contrast mode. + + + Gets or sets a value that specifies whether all selection user interface (UI) are drawn in high contrast when the system is in High Contrast mode. + + + Gets or sets the margins along the x-axis, in pixels. + + + Gets or sets the margins along the y-axis, in pixels. + + + Gets or sets a value that indicates whether the InkPicture is in ink mode, delete mode, or select/edit mode. + + + + + Gets or sets a value that indicates whether ink is erased by stroke or by point. + + + + + Gets or sets a value that specifies the width of the eraser pen tip. + + + + + Gets or sets the Strokes collection that is currently selected inside the InkPicture control. + + + + + + + + + + + + + + + + + + + + + Defines values that determine whether ink, gesture, or ink and gestures are recognized as the user writes. + + + Collects only ink, creating a stroke. The Gesture, Gesture, or Gesture event interest is set to false, meaning that gestures are not collected (all other event interests remain as they were). + + + Collects only gestures and does not create a stroke + + + Accepts only single-stroke gestures + + + Defines values that set the interest in a set of operating system-specific gesture + + + Maps to a left-click on a mouse + + + Maps to a double-click on a mouse + + + Maps to a right-click on a mouse + + + Maps to a left drag on a mouse + + + Specifies a press and hold followed by a stroke, which maps to a right drag on a mouse + + + Specifies a left click for a long time, which has no mouse equivalent + + + Not implemented. + + + Maps to a mouse hover + + + Maps to a mouse leaving a hover + + + Represents the method that handles the CursorDown event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorCursorDownEventArgs object that contains the event data. + + + Provides data for the CursorDown, CursorDown, and CursorDown events. + + + Initializes a new instance of the InkCollectorCursorDownEventArgs class. + + + The Cursor object that triggers the CursorDown event. + The Stroke object created during the CursorDown event. + + + Gets the Cursor object that generated the CursorDown event. + + + Gets the Stroke object that was started when the Cursor object caused the CursorDown event to fire. + + + Represents the method that handles the Stroke event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorStrokeEventArgs object that contains the event data. + + + Provides data for the Stroke events of ink collector (Stroke, Stroke, and Stroke), which occur when the user draws a new stroke on any tablet. + + + Initializes a new instance of the InkCollectorStrokeEventArgs class. + + + The Cursor used to create the Stroke object. + The inked Stroke object that triggers the Stroke event. + Set to true if the Stroke object should be stored after returning from the event handler; false if the Stroke object is deleted after exiting the event handler. + + + Gets the Cursor object that was used to create the new stroke. + + + Gets the collected Stroke object that generated the Stroke event. + + + Represents the method that handles the NewPackets event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorNewPacketsEventArgs object that contains the event data. + + + Provides data for the NewPackets events of ink collector (NewPackets, NewPackets, and NewPackets), which occur when ink collector receives packet. + + + Initializes a new instance of the InkCollectorNewPacketsEventArgs class. + + + The Cursor object that triggers the NewPackets event. + The inked Stroke object. + The number of packet collected. + The packet data in raw format. + + + Gets the Cursor object that was used to create the Stroke object that generated the NewPackets event. + + + Gets the Stroke object that generated the NewPackets event. + + + Gets the number of packet received for a Stroke object. + + + Gets an array of type Int32 containing the selected data for the packet. + + + + + Represents the method that handles the NewInAirPackets event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorNewInAirPacketsEventArgs object that contains the event data. + + + Provides data for the NewInAirPackets events of ink collector (NewInAirPackets, NewInAirPackets, and NewInAirPackets), which occur when an in-air packet is seen. + + + Initializes a new instance of the InkCollectorNewInAirPacketsEventArgs class. + + + The Cursor that triggers the NewInAirPackets event. + The number of packet collected. + The packet data in raw format. + + + Gets the Cursor object that generated the NewInAirPackets event. + + + Gets the number of in-air packet received. + + + Gets an array of type Int32 containing the selected data for the packet. + + + + + Represents the method that handles the CursorInRange event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorCursorInRangeEventArgs object that contains the event data. + + + Provides data for the CursorInRange, CursorInRange, and CursorInRange events. + + + Initializes a new instance of the InkCollectorCursorInRangeEventArgs class. + + + The Cursor object that triggers the CursorInRange event. + Set to true if this is the first time this InkCollector object has seen this Cursor object; otherwise false. + The values of the State properties for the CursorButtons collection associated with the Cursor object, taken at the time the CursorInRange event occurs. + + + Gets the Cursor object that generated the CursorInRange event. + + + Gets a Boolean value that indicates whether this is the first time this InkCollector object has come in contact with the Cursor object that generated the CursorInRange event. + + + Gets an array of values from the CursorButtonState enumeration describing the state of the buttons for the cursor that generated the CursorInRange event. + + + + + Represents the method that handles the CursorOutOfRange event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorCursorOutOfRangeEventArgs object that contains the event data. + + + Provides data for the CursorOutOfRange, CursorOutOfRange, and CursorOutOfRange events. + + + Initializes a new instance of the InkCollectorCursorOutOfRangeEventArgs class. + + + The Cursor object that triggers the CursorOutOfRange event. + + + Gets the Cursor object that generated the CursorOutOfRange event. + + + Represents the method that handles the CursorButtonDown event of an InkCollector. + + + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorCursorButtonDownEventArgs object that contains the event data. + + + Provides data for the CursorButtonDown, CursorButtonDown, and CursorButtonDown events. + + + Initializes a new instance of the InkCollectorCursorButtonDownEventArgs class. + + + The Cursor object that triggers the CursorButtonDown event. + The CursorButton that was pressed. + + + Gets the Cursor object that generated the CursorButtonDown event. + + + Gets the CursorButton object for the button that was pressed. + + + Represents the method that handles the CursorButtonUp event of an InkCollector. + + + [in] Specifies the source InkCollector of this event + [in] Specifies the InkCollectorCursorButtonUpEventArgs object that contains the event data. + + + Provides data for the CursorButtonUp, CursorButtonUp, and CursorButtonUp events. + + + Initializes a new instance of the InkCollectorCursorButtonUpEventArgs class. + + + The Cursor object that triggers the CursorButtonUp event. + The CursorButton that was released. + + + Gets the Cursor object that generated the CursorButtonUp event. + + + Gets the CursorButton object for the button that was released. + + + Represents the method that handles the TabletAdded event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorTabletAddedEventArgs object that contains the event data. + + + Provides data for the TabletAdded events of ink collector (TabletAdded, TabletAdded, and TabletAdded), which occur when a tablet is added to the system. + + + Initializes a new instance of the InkCollectorTabletAddedEventArgs class. + + + The Tablet object that is added. + + + Gets the Tablet object that is added to the system. + + + Represents the method that handles the TabletRemoved event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorTabletRemovedEventArgs object that contains the event data. + + + Provides data for the TabletRemoved events of ink collector (TabletRemoved, TabletRemoved, and TabletRemoved), which occur when a tablet is removed from the system. + + + Initializes a new instance of the InkCollectorTabletRemovedEventArgs class. + + + The value that was used as the ID for the Tablet object that was removed. + + + Gets the Int32 value that was used as the ID for the Tablet object that was removed. + + + + + Provides data for cancelable mouse events. + + + Initializes a new instance of the CancelMouseEventArgs class + A member of the MouseButtons enumeration, specifying which mouse button was pressed + The number of times the mouse button was pressed and released + The x-coordinate, in pixels, of a mouse click. + The y-coordinate, in pixels, of a mouse click + A signed count of the number of detents the mouse wheel has rotated. + Set to true to cancel the event for the parent control; otherwise false + + + Gets or sets a value that indicates whether the mouse event is canceled for the parent control. + + + Represents the method that handles the MouseDown event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the CancelMouseEventArgs object that contains the event data + + + Represents the method that handles the MouseMove event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the CancelMouseEventArgs object that contains the event data + + + Represents the method that handles the MouseUp event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the CancelMouseEventArgs object that contains the event data + + + Represents the method that handles the MouseWheel event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the CancelMouseEventArgs object that contains the event data + + + Represents the method that handles the DoubleClick event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the CancelEventArgs object that contains the event data + + + Represents the method that handles the Gesture event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorSystemGestureEventArgs object that contains the event data. + + + Provides data for the SystemGesture events of ink collector (SystemGesture, SystemGesture, and SystemGesture), which occur when a system gesture is recognized + + + Initializes a new instance of the InkCollectorSystemGestureEventArgs class. + + + The Cursor used to create the Stroke object. + The value of the SystemGesture enumeration that corresponds to the system gesture. + The location of the hot point for the system gesture. + Reserved. + Reserved. + Set to 1 if the Cursor object is in ink mode; 2 if the Cursor object is in erase mode. + + + Gets the Cursor object that generated the SystemGesture event. + + + Gets the Point structure that indicates the location of the system gesture. + + + Gets the value of the system gesture. + + + Reserved. + + + Reserved. + + + Gets a value that indicates whether the Cursor object is in normal mode or eraser mode. + + + Represents the method that handles the Gesture event of an InkCollector. + [in] Specifies the source InkCollector of this event. + [in] Specifies the InkCollectorGestureEventArgs object that contains the event data. + + + Provides data for the Gesture events of ink collector (Gesture, Gesture, and Gesture), which occur when an application-specific gesture is recognized. + + + Initializes a new instance of the InkCollectorGestureEventArgs event. + + + The Cursor used to create the application gesture. + The Strokes collection that the gesture is make of. + An array of Gesture objects recognized, in order of confidence. + Set to true if the gesture was handled in the Gesture event handler (default value); otherwise false. + + + Gets the Cursor object that generated the Gesture event. + + + Gets the Strokes collection that the recognizer returned as the application gesture. + + + Gets an array of Gesture objects, in order of confidence, from the recognizer. + + + Represents an object that is used to capture ink from available tablet devices. + + + + + + + + + Not implemented. + + + Frees the resources of the current InkCollector object before it is reclaimed by the garbage collector. + + + Releases resources used by the InkCollector object. + + + Releases the unmanaged resources used by the InkCollector object and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Initializes a new instance of the InkCollector class + + + + + Initializes a new instance of the InkCollector class and associates a window handle with it. + The handle of the window to which the InkCollector object is attached + + + Initializes a new instance of the InkCollector class + The control to which the InkCollector object is attached. + + + Creates an InkCollector object, attaches it to the specified window handle, and determines whether to use the mouse for input + The handle of the window to which the InkCollector object is attached + Set to true to use the mouse for tablet input; otherwise false. + + + Initializes a new instance of the InkCollector class + The control to which the InkCollector object is attached. + Set to true to use the mouse for tablet input; otherwise false. + + + Creates an InkCollector object and attaches it to a specified window handle on a specified tablet + The handle of the window to which the InkCollector object is attached + The Tablet object to which the new window of the InkCollector object is attached. + + + Initializes a new instance of the InkCollector class + The control to which the InkCollector object is attached. + The Tablet object to which the new window of the InkCollector object is attached. + + + + + + + + + + + + + + + + + + + Sets whether or not the InkCollector object has interest in a known application gesture. + A member of the ApplicationGesture enumeration, which indicates the gesture to set the status of + Whether or not the InkCollector object has interest in a known application gesture. + + + + + Returns a value that indicates whether the InkCollector object has interest in a particular application gesture. + A member of the ApplicationGesture enumeration that represents the gesture to query about. + Whether the InkCollector object has interest in a particular application gesture. + + + + + Sets the window rectangle, in pixels, within which ink is drawn. + + + + + Gets the window rectangle, in pixels, within which ink is drawn. + The rectangle in which ink is drawn. + + + + + Sets the InkCollector object to collect ink from any tablet attached to the Tablet PC. + + + Sets the InkCollector object to collect ink from any tablet attached to the Tablet PC. + The Boolean value that indicates whether to use the mouse as an input device + + + Sets the InkCollector object to collect ink from only one tablet attached to the Tablet PC + The Tablet object on which ink is collected. + + + Allows derived classes to modify the default behavior of the MouseDown event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseMove event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseUp event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the MouseWheel event. + The CancelMouseEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the DoubleClick event. + The CancelEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Stroke event. + + + The InkCollectorStrokeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorDown event. + + + The InkCollectorCursorDownEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the NewPackets event. + + + The InkCollectorNewPacketsEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the NewInAirPackets event. + + + The InkCollectorNewInAirPacketsEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorButtonDown event. + + + The InkCollectorCursorButtonDownEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorButtonUp event. + + + The InkCollectorCursorButtonUpEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorInRange event. + + + The InkCollectorCursorInRangeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the CursorOutOfRange event. + + + The InkCollectorCursorOutOfRangeEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the SystemGesture event. + + + The InkCollectorSystemGestureEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the Gesture event. + + + The InkCollectorGestureEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the TabletAdded event. + + + The InkCollectorTabletAddedEventArgs object that contains the event data + + + Allows derived classes to modify the default behavior of the TabletRemoved event. + + + The InkCollectorTabletRemovedEventArgs object that contains the event data + + + + + + + + + + + + + Occurs when the cursor tip contacts the digitizing tablet surface + + + Occurs when the mouse pointer is over the InkCollector and a mouse button is pressed + + + Occurs when the mouse pointer is moved over the InkCollector + + + Occurs when the mouse pointer is over the InkCollector and a mouse button is released + + + Occurs when the mouse wheel moves while the InkCollector object has focus + + + Occurs when the InkCollector object is double-clicked + + + Occurs when the user finishes drawing a new stroke on any tablet + + + Occurs when the InkCollector receives packets + + + Occurs when an in-air packet is seen, which happens when a user moves a pen near the tablet and the cursor is within the InkCollector object's window or the user moves a mouse within the InkCollector object's associated window + + + Occurs when a cursor enters the physical detection range (proximity) of the tablet context + + + Occurs when a cursor leaves the physical detection range (proximity) of the tablet context + + + Occurs when the InkCollector detects a cursor button that is down + + + Occurs when the InkCollector detects a cursor button that is up + + + Occurs when a Tablet is added to the system + + + Occurs when a Tablet is removed from the system + + + Occurs when a system gesture is recognized + + + Occurs when an application-specific gesture is recognized + + + Gets or sets the handle of the window to which the InkCollector object is attached. + + + + + Gets or sets the control to which the InkCollector object is attached. + + + + + Gets or sets a value that specifies whether the InkCollector object repaints the ink when the window is invalidated. + + + + + Gets or sets a value that indicates whether ink is rendered as it is drawn. + + + Gets a value that specifies whether ink is currently being drawn on an InkCollector object. + + + + + Gets or sets the default DrawingAttributes object, which specifies the drawing attributes that are used when drawing and displaying ink. + + + + + Gets or sets the Renderer object that is used to draw ink. + + + Gets the Cursors collection that is available for use in the inking region + + + + + Gets or sets the Ink object that is associated with the InkCollector object. + + + + + Gets or sets a value that specifies whether the InkCollector object collects pen input. + + + + + Gets or sets the collection mode that determines whether ink, gesture, or both are recognized as the user writes. + + + Gets or sets interest in aspects of the packet associated with ink drawn on the InkCollector object. + + + + + Gets the tablet device that the InkCollector object is currently using to collect input. + + + + + Gets or sets the cursor that appears when the mouse pointer is over the InkPicture control. + + + Gets or sets a value that specifies whether ink is rendered as just one color when the system is in High Contrast mode. + + + Gets or sets the margins along the x-axis, in pixels. + + + Gets or sets the margins along the y-axis, in pixels. + + + Defines values that specify how ink is inserted onto the InkEdit control. + + + Specifies drawn ink is inserted as text. + + + Specifies drawn ink is inserted as ink. + + + Defines values that specify the collection mode of the InkEdit control. + + + Specifies that ink collection is disabled + + + Specifies that only ink is collected, creating a stroke. + + + Specifies that ink is collected and single stroke application gesture are accepted. + + + Defines values that specify how a selection appears on the InkEdit control. + + + Specifies the selection appears as text. + + + Specifies the selection appears as ink. + + + Indicates the collection status of the InkEdit control. + + + Indicates the control is idle (not collecting or recognizing ink). + + + Indicates the control is collecting ink. + + + Indicates the control is recognizing ink. + + + Represents the method that handles the Stroke event of an InkEdit control. + [in] Specifies the source InkEdit of this event. + [in] Specifies the InkEditStrokeEventArgs object that contains the event data. + + + Provides data for Stroke events, which occur when the user draws a new stroke on an InkEdit control. + + + Initializes a new instance of the InkEditStrokeEventArgs class. + + + The Cursor that was used to create the Stroke object. + The Stroke object that triggers the Stroke event. + Set to true to cancel collection of the Stroke object; otherwise false. + + + Gets the Cursor object that generated the Stroke event. + + + Gets the Stroke object that generated the Stroke event. + + + Represents the method that handles the Recognition event of an InkEdit control. + [in] Specifies the source InkEdit of this event. + [in] Specifies the InkEditRecognitionEventArgs object that contains the event data. + + + Provides data for Recognition events, which occur when the InkEdit control gets results manually from a call to the Recognize method or automatically after the recognition timeout fires. + + + Initializes a new instance of the InkEditRecognitionEventArgs class. + + + The RecognitionResult object that contains the result of the Recognition event. + + + Gets the RecognitionResult object that contains the results of the Recognition event. + + + Represents the method that handles the Gesture event of an InkEdit control. + [in] Specifies the source InkEdit of this event. + [in] Specifies the InkEditGestureEventArgs object that contains the event data. + + + Provides data for Gesture events, which occur when an application-specific gesture is recognized in an InkEdit control. + + + Initializes a new instance of the InkEditGestureEventArgs class. + + + The Cursor that was used to create the application gesture. + The Strokes collection that makes up the application gesture. + The array of Gesture objects recognized, in order of confidence. + Set to true if the gesture was handled in the Gesture event handler (default value); otherwise false. + + + Gets the Cursor object that generated the Gesture event. + + + Gets the Strokes collection that the recognizer returned as the application gesture. + + + Gets an array of Gesture objects, in order of confidence, from the recognizer. + + + The InkEdit control enables you to collect ink, recognize it, and display it as text + + + Sets whether or not the InkEdit control has interest in a known application gesture. + + + A member of the ApplicationGesture enumeration, which indicates the gesture to set the status of + Whether or not the InkEdit control has interest in a known application gesture. + + + Returns a value that indicates whether the InkEdit object has interest in a particular application gesture. + + + A member of the ApplicationGesture enumeration that represents the gesture to query about. + Whether the InkEdit control has interest in a particular application gesture. + + + Causes the ink on the InkEdit control to be recognized. + + + + + Allows derived classes to modify the default behavior of the Stroke event. + + + The InkEditStrokeEventArgs object that contains the event data. + + + Allows derived classes to modify the default behavior of the Recognition event. + + + The InkEditRecognitionEventArgs object that contains the event data. + + + Allows derived classes to modify the default behavior of the Gesture event. + + + The InkEditGestureEventArgs object that contains the event data. + + + Initializes a new instance of the InkEdit control. + + + Releases the unmanaged resources used by the InkEdit control and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Allows derived classes to modify the default behavior of the HandleCreated event. + The EventArgs object that contains the event data. + + + Processes <tla rid="win" /> messages + The <tla rid="win" />Message to process. + + + Occurs when the user finishes drawing a new stroke on any tablet + + + Occurs when the InkEdit control gets results manually from a call to the Recognize method or automatically after the recognition timeout fires + + + Occurs when an application-specific gesture is recognized + + + Gets or sets a value from the InkMode enumeration type that indicates whether the InkEdit control collects ink, gesture, or both. + + + Gets or sets the length of time, in milliseconds, between the last stroke collected and the onset of text recognition. + + + Gets or sets a value from the InkInsertMode enumeration that indicates how ink is inserted onto the control. + + + Gets or sets the drawing attributes to apply to ink as it is drawn + + + + + Gets or sets the Recognizer object used by the InkEdit control. + + + + + Gets or sets the string name of the factoid used by the InkEdit control. + + + + + Gets or sets the array of embedded Ink objects (if displayed as ink) in the current selection. + + + Gets or sets a value that indicates whether the selected ink appears as ink or text. + + + Gets or sets a value that indicates whether the mouse can be used as an input device for the InkEdit control. + + + Gets or sets the Cursor that appears when the mouse pointer is over the control. + + + Gets a value that indicates whether the InkEdit control is idle, collecting ink, or recognizing ink. + + + + + Gets a value that indicates whether the InkEdit control is being disposed of. + + + Gets an overridden version of the CreateParams property that contains the required creation parameters for when the control handle is created. + + + + + + + + + + + + + + + Represents the ability to analyze the layout of a collection of strokes and divide them into text and graphics. + + + Initializes a new instance of the Divider class. + + + Frees the resources of the current Divider object before it is reclaimed by the garbage collector. + + + Releases resources used by the Divider object. + + + + + + + Initializes a new instance of the Divider class. + The Strokes collection to place in the Strokes property of the new Divider object. + + + + + + + + + + + Returns a DivisionResult object that contains structural information about the Strokes property of the Divider object. + Returns a DivisionResult object that contains structural information about the Strokes property of the Divider object. + + + + + + + + + + + Gets or sets the expected handwriting height, in HIMETRIC units. + + + Gets or sets the RecognizerContext object that the Divider object uses for handwriting analysis. + + + + + Gets or sets the Strokes collection on which the Divider object performs ink analysis. + + + + + Defines values for the structural types within the DivisionResult object. + + + A recognition segment. + + + A line of handwriting that contains one or more recognition segments. + + + A block of strokes that contains one or more lines of handwriting. + + + ink that is not text. + + + Represents the layout analysis of the collection of strokes contained by the Divider object. + + + Gets the requested structural units of the analysis results for a DivisionUnits collection. + Returns the DivisionUnits collection containing the requested structural units of the analysis results. + One of the values of the InkDivisionType enumeration, which indicates the structural units to return + + + Gets the Strokes collection that applies to this DivisionResult. + + + Represents a single structural element within a DivisionResult object. + + + Frees the resources of the current DivisionUnit object before it is reclaimed by the garbage collector. + + + Returns the recognized text for the Strokes collection in the DivisionUnit object. + Returns the recognized text for the Strokes collection in the DivisionUnit object, or null (Nothing in Microsoft Visual Basic .NET) for drawing elements. + + + Gets the structural type of the DivisionUnit object. + + + Gets the transformation matrix that the DivisionUnit object uses to rotate the strokes to horizontal. + + + Gets the recognized text for the Strokes collection in the DivisionUnit object. + + + Gets the Strokes collection that was used to create the DivisionUnit object + + + Contains a collection of DivisionUnit objects that are contained in a DivisionResult object. + + + + + + + + + + + + + + + + + + + + + Copies all of the elements of the current DivisionUnits collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns an object that implements the IEnumerator interface and that can iterate through the DivisionUnit objects within the DivisionUnits collection. + Returns an object that implements the IEnumerator interface and that can iterate through the DivisionUnit objects within the DivisionUnits collection. + + + + + + + Gets an object that can be used to synchronize access to the DivisionUnits collection. + + + Gets the number of DivisionUnit objects contained in the DivisionUnits collection. + + + Gets a value that indicates whether or not access to the DivisionUnits collection is synchronized (thread safe). + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over a DivisionUnits collection. + + + Initializes a new instance of the InkDivisionUnitsEnumerator object + The DivisionUnit collection that this enumerator iterates over + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the DivisionUnits collection. + + + + + + + + + Gets the DivisionUnit object in the DivisionUnits collection to which the enumerator is pointing. + + + + + Defines the type of input currently available in the PenInputPanel object. + + + Specifies the PenInputPanel object displays the last panel type used for any pen input panel in any application + + + Specifies the PenInputPanel object does not accept input + + + Specifies the PenInputPanel object displays the default handwriting panel for the current input language. + + + Specifies the PenInputPanel object displays the default keyboard panel for the current input language. + + + The PenInputPanel object enables you to easily add in-place pen input to your applications. + + + Initializes a new instance of the PenInputPanel class + + + Initializes a new instance of the PenInputPanel class and attaches it to a window handle. + The window handle to attach the PenInputPanel object to. + + + Creates a PenInputPanel object and attaches it to the specified control. + The control to attach the PenInputPanel object to. + + + Frees the resources of the current PenInputPanel object before it is reclaimed by the garbage collector. + + + Releases resources used by the PenInputPanel object. + + + Releases the unmanaged resources used by the PenInputPanel object and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + + + Sets the position of the PenInputPanel object to a defined screen position. + + + The new x-axis position of the left edge of the PenInputPanel object, in screen coordinates. + The new y-axis position of the top edge of the PenInputPanel object, in screen coordinates. + + + Sends collected ink to the recognizer and posts the recognition result. + + + Updates and restores the PenInputPanel properties based on Tablet PC Input Panel, automatically positions the pen input panel, and sets the user interface to the default panel. + + + Indicates whether or not the PenInputPanel object attempts to send text to the attached control through the Text Services Framework (TSF) and enables the use of the correction user interface. + + + A Boolean variable that indicates whether or not the PenInputPanel object attempts to send text to the attached control through the Text Services Framework (TSF) and enables the use of the correction user interface. + + + + + + + Allows derived classes to modify the default behavior of the VisibleChanged event. + + + + + + + + + + + Allows derived classes to modify the default behavior of the PanelMoving event. + The PenInputPanelMovingEventArgs object that contains the event data + + + + + + + + + Allows derived classes to modify the default behavior of the PanelChanged event. + The PenInputPanelChangedEventArgs object that contains the event data + + + + + + + + + Allows derived classes to modify the default behavior of the InputFailed event. + The PenInputPanelInputFailedEventArgs object that contains the event data + + + + + Gets or sets the window handle that the PenInputPanel object is attached to. + + + Gets or sets the control that the PenInputPanel object is attached to. + + + Gets a Boolean value that indicates whether the PenInputPanel object is currently processing ink. + + + Gets or sets the string name of the factoid used by the PenInputPanel object. + + + + + Gets or sets which panel type is currently being used for input within the PenInputPanel object. + + + + + Gets or sets the default panel type used for input within the PenInputPanel object. + + + + + Gets or sets a Boolean value that indicates whether the pen input panel appears when focus is set on the attached control by using the pen. + + + Gets or sets a value that indicates whether the PenInputPanel object is visible. + + + + + Gets the vertical, or y-axis, location of the top edge of the PenInputPanel object, in screen coordinates. + + + Gets the horizontal, or x-axis, location of the left edge of the PenInputPanel object, in screen coordinates. + + + Gets the width of the pen input panel in client coordinates. + + + + + Gets the height of the pen input panel in client coordinates. + + + + + Gets or sets the offset between the closest horizontal edge of the pen input panel and the closest horizontal edge of the control to which it is attached. + + + + + Gets or sets the offset between the left edge of the pen input panel and the left edge of the control to which it is attached. + + + + + Occurs when the PenInputPanel object has shown or hidden itself. + + + Occurs when the PenInputPanel object is moving. + + + Occurs when the PenInputPanel object changes between layouts. + + + Occurs when input focus changes before the PenInputPanel object was able to insert user input into the attached control. + + + Represents the method that handles the VisibleChanged event of a PenInputPanel object. + [in] Specifies the source PenInputPanel object of this event. + [in] Specifies the PenInputPanelVisibleChangedEventArgs object that contains the event data. + + + Provides data for VisibleChanged events, which occur when the PenInputPanel object has shown or hidden itself. + + + Initializes a new instance of the PenInputPanelVisibleChangedEventArgs class. + Set to true if the PenInputPanel object has become visible; otherwise false + + + Gets a value that indicates whether the PenInputPanel object has become visible. + + + Represents the method that handles the PanelMoving event of a PenInputPanel object. + [in] Specifies the source PenInputPanel object of this event. + [in] Specifies the PenInputPanelMovingEventArgs object that contains the event data. + + + Provides data for PanelMoving events, which occur when a PenInputPanel is moving. + + + Initializes a new instance of the PenInputPanelMovingEventArgs class. + The new horizontal position, in screen coordinates. + The new vertical position, in screen coordinates. + + + Gets the new horizontal, or x-axis, position of the left edge of the PenInputPanel object, in screen coordinates. + + + Gets the new vertical, or y-axis, position of the PenInputPanel object, in screen coordinates. + + + Represents the method that handles the PanelChanged event of a PenInputPanel object. + [in] Specifies the source PenInputPanel object of this event. + [in] Specifies the PenInputPanelChangedEventArgs object that contains the event data. + + + Provides data for PanelChanged events, which occur when a PenInputPanel object changes between layouts. + + + Initializes a new instance of the PenInputPanelChangedEventArgs class. + A member of the PanelType enumeration that indicates the type of panel the PenInputPanel object has changed to. + + + Gets the new panel type used for input within the PenInputPanel object, after the PanelChanged event fires. + + + Represents the method that handles the InputFailed event of a PenInputPanel object. + [in] Specifies the source PenInputPanel object of this event. + [in] Specifies the PenInputPanelInputFailedEventArgs object that contains the event data. + + + Provides data for InputFailed events, which occur when the input focus changes before a PenInputPanel object was able to insert user input into the attached control. + + + Initializes a new instance of the PenInputPanelInputFailedEventArgs class class. + The state of keys pressed, including SHIFT, CAPS LOCK, CTRL, and ALT. + The string returned from the recognizer, intended to be inserted into the control represented by the Handle property. + The handle of the control that invoked the PenInputPanel object. + + + Gets the handle of the control that invoked the PenInputPanel object. + + + Gets the string that was to be inserted into the control when the InputFailed event fired. + + + Represents general information about a tablet pointing and selecting device. + + + Returns a string that represents the current Cursor object. + Returns a string that represents the current Cursor object. + + + Gets the name of the Cursor object. + + + Gets the identifier of the Cursor object. + + + Gets a value that indicates whether the cursor is the inverted end of the pen. + + + Gets or sets the drawing attributes to apply to ink as it is drawn + + + + + Gets the tablet device to which the Cursor belongs. + + + Returns the CursorButtons collection that is available on a known Cursor. + + + Defines values that specify the state of a cursor button. + + + Shows that the cursor button is unavailable + + + Shows that the cursor button is up + + + Shows that the cursor button is down + + + Represents general information about a button on a tablet pointing and selecting device. + + + Returns a string that represents the current CursorButton object. + Returns a string that represents the current CursorButton object. + + + Gets the name of the CursorButton object. + + + Gets the identifier of the CursorButton object. + + + Gets the state of the CursorButton object. + + + Represents a collection of CursorButton objects for a Cursor object. + + + Copies all of the elements of the current CursorButtons collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns an object that implements the IEnumerator interface and that can iterate through the CursorButton objects within the CursorButtons collection. + Returns an object that implements the IEnumerator interface and that can iterate through the CursorButton objects within the CursorButtons collection. + + + + + + + Returns an object that can be used to synchronize access to the CursorButtons collection. + + + Gets the number of CursorButton objects contained in the CursorButtons collection. + + + Gets a value that indicates whether or not access to the CursorButtons collection is synchronized (thread safe). + + + + + + + + + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over a CursorButtons collection. + + + Initializes a new instance of the CursorButtonsEnumerator class + The CursorButton collection that this enumerator iterates over + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the CursorButtons collection. + + + + + + + + + Gets the CursorButton object in the CursorButtons collection to which the enumerator is pointing. + + + + + Represents a collection of Cursor objects. + + + Copies all of the elements of the current Cursors collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns an object that implements the IEnumerator interface and that can iterate through the Cursor objects within the Cursors collection. + Returns an object that implements the IEnumerator interface and that can iterate through the Cursor objects within the Cursors collection. + + + + + + + Returns an object that can be used to synchronize access to the Cursors collection. + + + Gets the number of Cursors objects contained in the Cursors collection. + + + Gets a value that indicates whether or not access to the Cursors collection is synchronized (thread safe). + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over a Cursors collection. + + + Initializes a new instance of the CursorsEnumerator class. + The Cursor collection that this enumerator iterates over + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the Cursors collection. + + + + + + + + + Gets the Cursor object in the Cursors collection to which the enumerator is pointing. + + + + + Contains a collection of user-defined Strokes collections. + + + + + Clears all the Strokes collections from the CustomStrokes collection + + + + + Adds a Strokes collection to the CustomStrokes collection. + The name of the Strokes collection to add to the CustomStrokes collection. + The Strokes collection to add to the CustomStrokes collection. + + + Removes a Strokes collection from a CustomStrokes collection + The name that was used to add the Strokes collection to the CustomStrokes collection + + + Removes a Strokes collection at the specified index of the CustomStrokes collection + The index of the Strokes collection to be removed from the CustomStrokes collection + + + Copies all of the elements of the current CustomStrokes collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns an object that implements the IEnumerator interface and that can iterate through the Stroke collections within the CustomStrokes collection. + Returns an object that implements the IEnumerator interface and that can iterate through the Strokes collections within the CustomStrokes collection. + + + + + + + + + Gets the number of Strokes objects contained in the CustomStrokes collection. + + + Gets a value indicating whether the CustomStrokes collection has a fixed size. + + + + + Gets a value that indicates whether or not access to the CustomStrokes collection is synchronized (thread safe). + + + + + Returns an object that can be used to synchronize access to the CustomStrokes collection. + + + Gets a value indicating whether the CustomStrokes collection is read-only. + + + + + + + + + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over a CustomStrokes collection. + + + Initializes a new instance of the CustomStrokesEnumerator class + The CustomStrokes collection that this enumerator iterates over + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the CustomStrokes collection. + + + + + + + + + Gets the Strokes collection in the CustomStrokes collection to which the enumerator is pointing. + + + + + Defines values that specify the shape of the pen tip. + + + Specifies a round pen tip. + + + Specifies a rectangular pen tip. + + + Defines values for performing raster operations on drawn ink + + + Specifies black pen color. + + + Specifies the inverse of MergePen. + + + Specifies a combination of the colors are common to the background color and the inverse of the pen. + + + Specifies the inverse of CopyPen. + + + Specifies a combination of the colors are common to both the pen and the inverse of the display. + + + Specifies the inverse of the display color. + + + Specifies a combination of the colors in the pen and in the display color, but not in both. + + + Specifies the inverse of MaskPen. + + + Specifies a combination of the colors common to both the pen and the display. + + + Specifies an inverse of XOrPen. + + + Specifies no operation; the output remains unchanged. + + + Specifies a combination of the display color and the inverse of the pen color. + + + Specifies the pen color. + + + Specifies a combination of the pen color and the inverse of the display color. + + + Specifies a combination of the pen color and the display color. + + + Specifies a white pen color. + + + Represents the attributes that are applied to ink when it is drawn. + + + Initializes a new instance of the DrawingAttributes class. + + + Initializes a new instance of the DrawingAttributes class. + The specific color to which the DrawingAttributes object is initialized. + + + Initializes a new instance of the DrawingAttributes class. + The specific pen width to which the DrawingAttributes object is initialized. + + + Initializes a new instance of the DrawingAttributes class. + The Pen object from which the pen color and width for the DrawingAttributes object is taken. + + + + + + + + + Returns a copy of this DrawingAttributes object. + The new copy of the DrawingAttributes object. + + + Gets or sets the color of the ink that is drawn with this DrawingAttributes object. + + + Gets or sets the value that indicates whether a stroke is antialiased. + + + + + Gets or sets the y-axis dimension, or width, of the pen tip when drawing ink. + + + + + Gets or sets the y-axis dimesion, or height, of the pen tip when drawing ink. + + + + + Gets or sets the value that indicates whether Bezier smoothing is used to render ink. + + + + + Gets or sets the value that indicates whether ink gets wider with increased pressure of the pen tip on the tablet surface. + + + Gets or sets a value that indicates the transparency value of ink. + + + Gets or sets a value that defines how the colors of the pen and background interact. + + + Gets or sets a value that indicates which pen tip to use when drawing ink that is associated with this DrawingAttributes object. + + + Gets the collection of application-defined data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Represents a collection of ExtendedProperty objects that contain application-defined data. + + + Clears all the ExtendedProperty objects from the ExtendedProperties collection + + + + + Returns the index of a specific ExtendedProperty object within an ExtendedProperties collection, based on the name of the ExtendedProperty object. + The ExtendedProperty object to check for + Returns the index of the ExtendedProperty object within an ExtendedProperties collection + + + Returns the index of a specific ExtendedProperty object within an ExtendedProperties collection, based on the globally unique identifier (GUID) for the ExtendedProperty object. + The Guid of the ExtendedProperty object to check for + Returns the index of the ExtendedProperty object within an ExtendedProperties collection + + + Indicates whether the ExtendedProperties collection contains a specific ExtendedProperty object. + The ExtendedProperty object to check for. + Whether the ExtendedProperties collection contains a specific ExtendedProperty object. + + + Indicates whether the ExtendedProperties collection contains a specific ExtendedProperty object. + The globally unique identifier (GUID) of the ExtendedProperty object to check for. + Whether the ExtendedProperties collection contains a specific ExtendedProperty object. + + + Creates an ExtendedProperty object and adds it to the ExtendedProperties collection. + The identifier of the new ExtendedProperty object. + The data for the new ExtendedProperty object. + The new ExtendedProperty object. + + + Removes an ExtendedProperty object from the ExtendedProperties collection + The globally unique identifier (GUID) of the ExtendedProperty object to remove from the ExtendedProperties collection + + + Removes an ExtendedProperty object from the ExtendedProperties collection + The ExtendedProperty object to remove from the ExtendedProperties collection + + + Removes an ExtendedProperty object at the specified index of the ExtendedProperties collection + The index of the ExtendedProperty object to be removed from the ExtendedProperties collection + + + Indicates whether a specified ExtendedProperty object exists in the ExtendedProperties collection. + The globally unique identifier (GUID) of the property to check for + Whether the specified ExtendedProperty object exists in the ExtendedProperties collection. + + + Copies all of the elements of the current ExtendedProperties collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns an object that implements the IEnumerator interface that can iterate through the ExtendedProperty objects within the ExtendedProperties collection. + Returns an object that implements the IEnumerator interface that can iterate through the ExtendedProperty objects within the ExtendedProperties collection. + + + + + + + Gets the number of ExtendedProperty objects contained in the ExtendedProperties collection. + + + Gets a value indicating whether the ExtendedProperties collection has a fixed size. + + + + + Gets a value indicating whether the ExtendedProperties collection is read-only. + + + + + Gets a value that indicates whether or not access to the ExtendedProperties collection is synchronized (thread safe). + + + + + Returns an object that can be used to synchronize access to the ExtendedProperties collection. + + + + + + + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over an ExtendedProperties collection. + + + Initializes a new instance of the ExtendedPropertiesEnumerator class. + The ExtendedProperties collection that this enumerator iterates over. + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the ExtendedProperties collection. + + + + + + + + + Gets the ExtendedProperty object in the ExtendedProperties collection to which the enumerator is pointing. + + + + + Represents the ability to add your own data to a variety of objects within the Tablet PC object model. + + + Gets the identifier of the ExtendedProperty object. + + + + + Gets or sets the data for the ExtendedProperty object + + + + + Defines values that indicate the level of confidence that the recognizer has in the accuracy of the recognition result. + + + Indicates strong confidence in the result or alternate. + + + Indicates intermediate confidence in the result or alternate. + + + Indicates poor confidence in the result or alternate. + + + Defines values for the set of available application-specific gesture. + + + Recognizes all application-specific gestures. + + + Recognizes no application-specific gestures. + + + Erases content + + + Inserts input + + + Marks an action item + + + Marks an action item + + + Denotes a check-off + + + Cuts a word + + + Copies a word + + + Has no suggested semantic behavior or action + + + Pastes a selection + + + Undoes an action + + + Redoes an action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Specifies a backspace + + + Signifies a space + + + Undoes an action + + + Has no suggested semantic behavior or action + + + Cuts a selection + + + Copies a selection + + + Decreases the indent + + + Signifies pressing a TAB key + + + Signifies pressing an ENTER key + + + Signifies pressing the spacebar + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Signifies an Input Method Editor (IME) convert + + + Has no suggested semantic behavior or action + + + Has no suggested semantic behavior or action + + + Signifies a mouse click + + + Signifies a mouse double-click + + + Represents the ability to query particular properties of a gesture returned from a gesture recognition. + + + Gets the value of the application gesture. + + + Gets the hot point of the gesture, in ink space coordinates. + + + Gets the level of confidence (strong, intermediate, or poor) that a recognizer has in the recognition of a gesture + + + Defines values that determine how strokes are extracted from an Ink object. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. + + + Copies ink from the Ink object. + + + Cuts ink from the Ink object. + + + Cuts ink from the Ink object. + + + Defines values that specify how ink is persisted. + + + Specifies ink that is persisted using Ink Serialized Format (ISF). This is the most compact persistent representation of ink + + + Specifies ink that is persisted by encoding the ink serialized format (ISF) as a base64 stream. This format is provided so ink can be encoded directly in an Extensible Markup Language (XML) or HTML file. + + + Specifies ink that is persisted by using a Graphics Interchange Format (GIF) file that contains Ink Serialized Format (ISF) as metadata embedded within the file. This allows ink to be viewed in applications that are not ink-enabled and maintain its full ink fidelity when it returns to an ink-enabled application + + + Specifies ink that is persisted by using a base64 encoded fortified Graphics Interchange Format (GIF). This format is provided when ink is to be encoded directly in an Extensible Markup Language (XML) or HTML file with later conversion into an image + + + Defines values for the compression modes that are used to save the Ink object to a serialized format. + + + Provides the optimum balance between saving time and minimizing storage space + + + Maximizes compression, thereby minimizing required storage space + + + Minimizes the time to save the ink + + + Specifies which characteristics of a stroke, such as drawing attributes, are used to calculate the bounding box of the ink. + + + Use the definition of each stroke. + + + Use the polyline of the strokes. + + + Use the Bezier curve fitting line of the strokes. + + + Use only the points of the strokes. + + + The union of NoCurveFit and CurveFit request. + + + Defines values that specify the copy options of the Clipboard. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. + + + Copies the ink to the Clipboard. + + + Cuts the ink and copies it to the Clipboard. + + + Does not copy the ink to the Clipboard + + + Uses delayed rendering to reduce the amount of data that is stored on the Clipboard + + + The Copy mode. + + + Defines values that specify the format of ink that is stored on the Clipboard. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. + + + Specifies a flag that can be used to verify whether any formats are present by checking against it. + + + Specifies ink is encoded in Ink Serialized Format (ISF) + + + Specifies ink is not expected to form words, but rather is interpreted as a picture + + + Specifies ink is expected to form words + + + Specifies the enhanced metafile to play to create the background + + + Specifies ink is stored as a metafile or a list of commands that can be played back to draw a graphic. + + + Specifies the bitmap to use as the background + + + Specifies the formats that can be used for pasting. This format includes the TextInk, SketchInk, and InkSerializedFormat formats. + + + Specifies the formats that are copied to the Clipboard through ink. + + + The CopyMask format. + + + Represents the collected strokes of ink within an ink space. + + + Returns a string that contains the name of the format for ink serialized format (ISF) for querying the Clipboard. + + + + + + + + + + + + + Initializes a new instance of the Ink class. + + + Releases resources used by the Ink object. + + + Releases the unmanaged resources used by the Ink object and optionally releases the managed resources. + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Frees the resources of the current Ink object before it is reclaimed by the garbage collector. + + + + + + + + + Creates a copy of this Ink object. + The new copy of the original Ink object. + + + Returns the Strokes collection contained within a known Rectangle. + The selection rectangle, in ink space coordinates. + <condition type="signature" criteria="sig_Rectangle_Single">The percentage value that determines which Stroke objects are included in the Strokes collection + Returns the Strokes collection contained within the specified area. + + + Returns the Strokes collection contained within a polyline selection boundary. + The points that are used in the selection boundary to select the Stroke objects + <condition type="signature" criteria="sig_Rectangle_Single">The percentage value that determines which Stroke objects are included in the Strokes collection + When this method returns, contains an out parameter that represents the specific portion of the selection boundary that is used for the selection + Returns the Strokes collection contained within the specified area. + + + + + Returns the Strokes collection contained within a polyline selection boundary. + The points that are used in the selection boundary to select the Stroke objects + <condition type="signature" criteria="sig_Rectangle_Single">The percentage value that determines which Stroke objects are included in the Strokes collection + Returns the Strokes collection contained within the specified area. + + + + + Returns the Strokes collection of Stroke objects that are either completely inside or intersected by a known circle. + The center of the hit test circle, in ink space coordinates. + The radius of the hit test circle, in ink space coordinates. + Returns the Strokes collection contained within the specified area. + + + Returns the Stroke object within the Ink object that is nearest to a specified Point, given in ink space coordinates. + The specified point within the Ink object, given in ink space coordinates. + Returns the Stroke that contains a Point that is closest to the specified point in the Ink object + + + Returns the Stroke object nearest a specified point, and returns the point on the Stroke object that is closest to the specified point. + The specified point within the Ink object, given in ink space coordinates. + The point on the Stroke object that is closest to the specified point within the Ink object + Returns the Stroke that contains a Point that is closest to the specified point in the Ink object + + + Returns the Stroke object nearest a specified point, returns the point on the Stroke object that is closest to the specified point, and returns the distance between the specified point and the nearest point on the Stroke object in the Ink object. + The specified point within the Ink object, given in ink space coordinates. + The point on the Stroke object that is closest to the specified point within the Ink object + The distance between the specified point and the nearest Stroke object in the Ink object. + Returns the Stroke that contains a Point that is closest to the specified point in the Ink object + + + Deletes a Stroke object from the Ink object. + The Stroke object to delete. + + + Deletes the specified Strokes collection from the Ink object. + The Strokes collection to delete. + + + Deletes a Strokes collection from the Ink object + + + Extracts the specified Stroke objects from the Ink object by using either cut or copy, as specified, and returns a new Ink object containing the extracted Stroke objects. + + + The Strokes collection to extract. + One of the ExtractFlags values that specifies whether the ink is cut or copied into the new Ink object. + Returns an Ink object that contains the extracted Strokes collection. + + + Extracts the specified Stroke objects from the Ink object and returns a new Ink object containing the extracted Stroke objects. + The Strokes collection to extract. + Returns an Ink object that contains the extracted Strokes collection. + + + Extracts Stroke objects from the Ink object and returns a new Ink object containing the extracted Stroke objects + Returns an Ink object that contains the extracted Strokes collection. + + + Extracts the all the Stroke objects within the bounds of a specified rectangle from the Ink object by using either cut or copy, as specified, and returns a new Ink object containing the extracted Strokes collection. + The Rectangle that delimits the ink to extract from the Ink object. + One of the ExtractFlags values that specifies whether the ink is cut or copied into the new Ink object. + Returns an Ink object that contains the extracted Strokes collection. + + + Extracts the all the Stroke objects that are within the bounds of a specified rectangle, from the Ink object, and returns a new Ink object containing the extracted Strokes collection. + The Rectangle that delimits the ink to extract from the Ink object. + Returns an Ink object that contains the extracted Strokes collection. + + + Removes the portions of a Stroke object or Strokes collection that are outside a given rectangle. + The rectangle outside of which the Stroke object or Strokes collection are clipped + + + Returns the bounding Rectangle that contains all of the Stroke objects in the Ink object, by using the specified BoundingBoxMode flag to determine the bounds. + A member of the BoundingBoxMode enumeration, which specifies which characteristics of a stroke, such as drawing attributes, are used to calculate the bounding box of the ink. + + + Returns the Rectangle that defines the bounding box of the Strokes collection in the Ink object. + + + Returns the bounding Rectangle that contains all of the Stroke objects in the Ink object + + + Returns the Rectangle that defines the bounding box of the Strokes collection in the Ink object. + + + Creates a Strokes collection for this Ink object + Returns a new Strokes collection. + + + Creates a Strokes collection based on specified Id properties of Stroke objects. + Returns a new Strokes collection. + An array of specified Id properties for Stroke objects that exist in the Ink object + + + Specifies the known Strokes collection to insert into this Ink object at a specified rectangle. + The Strokes collection to add to the Ink object + The rectangle where the strokes are added, in ink space coordinates. + + + Populates a new Ink object with known binary data. + The byte array that contains the ink data. + + + Converts the Ink object to the specified format, saves it by using the specified compression format, and returns the binary data in a Byte array. + A memeber of the PersistenceFormat enumeration that indicates the format of the persisted ink. + A member of the CompressionMode enumeration that specifies the compression mode of the persisted ink. + Returns the Byte array that contains the persisted ink. + + + Converts the Ink object to the specified format, saves it by using the CompressionMode compression mode, and returns the binary data in a Byte array. + A memeber of the PersistenceFormat enumeration that indicates the format of the persisted ink. + Returns the Byte array that contains the persisted ink. + + + + + Converts the Ink object to a specified format and returns the binary data in a Byte array. + Returns the Byte array that contains the persisted ink. + + + + + Creates a Stroke object from an array of Point input values. + The array of points that make up the Stroke object. + The newly created stroke. + + + Creates a Stroke object from packet data. + The array of packet data used to make up the Stroke object. + The TabletPropertyDescriptionCollection collection that describes what properties are in the CreateStroke array. + The newly created stroke. + + + Copies the Ink object to the Clipboard. + A member of the InkClipboardFormats enumeration that specifies the format for the Ink object + A member of the InkClipboardModes enumeration that specifies the mode for the Ink object + Returns the data object to be created + + + + + Copies a specified Strokes collection to the Clipboard. + The Strokes collection to copy. + A member of the InkClipboardFormats enumeration that specifies the format for the Ink object + A member of the InkClipboardModes enumeration that specifies the mode for the Ink object + Returns the data object to be created + + + + + Copies the Strokes collection contained in the specified rectangle to the Clipboard. + The rectangle that contains the Strokes collection to copy to the Clipboard. + A member of the InkClipboardFormats enumeration that specifies the format for the Ink object + A member of the InkClipboardModes enumeration that specifies the mode for the Ink object + Returns the data object to be created + + + + + + + Returns a value that indicates whether data (either on the Clipboard, or as an IDataObject) can be converted to an Ink object + Whether the data can be converted to an Ink object. + + + + + Returns a value that indicates whether the IDataObject can be converted to an Ink object. + The IDataObject to inspect + Whether the data can be converted to an Ink object. + + + + + Pastes an IDataObject, either specified or from the Clipboard, to this Ink object + Returns the Strokes collection that is pasted to the Ink object. + + + + + + + Pastes an IDataObject from the Clipboard to the specified point in this Ink object. + The point to paste to, in ink space coordinates + Returns the Strokes collection that is pasted to the Ink object. + + + + + Pastes the specified IDataObject to the specified point in this Ink object. + The point to paste to, in ink space coordinates + The IDataObject to paste into this Ink object. + Returns the Strokes collection that is pasted to the Ink object. + + + + + Occurs when a stroke is added to the Ink object + + + + + Occurs when a stroke is deleted from the Ink object + + + + + Gets a copy of the Strokes collection contained in the Ink object. + + + Gets the CustomStrokes collection to be persisted with the ink. + + + Gets or sets the value that indicates whether an Ink object has been modified since the last time the ink was saved. + + + Gets the collection of application-defined data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines values that specify the packet properties. + + + + + The Guid for the PacketProperty object for the x-coordinate in the tablet coordinate space. + + + The Guid for the PacketProperty object for the y-coordinate in the tablet coordinate space. + + + The Guid for the PacketProperty object for the z-coordinate or distance of the pen tip from the tablet surface. + + + The Guid for the PacketProperty object for the current status of the cursor + + + The Guid for the PacketProperty object for the time the packet was generated + + + The Guid for the PacketProperty object for identifying the packet. + + + The Guid for the PacketProperty object that represents pressure of the pen tip perpendicular to the tablet surface. + + + The Guid for the PacketProperty object that represents pressure of the pen tip along the plane of the tablet surface. + + + The Guid for the PacketProperty object for pressure on a pressure sensitive button. + + + The Guid for the PacketProperty object for the angle between the y,z-plane and the pen and y-axis plane. + + + The Guid for the PacketProperty object for the angle between the x,z-plane and the pen and x-axis plane. + + + The Guid for the PacketProperty object for the clockwise rotation of the cursor about the z-axis through a full circular range. + + + The Guid for the PacketProperty object for the angle between the axis of the pen and the surface of the tablet. + + + The Guid for the PacketPropertyobject for the clockwise rotation of the cursor about its own axis. + + + The Guid for the PacketProperty object that indicates whether the tip is above or below a horizontal line that is perpendicular to the writing surface. + + + The Guid for the PacketProperty object for the clockwise rotation of the pen around its own axis. + + + The Guid for the PacketProperty object for the angle of the pen to the left or right around the center of its horizontal axis when the pen is horizontal. + + + Represents the management of mappings from ink to the display window + + + + + + + + + + + + + Initializes a new instance of the Renderer class. + + + Sets the Matrix object that represents the view transform that is used to render ink. + The Matrix object that represents the geometric transformation values-rotation, scaling, shear, and reflection-to use to transform the ink from ink space coordinates to logical device context coordinates + + + + + Sets the Matrix object that represents the object transform that is used to render ink. + The Matrix object that represents the geometric transformation values-rotation, scaling, shear, and reflection-to use to transform the coordinates of the ink, using ink space coordinates + + + + + Identifies the Matrix object that represents the object transform that was used to render ink. + + + + + + + Identifies the Matrix object that represents the object transform that was used to render ink. + + + The Matrix object that represents the geometric transformation values-rotation, scaling, shear, and reflection-to use to transform the stroke coordinates within the ink space + + + Converts a location in pixel space coordinates to be a location in ink space coordinates by using a Graphics object for the conversion. + The Graphics object to use for conversion + The point to convert into an ink space location + + + + + Converts a location in pixel space coordinates to be a location in ink space coordinates by using a handle for the conversion. + The handle of the containing control or form + The point to convert into an ink space location + + + + + Converts a location in ink space coordinates to be a location in pixel space by using a Graphics object for the conversion. + The Graphics object to use for conversion + The point to convert into a pixel location + + + Converts a location in ink space coordinates to be a location in pixel space by using a handle for the conversion. + The handle of the containing control or form + The point to convert into a pixel location + + + Converts an array of locations in pixel space coordinates to be an array of locations in ink space coordinates by using a Graphics object for the conversion + The Graphics object to use for conversion + The array of points to convert into ink space locations + + + + + Converts an array of locations in pixel space coordinates to be an array of locations in ink space coordinates by using a handle for the conversion + The handle of the containing control or form + The array of points to convert into ink space locations + + + + + Converts an array of locations in ink space coordinates to be in pixel space by using a Graphics object for the conversion + The Graphics object to use for conversion + The array of points to convert into pixel locations + + + Converts an array of locations in ink space coordinates to be in pixel space by using a handle for the conversion + The handle of the containing control or form + The array of points to convert into pixel locations + + + Draws the Strokes collection on the specified Graphics surface + The Graphics object with which to draw. + The Strokes collection to draw. + + + Draws the Strokes collection on the device context whose handle is passed in + The handle of the device context on which to draw + The Strokes collection to draw. + + + Draws the Stroke object on the specified Graphics surface. + The Graphics object with which to draw. + The Stroke object to draw. + + + Draws the Stroke object, with DrawingAttributes, on the specified Graphics surface + The Graphics object with which to draw. + The Stroke object to draw. + The DrawingAttributes property to use for the drawing + + + Draws the Stroke object on the device context whose handle is passed in. + The handle of the device context on which to draw + The Stroke object to draw. + + + Draws the Stroke object, with DrawingAttributes, on the device context whose handle is passed in. + The handle of the device context on which to draw + The Stroke object to draw. + The DrawingAttributes property to use for the drawing + + + Calculates the Rectangle on the device context needed to contain the Strokes collection to be drawn with the Draw method of the Renderer object. + The Strokes collection to measure + + + The Rectangle on the device context needed to contain the <condition type="signature" criteria="sig_Stroke sig_Stroke_DrawingAttributes">stroke if the stroke</condition><condition type="signature" criteria="sig_Strokes">strokes if the strokes</condition> were drawn with the Draw method of the Renderer object + + + Calculates the Rectangle on the device context needed to contain the Stroke object to be drawn with the Draw method of the Renderer object. + The Stroke object to measure + + + The Rectangle on the device context needed to contain the <condition type="signature" criteria="sig_Stroke sig_Stroke_DrawingAttributes">stroke if the stroke</condition><condition type="signature" criteria="sig_Strokes">strokes if the strokes</condition> were drawn with the Draw method of the Renderer object + + + Calculates the Rectangle on the device context needed to contain the Stroke object to be drawn with the Draw method of the Renderer object by using the specified DrawingAttributes. + The Stroke object to measure + The DrawingAttributes to use when calculating the rectangle, which override the DrawingAttributes property of the Stroke object + + + The Rectangle on the device context needed to contain the <condition type="signature" criteria="sig_Stroke sig_Stroke_DrawingAttributes">stroke if the stroke</condition><condition type="signature" criteria="sig_Strokes">strokes if the strokes</condition> were drawn with the Draw method of the Renderer object + + + Applies a translation to the GetViewTransform in ink space coordinates. + The amount to translate the view transform in the X dimension, in ink space coordinates + The amount to translate the view transform in the Y dimension, in ink space coordinates + + + Applies a rotation to the GetViewTransform. + The degrees by which to rotate clockwise + The point-in ink space coordinates-around which to rotate + + + Applies a rotation to the GetViewTransform. + The degrees by which to rotate clockwise + + + Scales the GetViewTransform in the X and Y dimensions. + The factor to scale the X dimension of the ink in the view transform + The factor to scale the Y dimension of the ink in the view transform + A value that indicates whether to apply the scale factors to the width of the drawing attributes of the ink in addition to the overall dimensions of the ink + + + Scales the GetViewTransform in the X and Y dimensions. + The factor to scale the X dimension of the ink in the view transform + The factor to scale the Y dimension of the ink in the view transform + + + + + + + + + + + + + Represents a single ink stroke. A stroke is a set of properties and point data that the digitizer captures that represent the coordinates and properties of a known ink mark + + + Returns the bounding Rectangle that contains the Stroke object, by using the specified BoundingBoxMode flag to determine the bounds. + A member of the BoundingBoxMode enumeration that specifies which characteristics of a Stroke object, such as drawing attributes, are used to calculate the bounding box of the Stroke object. + + + Returns the bounding Rectangle that defines the bounding box for the Stroke object. + + + Returns the bounding Rectangle that contains the Stroke object. + + + Returns the bounding Rectangle that defines the bounding box for the Stroke object. + + + Returns the array of actual points that are used to approximate the Bezier representation of a Stroke object + The maximum distance (accuracy), in HIMETRIC units, between the Bezier control points and the points of the Stroke object + Returns a Point array that indicates the points that were used to draw the Bezier curve representation of the Stroke object. + + + Returns the array of actual points that are used to approximate the Bezier representation of a Stroke object + Returns a Point array that indicates the points that were used to draw the Bezier curve representation of the Stroke object. + + + Returns an array of Point structures that make up the Stroke object. + The starting zero-based index within the array of Point structures that make up the Stroke object. + The number of points to return. + Returns an array of Point structures that make up the Stroke object. + + + Returns the Point structure at the specified index in a Stroke object. + The zero-based index of the Point structure to return. + Returns the Point structure at the specified index in the Stroke object. + + + Returns an array of Point structures that make up the Stroke object. + Returns an array of Point structures that make up the Stroke object. + + + Returns the metrics for a given packet description type + The Guid from the PacketProperty object, which identifies the property for which to obtain metrics. + Returns the metrics for a given packet description type. + + + Sets an array of Point structures at the specified indices in a Stroke object. + The zero-based index of the first point in the Stroke object to be modified + The array of new Point values to replace the points in the Stroke object beginning at <condition type="signature" criteria="sig_Int32_PointA">index</condition><condition type="signature" criteria="sig_PointA">the first point</condition> + Returns the actual number of points set. + + + Sets the Point structure at the specified index in a Stroke object. + The zero-based index of the Point structure to modify + The new Point value for the point in the Stroke object at index + Returns the number of points changed + + + Sets an array of Point structures at the specified indices in a Stroke object. + The array of new Point values to replace the points in the Stroke object beginning at <condition type="signature" criteria="sig_Int32_PointA">index</condition><condition type="signature" criteria="sig_PointA">the first point</condition> + Returns the actual number of points set. + + + Returns the packet data for a range of packets within the Stroke object. + The starting point of the zero-based index to a packet within the stroke. + The number of point packet data sets to return, starting with the packet specified in the index parameter. + Returns a signed 32-bit integer array containing the packet data for the requested points in the Stroke object + + + Returns the packet data for one packet, at a specified index, within the Stroke object. + The starting point of the zero-based index to a packet within the stroke. + Returns a signed 32-bit integer array containing the packet data for the requested points in the Stroke object + + + Returns the packet data associated with one or more points in a Stroke object. + Returns a signed 32-bit integer array containing the packet data for the requested points in the Stroke object + + + Returns the data for a known packet property from one or more packets in the Stroke object. + The Guid identifier from the PacketProperty object that is used to select which packet data is retrieved. + The starting point of the zero-based index to a packet within the Stroke object + The number of points that make up the stroke data + Returns an array of signed 32-bit integers that specifies the value of the requested PacketProperty object for each point requested from the Stroke object + + + + + Returns the data for a known packet property from one or more packets in the Stroke object. + The Guid identifier from the PacketProperty object that is used to select which packet data is retrieved. + The starting point of the zero-based index to a packet within the Stroke object + Returns an array of signed 32-bit integers that specifies the value of the requested PacketProperty object for each point requested from the Stroke object + + + + + Returns the data for a known packet property from one or more packets in the Stroke object. + The Guid identifier from the PacketProperty object that is used to select which packet data is retrieved. + Returns an array of signed 32-bit integers that specifies the value of the requested PacketProperty object for each point requested from the Stroke object + + + + + Sets the packet values for a particular PacketProperty object within this Stroke object + The Guid identifier from the PacketProperty object that is used to select which packet data is set + The starting index of the packet to be modified + The number of packets in the stroke to modify and the number of values in packetValues + The array of packet data values + Returns the actual number of packets set + + + + + Sets the packet values for a particular PacketProperty object within this Stroke object + The Guid identifier from the PacketProperty object that is used to select which packet data is set + The starting index of the packet to be modified + The array of packet data values + Returns the actual number of packets set + + + + + Sets the packet values for a particular PacketProperty object within this Stroke object + The Guid identifier from the PacketProperty object that is used to select which packet data is set + The array of packet data values + Returns the actual number of packets set + + + + + Returns an array of Point structures that indicate where a Stroke object intersects a given Rectangle. + The Rectangle structure, in ink space coordinates, that describes the hit test area + Returns an array of Point structures that indicate where a Stroke object intersects the intersectRectangle parameter + + + Finds the points where this Stroke object intersects other Stroke objects within a given Strokes collection + The Strokes collection used to test for intersections with this Stroke object + This method returns an array of floating point index values that indicate the locations where the intersections occur + + + Returns a value that indicates whether a Stroke object is either completely inside or intersected by a given circle. + The center of the hit test circle, in ink space coordinates + The radius of the hit test circle + Whether a Stroke object is either completely inside or intersected by a given circle. + + + Returns the location on the Stroke object nearest to a specified Point and the distance between the point and the Stroke object. + The specified point, in ink space coordinates + The distance from the point to the Stroke object, as a floating point index + Returns the location on the Stroke object nearest to a specified Point. + + + + + Returns the location on the Stroke object nearest to a specified Point. + The specified point, in ink space coordinates + Returns the location on the Stroke object nearest to a specified Point. + + + + + Splits the Stroke object at the specified location on the Stroke object and returns the new Stroke object + The floating point index value that represents where to split the Stroke object + + + The new Stroke object that is created as a result of calling this method + + + Removes the portions of the Stroke object that are outside a given rectangle + The rectangle outside of which each Stroke object is clipped. + + + Applies a linear transformation to a Stroke, object + The Matrix transform to use on the Stroke object + The Boolean value that indicates whether to apply the transform to the width of the ink in the DrawingAttributes of the Stroke object. + + + Applies a linear transformation to a Stroke, object + The Matrix transform to use on the Stroke object + + + Scales the Stroke object to fit in the specified Rectangle structure + The Rectangle structure, in ink space coordinates, to which the Stroke object is scaled + + + + + Applies a translation to the ink of the Stroke object. + The distance to translate the view transform in the X dimension, in HIMETRIC units, in ink space coordinates + The distance to translate the view transform in the Y dimension, in HIMETRIC units, in ink space coordinates + + + Rotates the Stroke object around a center point + The degrees by which to rotate clockwise + The point-in ink space coordinates-around which to rotate + + + Scales the Stroke object in the X and Y dimensions + The factor to scale the X dimension of the view transform + The factor to scale the Y dimension of the view transform + + + Shears the Stroke object by the specified horizontal and vertical factors. + The horizontal factor of the shear. + The vertical factor of the shear. + + + + + Gets the identifier of the Stroke object. + + + Gets a value that indicates whether the Stroke object has been deleted from its parent Ink object. + + + Gets an array that contains the indices of the cusps of the Stroke object + + + + + Gets an array that contains the indices of the cusps of the Bezier approximation of the Stroke object. + + + + + Gets the array of control points that represent the Bezier approximation of the stroke + + + Gets the self-intersections of the Stroke object. + + + + + Gets the number of packet received for a Stroke object. + + + Gets the size, in bytes, of a packet. + + + Gets an array of type Guid that describes the types of packet data stored in the Stroke object. + + + Gets or sets the drawing attributes to apply to ink as it is drawn + + + + + Gets the parent Ink object of the Stroke object. + + + + + Gets the collection of application-defined data. + + + + + Represents the floating point index values where an intersection begins and ends on a stroke. + + + Creates a StrokeIntersection structure from the start and end indices. + [in] The start of the stroke intersection as a floating point index + [in] The end the stroke intersection as a floating point index + + + Gets or sets the beginning point of the StrokeIntersection. + + + + + Gets or sets the ending point of the StrokeIntersection. + + + + + Represents the method that handles stroke adding and removing events of the Ink object, InkOverlay object, InkPicture object, and Strokes collection. + [in] Specifies the source Ink object or Strokes collection of this event. + [in] Specifies the StrokesEventArgs object that contains the event data. + + + Provides data for StrokesAdded and StrokesRemoved events. + + + Initializes a new instance of the StrokesEventArgs class + + + The integer array of Id properties for all of the Stroke objects that changed. + + + Gets an array of identifiers, of type Int32, for every Stroke object affected by the event. + + + Contains the collection of Stroke objects. + + + + + + + Releases resources used by the Strokes collection. + + + + + Releases the unmanaged resources used by the Strokes collection and optionally releases the managed resources. + + + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Frees the resources of the current Strokes object before it is reclaimed by the garbage collector. + + + + + Clears all the Stroke objects from the Strokes collection + + + + + Returns the index of a specific Stroke object within a Strokes collection. + + + The Stroke object to check for + Returns the index of the Stroke within the Strokes collection + + + Indicates whether the Strokes collection contains a specific Stroke object. + + + The Stroke object to check for. + Whether the Strokes collection contains a specific Stroke object. + + + Adds a Stroke object to the Strokes collection + The Stroke to add to the Strokes collection. + This method always returns a value of -1 + + + + + + + + + Removes a Stroke object from the Strokes collection + The Stroke object to remove + + + + + + + + + Removes the Stroke object at the specified index of the Strokes collection + The index of the Stroke object to remove from the collection + + + Copies all of the elements of the current Strokes collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Sets the DrawingAttributes property of all of the Stroke objects in Strokes collection. + The new DrawingAttributes object assigned to all of the Stroke objects in the Strokes collection + + + Returns the bounding Rectangle that contains the Stroke object, by using the specified BoundingBoxMode flag to determine the bounds. + A member of the BoundingBoxMode enumeration that specifies which characteristics of a Strokes collection, such as drawing attributes, are used to calculate the bounding box of the Strokes collection. + + + Returns the bounding Rectangle that defines the bounding box for the Strokes collection. + + + Returns the bounding Rectangle that contains the Strokes collection. + + + Returns the bounding Rectangle that defines the bounding box for the Strokes collection. + + + Deprecated + Returns a string that represents the top RecognitionResult object for the current Strokes collection. + + + Removes the RecognitionResult object that is associated with the Strokes collection. + + + + + Applies a linear transformation to a Strokes collection. + The Matrix transform to use on the Strokes collection + The Boolean value that indicates whether to apply the transform to the width of the ink in the DrawingAttributes of the Stroke objects in the Strokes collection. + + + Removes the portions of every Stroke object in the Strokes collection that are outside a given rectangle + The rectangle outside of which each Stroke object in the collection is clipped. + + + Applies a linear transformation to a Strokes collection. + The Matrix transform to use on the Strokes collection + + + Scales the Strokes collection to fit in the specified Rectangle structure + The Rectangle structure, in ink space coordinates, to which the Strokes collection is scaled + + + + + Applies a translation to the ink of the Strokes collection. + The distance to translate the view transform in the X dimension, in HIMETRIC units and in ink space coordinates + The distance to translate the view transform in the Y dimension, in HIMETRIC units and in ink space coordinates. + + + Rotates the Strokes collection around a center point. + The degrees by which to rotate clockwise + The point-in ink space coordinates-around which to rotate + + + Scales the Strokes collection in the X and Y dimensions + The factor to scale the X dimension of the view transform. + The factor to scale the Y dimension of the view transform. + + + Shears the Strokes collection by the specified horizontal and vertical factors. + The horizontal factor of the shear. + The vertical factor of the shear. + + + + + Returns an object that implements the IEnumerator interface and that can iterate through the Stroke objects within the Strokes collection. + Returns an object that implements the IEnumerator interface and that can iterate through the Stroke objects within the Strokes collection. + + + + + + + + + Occurs when one or more strokes are added to the Strokes collection. + + + Occurs when one or more strokes are deleted from the Strokes collection. + + + Gets the number of Stroke objects contained in the Strokes collection. + + + Gets a value indicating whether the Strokes collection has a fixed size. + + + + + Gets a value that indicates whether or not access to the Strokes collection is synchronized (thread safe). + + + + + Returns an object that can be used to synchronize access to the Strokes collection. + + + Gets a value indicating whether the Strokes collection is read-only. + + + + + + + + + + + + + Gets the Ink object that contains the Strokes collection. + + + Gets the RecognitionResult object of the Strokes collection. + + + + + An implementation of the IEnumerator interface that supports iterating over a Strokes collection. + + + Initializes a new instance of the StrokesEnumerator class. + The Strokes collection that this enumerator iterates over. + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the Strokes collection. + + + + + + + + + Gets the Stroke object in the Strokes collection to which the enumerator is pointing. + + + + + Defines values that specify the hardware capabilities of a Tablet PC. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. + + + Indicates the digitizer is integrated with the display. + + + Indicates the cursor must be in physical contact with the device to report position. + + + Indicates the device can generate in-air packets when the cursor is in the physical detection range (proximity) of the device. + + + Indicates the device can uniquely identify the active cursor. + + + Defines values that indicate the unit of measurement of a property. + + + Specifies the units are unknown. + + + Specifies the property value is in inches (distance units). + + + Specifies the property value is in centimeters (distance units). + + + Specifies the property value is in degrees (angle units). + + + Specifies the property value is in radians (angle units). + + + Specifies the property value is in seconds (angle units). + + + Specifies the property value is in pounds (force, or mass, units). + + + Specifies the property value is in grams (force, or mass, units). + + + Defines the range and resolution of a packet property. + + + The minimum value, in logical units, that the tablet reports for this property + + + The maximum value, in logical units, that the tablet reports for this property. + + + The physical units of the property, such as inches or degrees + + + The resolution or increment value for the Units member + + + Describes a PacketProperty that is reported by the digitizer. + + + Initializes a new instance of the TabletPropertyDescription class. + + + The TabletPropertyMetrics property of the TabletPropertyDescription object, defined by the tablet device. + + + Gets the identifier for the TabletPropertyDescription object. + + + Gets the metrics for this packet property. + + + Contains an ordered collection of TabletPropertyDescription objects. + + + Initializes a new instance of the TabletPropertyDescriptionCollection collection. + + + Initializes a new instance of the TabletPropertyDescriptionCollection collection that uses the specified conversion factors for ink space to digitizer coordinates. + The horizontal, or x-axis, conversion factor. + The vertical, or y-axis, conversion factor. + + + Adds a TabletPropertyDescription object to the end of the TabletPropertyDescriptionCollection collection. + The TabletPropertyDescription object to add to the end of the TabletPropertyDescriptionCollection collection. + The zero-based index of the TabletPropertyDescription object within the TabletPropertyDescriptionCollection collection. + + + Removes a specified TabletPropertyDescription object from the TabletPropertyDescriptionCollection collection. + The TabletPropertyDescription object to remove. + + + Gets the conversion factor for the horizontal axis from ink space to digitizer coordinates. + + + Gets the conversion factor for the vertical axis from ink space to digitizer coordinates. + + + + + + + + + + + Represents the digitizer device of Tablet PC that receives tablet device messages or events. + + + Returns a string that represents the current Tablet object. + Returns a string that represents the current Tablet object + + + Returns a value that indicates whether a PacketProperty field, identified with a globally unique identifier (GUID), is supported by this Tablet object. + + + Returns a value that indicates whether a PacketProperty field, identified with a globally unique identifier (GUID), is supported by this Tablet object. + + + Returns the metrics data for a known PacketProperty object. + The Guid identifier for the PacketProperty that you are requesting. + This method returns a TabletPropertyMetrics object for the requested property that is supported by the tablet + + + Gets the name of the Tablet object. + + + Gets a string representation of the Plug and Play identifier of the Tablet object. + + + Gets the maximum input rectangle, in tablet device coordinates, that the Tablet object supports. + + + Gets a value from the TabletHardwareCapabilities enumeration that defines the hardware capabilities of the Tablet object. + + + Contains the Tablet objects that represent the hardware-specific properties of all tablets that are attached to the system. + + + Initializes a new instance of the Tablets class + + + Copies all of the elements of the current Tablets collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns a value that indicates whether a PacketProperty field, identified with a globally unique identifier (GUID), is supported by all Tablet objects in this Tablets collection. + Returns a value that indicates whether a PacketProperty field, identified with a globally unique identifier (GUID), is supported by all Tablet objects in this Tablets collection. + The Guid identifier for the requested PacketProperty field. + + + Returns an object that implements the IEnumerator interface and that can iterate through the Tablet objects within the Tablets collection. + Returns an object that implements the IEnumerator interface and that can iterate through the Tablet objects within the Tablets collection. + + + + + + + Returns an object that can be used to synchronize access to the Tablets collection. + + + Gets the number of Tablet objects contained in the Tablets collection. + + + Gets a value that indicates whether or not access to the Tablets collection is synchronized (thread safe). + + + + + + + + + + + + + Gets the default Tablet object of the Tablets collection. + + + + + An implementation of the IEnumerator interface that supports iterating over a Tablets collection. + + + Initializes a new instance of the TabletsEnumerator class. + The Tablets collection that this enumerator iterates over. + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the Tablets collection. + + + + + + + + + Gets the Tablet object in the Tablets collection to which the enumerator is pointing. + + + + + This class contains the pre-defined factoid identifiers + + + A constant string value that is used to increase recognition accuracy by not using any recognizer context. + + + The Default setting for factoids for western languages includes the system dictionary, user dictionary, various punctuations, and the Web and Number factoid + + + A constant string value that is used to increase recognition accuracy by only using the system dictionary. + + + A constant string value that is used to increase recognition accuracy by having the recognizer use a programmatically-defined list of words + + + A constant string value that is used to increase recognition accuracy by providing an e-mail context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a web address context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a single character context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a numeric context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a digit context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a simple numeric context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of currency to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a postal code context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a percent context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a date context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a time context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a telephone number context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a file name context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing an uppercase character context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a lowercase character context to a recognizer + + + A constant string value that is used to increase recognition accuracy by providing a punctuation context to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of commonly used Kanji, Katakana, and Hiragana characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of commonly used Simplified Chinese characters to a recognizer + + + A constant string value that is used to increase recognition accuracy by providing a context of commonly used Traditional Chinese characters to a recognizer + + + A constant string value that is used to increase recognition accuracy by providing a context of commonly used Korean characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of Hiragana characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of Katakana characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of commonly used Kanji characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of rarely used Kanji characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a Bopomofo context to the recognizer + + + A constant string value that is used to increase recognition accuracy by providing a context of Hangul compatibility Jamo characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of commonly used Hangul characters to a recognizer. + + + A constant string value that is used to increase recognition accuracy by providing a context of rarely used Hangul characters to a recognizer. + + + Defines the beginning and end points of a line segment. + + + Creates a Line structure with the specified begin and end points. + [in] The starting point of the line + [in] The ending point of the line + + + + + Gets or sets the beginning point of the Line. + + + Gets or sets the ending point of the Line. + + + Represents the possible word matches for segments of ink that are compared to a recognizer dictionary. + + + Returns a string that represents the current RecognitionAlternate object. + Returns a string that represents the current RecognitionAlternate object. + + + Returns the value of a specified RecognitionProperty of the RecognitionAlternate object + The property of the alternate to return, as a globally unique identifier (GUID) for one of the fields on the RecognitionProperty object + Returns the value of the property type in the form of a byte array + + + Determines the smallest range of recognized text for which the recognizer can return an alternate that contains a known Strokes collection. <!-- parameters --> + The Strokes collection contained within the alternate. + The start position of the range of recognized text. + The length of the range of recognized text. + + + Returns the Strokes collection that corresponds to the smallest set of recognition segment that contains a specified character range within the alternate. + The start of the character range within this alternate. + The length of the character range within the alternate + Returns the Strokes collection that corresponds to the smallest set of recognition segments that contains a specified character range within the alternate. + + + Returns the smallest Strokes collection that contains a known Strokes collection and for which the recognizer can provide alternates. + The Strokes collection to use to find the smallest Strokes collection of the recognition result alternate that contains this collection + Returns the smallest Strokes collection that contains a known Strokes collection and for which the recognizer can provide alternates. + + + + + Returns a RecognitionAlternates collection that is made of a division of the RecognitionAlternate object on which this method is called + The Guid identifier for the RecognitionProperty type for which each returned RecognitionAlternate in the collection has the same constant value. + Returns a RecognitionAlternates collection that is made of a division of the RecognitionAlternate object on which this method is called + + + Gets the Strokes collection that was used by the recognizer to generate the RecognitionAlternate object. + + + Gets the midline for a RecognitionAlternate object that represents a single line of text. + + + Gets the ascender line for a RecognitionAlternate object that represents a single line of text. + + + Gets the descender line for a RecognitionAlternate object that represents a single line of text. + + + Gets the baseline for a RecognitionAlternate object that represents a single line of text. + + + Gets the line number that the recognition alternate corresponds to in the recognition result. + + + Gets the RecognitionAlternates collection in which each alternate in the collection is on a separate line. + + + Gets the collection of alternates where the current alternate is split into a collection of smaller alternates + + + Gets the level of confidence that a recognizer has in the recognition of a RecognitionAlternate object. + + + Contains the RecognitionAlternate objects that represent possible word matches for segments of ink. + + + Copies all of the elements of the current RecognitionAlternates collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns an object that implements the IEnumerator interface that can iterate through the RecognitionAlternate objects within the RecognitionAlternates collection. + Returns an object that implements the IEnumerator interface that can iterate through the RecognitionAlternate objects within the RecognitionAlternates collection. + + + + + + + Gets the Strokes collection that the recognizer used to generate the RecognitionAlternates collection. + + + Gets an object that can be used to synchronize access to the RecognitionAlternates collection. + + + Gets the number of RecognitionAlternate objects contained in the RecognitionAlternates collection. + + + Gets a value that indicates whether or not access to the RecognitionAlternates collection is synchronized (thread safe). + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over a RecognitionAlternates collection. + + + Initializes a new instance of the RecognitionAlternatesEnumerator class. + The RecognitionAlternates collection that this enumerator iterates over. + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the RecognitionAlternates collection. + + + + + + + + + Gets the RecognitionAlternate object in the RecognitionAlternates collection to which the enumerator is pointing. + + + + + Defines values that specify types of character input modes. + + + Specifies recognition occurs as if all strokes have been input. + + + Specifies recognition occurs on partial input + + + Specifies recognition occurs on partial input + + + Defines values that indicate whether an error occurred during recognition and, if so, which error occurred. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. + + + Indicates no errors occurred. + + + Indicates the recognition was interrupted by a call to StopBackgroundRecognition. + + + Indicates the ink recognition process failed. + + + Indicates the ink could not be added. + + + Indicates the character Autocomplete mode could not be set. + + + Indicates the strokes could not be set. + + + Indicates the recognition guide could not be set. + + + Indicates the flags could not be set. + + + Indicates the factoid could not be set. + + + Indicates the suffix or the prefix could not be set. + + + Indicates the word list could not be set. + + + Represents the method that handles the Recognition event of a RecognizerContext object. + [in] Specifies the source RecognizerContext object of this event. + [in] Specifies the RecognizerContextRecognitionEventArgs object that contains the event data. + + + Provides data for Recognition events, which occur when stroke are recognized. + + + Initializes a new instance of the RecognizerContextRecognitionEventArgs class. + + + The RecognitionResult. + The custom data supplied by the application. + One of the values from the RecognitionStatus enumeration value that indicates the recognition status as of the most recent recognition result. + + + Gets a string that contains the recognition result text with the highest confidence. + + + Gets the object that contains the custom data for the recognition result. + + + Gets a RecognitionStatus enumeration value that indicates the recognition status as of the most recent recognition result. + + + Represents the method that handles the RecognitionWithAlternates event of a RecognizerContext object. + [in] Specifies the source RecognizerContext object of this event. + [in] Specifies the RecognizerContextRecognitionWithAlternatesEventArgs object that contains the event data. + + + Provides data for RecognitionWithAlternates events, which occur when stroke are recognized. + + + Initializes a new instance of the RecognizerContextRecognitionWithAlternatesEventArgs class. + + + The RecognitionResult object that the recognizer returned. + The custom data supplied by the application. + One of the values from the RecognitionStatus enumeration value that indicates the recognition status as of the most recent recognition result. + + + Gets the RecognitionResult object from the event. + + + Gets a RecognitionStatus enumeration value that indicates the recognition status as of the most recent recognition result. + + + Gets the object that contains the custom data for the recognition result. + + + Defines values that specify how the recognizer interprets the ink and determines the result string. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. + + + Specifies that the recognizer applies no recognition modes. + + + Specifies that the recognizer treats the ink as a single word + + + Disables multiple segmentation. This turns off the recognizer's ability to return recognition results based on more than one recognition segment of the ink, where each segment corresponds to a word (in recognizers of Latin script) or a character (in recognizers of East Asian characters).The word "together" always returns alternates based on "together" being a single word, and the recognizer does not consider the string might also be "to get her" or some other variation with differing segmentation.Turning on this flag enhances recognition speed. + + + Specifies that the recognizer coerces the result based on the factoid that you specified for the context. For example, if the Telephone factoid is specified and the user enters the word hello, the recognizer may return a random phone number or an empty string + + + Enables the ability to perform ink recognition, retrieve the recognition result, and retrieve alternates + + + + + + + + + + + + + Initializes a new instance of the RecognizerContext class. + + + Releases resources used by the RecognizerContext object. + + + + + Releases the unmanaged resources used by the RecognizerContext object and optionally releases the managed resources. + + + Set to true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Frees the resources of the current RecognizerContext object before it is reclaimed by the garbage collector. + + + + + + + + + + + Creates a copy of this RecognizerContext object + The new copy of the original RecognizerContext object. + + + Returns a value that indicates whether the system dictionary, user dictionary, or WordList contain a specified string + The string to look up in the dictionaries and word list. + A value that indicates whether the system dictionary, user dictionary, or WordList contain a specified string + + + Ends background recognition that was started with a call to BackgroundRecognize or BackgroundRecognizeWithAlternates + + + Causes the Recognizer object to recognize the associated Strokes collection and raise a Recognition event when recognition is complete + Any application-defined data that is available to the application in the Recognition event + + + Causes the Recognizer object to recognize the associated Strokes collection and raise a Recognition event when recognition is complete + + + Returns a RecognitionResult object for a Strokes collection. + The RecognitionResult object for a recognized Strokes collection + A member of the RecognitionStatus enumeration that indicates whether an error occurred during recognition and, if so, which error occurred + + + Ends ink input to the RecognizerContext object. + + + Causes the Recognizer object to recognize the associated Strokes collection and raise a Recognition event when recognition is complete + Any application-defined data that is available to the application in the Recognition event + + + Causes the Recognizer object to recognize the associated Strokes collection and raise a Recognition event when recognition is complete + + + Occurs when the RecognizerContext has generated results from the BackgroundRecognize method. + + + Occurs when the RecognizerContext has generated results after calling the BackgroundRecognizeWithAlternates method. + + + Gets or sets the WordList object that is used to improve the recognition results. + + + Gets or sets the characters that come before the Strokes collection in the RecognizerContext object + + + + + Gets or sets the characters that come after the Strokes collection in the RecognizerContext object + + + + + Gets or sets the Strokes collection associated with the RecognizerContext object. + + + + + Gets or sets the Recognizer object used by the RecognizerContext object. + + + Gets or sets the flags that specify how the recognizer interprets the ink and determines the result string. + + + Gets or sets the RecognizerGuide to use for ink input. + + + Gets or sets the string name of the factoid used by the RecognizerContext object + + + + + Gets or sets the character Autocomplete mode, which determines when characters or words are recognized + + + + + Represents the area that the recognizer uses in which ink can be drawn + + + Creates a RecognizerGuide object. + [in] The number of rows in the guide box + [in] The number of columns in the guide box + [in] The midline height, or distance from the baseline to the midline, of the guide box + [in] The invisible writing area of the guide box in which writing can actually take place + [in] The box that is physically drawn on the tablet's screen and in which writing takes place + + + Gets or sets the number of rows in the recognition guide box. + + + + + Gets or sets the number of columns in the recognition guide box. + + + + + Gets or sets the midline height + + + Gets or sets the invisible writing area of the recognition guide in which writing can actually take place + + + Gets or sets the box that is physically drawn on the tablet's screen and in which writing takes place. + + + Represents the result of the recognition + + + Retrieves the default maximum number of RecognitionAlternates to get from the recognizer. + + + Returns a string that represents the TopAlternate property of the current RecognitionResult object. + Returns a string that represents the TopAlternate property of the current RecognitionResult object. + + + Modifies the RecognitionResult object with a known RecognitionAlternate object. + The RecognitionAlternate object to use to modify the RecognitionResult object. + + + Returns the RecognitionAlternates collection from a selection within the best result string of the RecognitionResult object, so that each RecognitionAlternate object in the collection corresponds to only one segment of ink. + The start of the text selection from which the RecognitionAlternates collection is returned + The length of the text selection from which the RecognitionAlternates collection is returned + The maximum number of alternates to return + Returns the RecognitionAlternates collection from a selection within the best result string of the RecognitionResult object, so that each RecognitionAlternate object in the collection corresponds to only one segment of ink. + + + Returns the RecognitionAlternates collection from a selection within the best result string of the RecognitionResult object, so that each RecognitionAlternate object in the collection corresponds to only one segment of ink. + The start of the text selection from which the RecognitionAlternates collection is returned + The length of the text selection from which the RecognitionAlternates collection is returned + Returns the RecognitionAlternates collection from a selection within the best result string of the RecognitionResult object, so that each RecognitionAlternate object in the collection corresponds to only one segment of ink. + + + Returns the RecognitionAlternates collection from a selection within the best result string of the RecognitionResult object, so that each RecognitionAlternate object in the collection corresponds to only one segment of ink. + Returns the RecognitionAlternates collection from a selection within the best result string of the RecognitionResult object, so that each RecognitionAlternate object in the collection corresponds to only one segment of ink. + + + Assigns the RecognitionResult object to the Strokes collection that was used to generate the results. + + + + + Gets the result text for the TopAlternate property. + + + Gets the confidence level of the TopAlternate property of the RecognitionResult object. + + + Gets the Strokes collection that was used by the recognizer to generate the RecognitionResult object. + + + Gets the top alternate of the recognition result. + + + Defines values that specify the properties of the recognition alternate. + + + Retrieves a globally unique identifier (GUID) that specifies the line number of the RecognitionAlternate object. + + + + + + + Retrieves a globally unique identifier (GUID) that specifies the segmentation of the RecognitionAlternate object. + + + Retrieves a globally unique identifier (GUID) that specifies the hot point of the RecognitionAlternate object. + + + Retrieves a globally unique identifier (GUID) that specifies the maximum stroke count of the RecognitionAlternate object. + + + Retrieves a globally unique identifier (GUID) that specifies the points-per-inch metric of the RecognitionAlternate object. + + + Retrieves a globally unique identifier (GUID) that specifies the confidence level of the RecognitionAlternate object. + + + Retrieves a globally unique identifier (GUID) that specifies the line metrics of the RecognitionAlternate object. + + + Defines values that specify the attributes of a recognizer + + + Ignores all other flags that are set. + + + If set, specifies the recognizer performs object recognition; otherwise, the recognizer performs text recognition. + + + Specifies the recognizer supports free input + + + Specifies the recognizer supports lined input, which is similar to writing on lined paper. + + + Specifies the recognizer supports boxed input, in which each character or word is entered in a box. + + + Specifies the recognizer supports character Autocomplete + + + Specifies the recognizer supports western and Asian languages. + + + Specifies that the recognizer supports Hebrew and Arabic languages. + + + Specifies the recognizer supports Asian languages. + + + Specifies the recognizer supports the Chinese language. + + + Specifies the recognizer supports text is written at arbitrary angles. + + + Specifies the recognizer can return a lattice object. + + + Specifies that the recognizer's background recogition can be interrupted, as in when the ink has changed. + + + Specifies that stroke order - spatial and temporal - is handled. + + + Represents the ability to process ink, or handwriting, and translate the stroke into text or gestures + + + Returns a string that represents the current Recognizer object. + Returns a string that represents the current Recognizer object. + + + Creates a new RecognizerContext object for this Recognizer object. + The new RecognizerContext object. + + + Gets the name of the Recognizer object. + + + Gets the vendor name of the Recognizer object. + + + Gets the capabilities of the Recognizer object. + + + Gets an array of type Guid that represents the preferred packet properties for the recognizer. + + + Gets an array of type Guid that describe the properties that the Recognizer object supports. + + + Gets an array of language identifiers for the languages that the Recognizer object supports. + + + Contains the Recognizer objects that represent the ability to create a recognizer context, retrieve its attributes and properties, and determine which packet properties the recognizer needs to perform recognition. + + + Initializes a new instance of the Recognizers class. + + + Copies all of the elements of the current Recognizers collection to the specified one-dimensional array, starting at the specified destination array index. + The one-dimensional array that is the destination of elements copied from the collection + The zero-based index in the array parameter at which copying begins. + + + + + Returns the default recognizer. + + + Returns the requested recognizer. + + + Returns the default recognizer for a known language, specified by a national language support (NLS) language code identifier (LCID). + The language code identifier (LCID) of the language for which you are retrieving the default recognizer + Returns the requested recognizer. + + + Returns an object that implements the IEnumerator interface and that can iterate through the Recognizer objects within the Recognizers collection. + Returns an object that implements the IEnumerator interface and that can iterate through the Recognizer objects within the Recognizers collection. + + + + + + + Returns an object that can be used to synchronize access to the Recognizers collection. + + + Gets the number of Recognizer objects contained in the Recognizers collection. + + + Gets a value that indicates whether or not access to the Recognizers collection is synchronized (thread safe). + + + + + + + + + + + + + An implementation of the IEnumerator interface that supports iterating over a Recognizers collection + + + Initializes a new instance of the RecognizersEnumerator class. + The Recognizers collection that this enumerator iterates over. + + + Moves the enumerator index to the next object in the collection. + Set to true if the index position references an object; false if the index position references the end of the collection. + + + Resets the enumerator index to the beginning of the Recognizers collection. + + + + + + + + + Gets the Recognizer object in the Recognizers collection to which the enumerator is pointing. + + + + + Represents a list of String types that can be used to improve the recognition result. + + + Initializes a new instance of the WordList class. + + + Adds a single string to the WordList object. + The string to add to the WordList object + + + Removes a single string from a WordList object. + The string to remove from the WordList + + + Merges a specified WordList object into this WordList object. + The specified WordList object to merge into this WordList object. + + + + + + + + + + + + + + + + + + + Container to encapsulate any arbitrary developer custom data in the Data member, with a Guid identifier in the CustomStylusDataId member to pass type information. + + + + + + + + + + + Gets a unique identifier for the CustomStylusData object. + + + Gets the user data for the CustomStylusData object. + + + An object that displays the tablet pen data in real-time as it is being handled by the RealTimeStylus object + + + Interface implemented by synchronous plug-ins + + + Informs the implementing plug-in that the RealTimeStylus is enabled + The RealTimeStylus that sent the notification. + Contains information about the Tablets associated with this RealTimeStylus. + + + Informs the implementing plug-in that the RealTimeStylus is disabled. + The RealTimeStylus that sent the disabled notification. + Contains information about the Tablets associated with this RealTimeStylus. + + + Informs the implementing plug-in that the Stylus is in range of the digitizer. + The RealTimeStylus that sent the notification. + + + + + Occurs when the stylus is out of range of the digitizer. + The RealTimeStylus that sent the notification. + Provides access to the stylus associated with the notification. + + + Informs the implementing plug-in that the Stylus has touched the digitizer. + The RealTimeStylus that sent the notification. + Contains information about the stylus. + + + Notification occurs when the stylus is raised off of the digitizer. + The RealTimeStylus that sent the notification. + Provides access to the stylus associated with the notification. + + + Informs the implementing plug-in that the stylus button is pressed + The RealTimeStylus that sent the notification. + Contains information about the button that was pressed. + + + Informs the implementing plug-in that the stylus button has been released. + The RealTimeStylus that sent the notification. + + + + + Informs the object implementing the IStylusSyncPlugin interface that the stylus is moving above the digitizer. + The RealTimeStylus which called this method. + The information about the stylus movement. + + + Informs the object implementing the IStylusSyncPlugin interface that the stylus is moving on the digitizer. + The RealTimeStylus which called this method. + The information about the stylus movement. + + + Notification occurs when a system gesture is received. + The RealTimeStylus that sent the notification. + Provides access to information about the system gesture. + + + Notification occurs when a Tablet is attached to the system. + The RealTimeStylus that sent the notification. + Provides access to the Tablet that was added. + + + Notification occurs when a Tablet is removed from the system. + The RealTimeStylus that sent the notification. + Provides access to the Tablet that was added. + + + Informs the implementing plug-in that CustomStylusData is available. + + + + + + + Informs the implementing object that this plug-in or one of the previous ones in the list threw an error. + + + + + + + Used to define the set of data notifications that the plug-in requires. + + + The Guid that is used by a CustomStylusData object to indicate that the Data property contains DynamicRendererCachedData from a DynamicRenderer. + + + Creates a DynamicRenderer object. + The control on which the tablet pen data is displayed + + + Creates a DynamicRenderer object. + The handle for the window on which the tablet pen data is displayed + + + Frees the resources of the current DynamicRenderer object before it is reclaimed by the garbage collector. + + + Releases stroke data from the temporal data held by the DynamicRenderer. + The identifier for the data to be released. + + + Causes the DynamicRenderer to refresh the data that it is currently rendering. + + + Releases the unmanaged resources used by the object and optionally releases the managed resources. + A value that indicates whether to release the managed resources. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Releases resources used by the object. + + + Gets and sets the DrawingAttributes used by the DynamicRenderer. + + + + + + + + + + + Turns dynamic rendering on and off. + + + + + + + Gets or sets whether data caching is enabled for the DynamicRender. + + + + + + + + + + + The DynamicRenderer instantiates this data object and adds it to the queue using the AddCustomStylusDataToQueue method with the CustomDataId set to DynamicRendererCachedDataGuid. + + + Gets the identifier of the corresponding data cache that can be released by the DynamicRenderer when this data object reaches the asynchronous plug-in that stores and renders ink. + + + Gets a reference back to the DynamicRenderer to release the cached data. + + + Data object created by GestureRecognizer as part of the GestureRecognitionData object + + + + + + + + + + + + + + + + + ApplicationGesture of the gesture that occured. + + + Confidence level assigned to this alternate by the gesture recongizer. + + + The number of most recent Strokes assumed to be a part of the gesture. + + + Provides a container for gesture recognition results. + + + Returns the IEnumerator interface used to iterate through the gesture recognition alternates. + Returns the IEnumerator interface used to iterate through the gesture recognition alternates. + + + + + + + Returns the number of alternates. + + + An object that reacts to events by recognizing gesture and adding gesture data into the input queue. + + + Interface implemented by asynchronous plug-ins + + + Informs the implementing plug-in that the RealTimeStylus is enabled + The RealTimeStylus that sent the notification. + Contains information about the Tablets associated with this RealTimeStylus. + + + Informs the implementing plug-in that the RealTimeStylus is disabled. + The RealTimeStylus that sent the disabled notification. + Contains information about the Tablets associated with this RealTimeStylus. + + + Informs the implementing plug-in that the Stylus is in range of the digitizer. + The RealTimeStylus that sent the notification. + + + + + Occurs when the stylus is out of range of the digitizer. + The RealTimeStylus that sent the notification. + Provides access to the stylus associated with the notification. + + + Informs the implementing plug-in that the Stylus has touched the digitizer. + The RealTimeStylus that sent the notification. + Contains information about the stylus. + + + Notification occurs when the stylus is raised off of the digitizer. + The RealTimeStylus that sent the notification. + Provides access to the stylus associated with the notification. + + + Informs the implementing plug-in that the stylus button is pressed + The RealTimeStylus that sent the notification. + Contains information about the button that was pressed. + + + Informs the implementing plug-in that the stylus button has been released. + The RealTimeStylus that sent the notification. + + + + + Informs the object implementing the IStylusAsyncPlugin interface that the stylus is moving above the digitizer. + The RealTimeStylus which called this method. + The information about the stylus movement. + + + Informs the object implementing the IStylusAsyncPlugin interface that the stylus is moving on the digitizer. + The RealTimeStylus which called this method. + The information about the stylus movement. + + + Notification occurs when a system gesture is received. + The RealTimeStylus that sent the notification. + Provides access to information about the system gesture. + + + Notification occurs when a Tablet is attached to the system. + The RealTimeStylus that sent the notification. + Provides access to the Tablet that was added. + + + Notification occurs when a Tablet is removed from the system. + The RealTimeStylus that sent the notification. + Provides access to the Tablet that was added. + + + Informs the implementing plug-in that CustomStylusData is available. + + + + + + + Informs the implementing object that this plug-in or one of the previous ones in the list threw an error. + + + + + + + Used to define the set of data notifications that the plug-in requires. + + + The Guid that is used by a CustomStylusData object to indicate that the Data property contains GestureRecognitionData from a GestureRecognizer. + + + Initializes a new instance of the GestureRecognizer class. + + + Frees the resources of the current GestureRecognizer object before it is reclaimed by the garbage collector. + + + Releases the unmanaged resources used by the object and optionally releases the managed resources. + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Sets which ApplicationGesture will result in the GestureRecognizer adding GestureRecognitionData into the input q queue. + The ApplicationGesture that the GestureRecognizer will respond to. + + + Clears the GestureRecognizer of past stroke information + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Releases resources used by the object. + + + Gets or sets a value which indicates whether gesture recognition is enabled. + + + Sets the maximum number of strokes allowed for gesture recognition. + + + + + + + + + + + + + + + + + Passed in via the InAirPackets and InAirPackets methods when packets are detected as the pen passes above the digitizer. + + + Base class for the various data classes. + + + + + + + + + + + + + + + + + Returns the packet data. + The packet data as an array of integers. + + + Replaces the packet data with the data argument + Integer array containing the new packet data + + + Packet data validation called when the object is initialized. + + + + + + + Returns the IEnumerator interface used to iterate through the data. + The IEnumerator interface. + + + Gets information about the tablet pen at the time the associated data is generated. + + + Gets the number of packets contained in the plug-in data object. + + + Returns the length of the packet data. + + + + + + + Creates an InAirPacketsData object. + The stylus associated with the data in the notification. + Number of packet properties in the array of packet data. + Array of packet data. + + + Used by plug-ins to specify interest in receiving notification about events. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Passed in the Packets and Packets methods when packets are detected. + + + Creates a PacketsData object. + + + + + + + + + Handles the stylus packet data from a digitizer in real time + + + + + Creates a RealTimeStylus object. + + + Creates a RealTimeStylus object and attaches it to the specified window. + The handle of the window to which to attach the RealTimeStylus object + + + Creates a RealTimeStylus object, attaches it to the specified window, and specifies whether or not to use the mouse for input. + The handle of the window to which to attach the RealTimeStylus object + Set to true to use the mouse for tablet input; otherwise, false. + + + Creates a RealTimeStylus object, attaches it to the specified window, and restricts ink collection to the specified tablet. + The handle of the window to which to attach the RealTimeStylus object + The tablet on which to collect ink, or null (Nothing in Microsoft Visual Basic .NET) to allow ink collection on any attached tablet. + + + Creates a RealTimeStylus object and attaches it to the specified control. + The control to which to attach the RealTimeStylus object + + + Creates a RealTimeStylus object, attaches it to the specified control, and specifies whether or not to use the mouse for input. + The control to which to attach the RealTimeStylus object + Set to true to use the mouse for tablet input; otherwise, false. + + + Creates a RealTimeStylus object, attaches it to the specified control, and restricts ink collection to the specified tablet. + The control to which to attach the RealTimeStylus object + The tablet on which to collect ink, or null (Nothing in Microsoft Visual Basic .NET) to allow ink collection on any attached tablet. + + + + + + + + + + + + + + + + + Frees the resources of the current RealTimeStylus object before it is reclaimed by the garbage collector. + + + Returns the array of Stylus objects encountered by the RealTimeStylus. + + + The styluses encountered by the RealTimeStylus. + + + Gets the RealTimeStylus object's interest in aspects of the packet collected on a tablet context. + + + + + + + + + + + Returns the globally unique identifier (GUID) for the packet properties in which the RealTimeStylus object is interested. + + + Sets the RealTimeStylus object's interest in aspects of the packet collected on a tablet context. + The globally unique identifier (GUID) for the packet properties in which the RealTimeStylus object is interested. + + + Returns the TabletPropertyDescriptionCollection collection that is associated with a given tablet context identifier. + + + The TabletPropertyDescriptionCollection collection that is associated with a given tablet context identifier. + + + + + + + + + + + + + Clears the stylus queues. + + + + + + + + + + + + + + + Releases all resources used by the object. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Releases all resources used by the object. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gets the collection of IStylusAsyncPlugin plug-ins that receive tablet pen data from the RealTimeStylus object. + + + Gets the collection of IStylusSyncPlugin plug-ins that receive tablet pen data from the RealTimeStylus object. + + + Gets or sets a value which indicates whether the collection of stylus data is enabled. + + + Gets or sets the input rectangle for the RealTimeStylus object. + + + + + + + + + + + + + + + + + + + Passed into the RealTimeStylusDisabled and RealTimeStylusDisabled methods when the RealTimeStylus is being disabled. + + + + + + + Gets the enumerator for the TabletContextID collection. + The enumerator for the TabletContextID collection. + + + + + + + Gets the number of TabletContextID objects related to the RealTimeStylus. + + + Passed into the RealTimeStylusEnabled and RealTimeStylusEnabled methods when the RealTimeStylus is being enabled. + + + + + + + Gets the enumerator for the TabletContextID collection. + The enumerator for the TabletContextID collection. + + + + + + + Gets the number of TabletContextID objects related to the RealTimeStylus. + + + Provides access to general information about a tablet stylus. + + + + + + + + + + + + + + + + + Gets a value that indicates whether the stylus is inverted or not. + + + Gets the globally unique identifier (GUID) of the stylus. + + + Gets the name of the Stylus object. + + + + + + + Gets the stylus buttons that are available on the stylus + + + Maintains a sequenced list of asynchronous plug-ins that implement IStylusAsyncPlugin + + + This class supports the RealTimeStylus infrastructure and is not intended to be used directly from your code. + + + This class supports the RealTimeStylus infrastructure and is not intended to be used directly from your code. + The zero-based index at which to insert item. + The new value of the element at index. + + + This class supports the RealTimeStylus infrastructure and is not intended to be used directly from your code. + + + + + + + This class supports the RealTimeStylus infrastructure and is not intended to be used directly from your code. + + + + + + + + + This class supports the RealTimeStylus infrastructure and is not intended to be used directly from your code. + + + Performs additional validation of a value. + The object to validate. + + + Adds an asynchronous plug-in to the end of the asynchronous plug-in collection. + The asynchronous plug-in to be added to the end of the saynchronous plug-in collection. + + + Inserts an asynchronous plug-in into the asynchronous plug-in collection at the specified index. + The zero-based index at which item should be inserted. + The asynchronous plug-in to insert. + + + Removes the asynchronous plug-in from the asynchronous plug-in collection. + The asynchronous plug-in to remove from the asynchronous plug-in collection. + + + Searches for the specified asynchronous plug-in and returns the zero-based index of the first occurrence within the entire asynchronous plug-in collection. + The asynchronous plug-in to locate in the asynchronous plug-in collection. + The zero-based index of the first occurrence of plugin within the entire asynchronous plug-in collection, if found; otherwise, -1. + + + Determines whether the asynchronous plug-in collection contains a specific asynchronous plug-in. + The asynchronous plug-in to locate in the asynchronous plug-in collection. + true if the asynchronous plug-in collection contains the specified asynchronous plug-in; otherwise, false. + + + Copies the entire asynchronous plug-in collection to a compatible one-dimensional Array, starting at the specified index of the target array. + The one-dimensional Array that is the destination of the elements copied from the asynchronous plug-in collection + The zero-based index in array at which copying begins. + + + + + + + Contains data passed when a button down occurs. + + + Abstract base class for button state data objects passed with button up and down methods. + + + + + + + The Stylus the button is on. + + + Index of the button that had a state change. + + + Creates a new <xref rid="" /> object. + Stylus the button is on. + Index of the button + + + Provides access to the stylus buttons of a stylus. + + + + + + + + + + + Gets the name of the button at the given index. + The index of the button. + Returns the name for the specified button. + + + Gets the number of buttons on the stylus. + + + Contains the data passed by the StylusButtonUp and StylusButtonUp method. + + + + + The Stylus with which the button is associated.. + The index of the button that is pressed. + + + Provides data for the StylusDown and StylusDown methods + + + Creates a StylusDownData object. + Information about the tablet pen for the StylusDownData object. + The number of packets contained in this StylusDownData object + The packet data for packets contained in this StylusDownData object. + + + Verifies the packet data for the StylusDownData object + + + + + + + Contains error data, including the exception thrown to generate the object and the plug-in that threw the exception. + + + + + + + + + + + + + Returns the plug-in method on which the exception originated. + + + Returns a reference to the plug-in where the exception originated. + + + Returns the exception object that originated the notification. + + + + + + + + + + + + + + + Provides data for the StylusInRange and StylusInRange methods + + + Creates a StylusInRangeData object. + Information about the tablet pen for the StylusInRangeData object. + + + Gets the information about the tablet pen at the time that it enters the input area of the RealTimeStylus object or enters the detection range of the digitizer above the input area of the RealTimeStylus object. + + + Provides data for the StylusOutOfRange and StylusOutOfRange methods + + + Creates a StylusOutOfRangeData object. + Information about the tablet pen for the StylusOutOfRangeData object. + + + Gets the information about the tablet pen at the time that it leaves the input area of the RealTimeStylus object or leaves the detection range of the digitizer above the input area of the RealTimeStylus object. + + + + + + + + + + + + + + + Maintains a sequenced list of synchronous plug-ins that implement IStylusSyncPlugin + + + Performs additional validation of a value. + The object to validate. + + + Adds a synchronous plug-in to the end of the synchronous plug-in collection. + The synchronous plug-in to be added to the end of the synchronous plug-in collection. + + + Inserts a synchronous plug-in into the synchronous plug-in collection at the specified index. + The zero-based index at which item should be inserted. + The synchronous plug-in to insert. + + + Removes the synchronous plug-in from the synchronous plug-in collection. + The synchronous plug-in to remove from the synchronous plug-in collection. + + + Searches for the specified synchronous plug-in and returns the zero-based index of the first occurrence within the entire synchronous plug-in collection. + The synchronous plug-in to locate in the synchronous plug-in collection. + The zero-based index of the first occurrence of plugin within the entire synchronous plug-in collection, if found; otherwise, -1. + + + Determines whether the synchronous plug-in collection contains a specific synchronous plug-in. + The synchronous plug-in to locate in the synchronous plug-in collection. + true if the synchronous plug-in collection contains the specified synchronous plug-in; otherwise, false. + + + Copies the entire synchronous plug-in collection to a compatible one-dimensional Array, starting at the specified index of the target array. + The one-dimensional Array that is the destination of the elements copied from the synchronous plug-in collection + The zero-based index in array at which copying begins. + + + + + + + Provides data for the StylusUp and StylusUp methods + + + Creates a StylusUpData object. + Information about the tablet pen for the StylusUpData object. + The number of packets contained in this StylusUpData object + The packet data for packets contained in this StylusUpData object. + + + Verifies the packet data for the StylusUpData object + + + + + + + Provides data for the SystemGesture and SystemGesture methods + + + + + + + + + + + + + + + + + + + Gets the information about the tablet pen at the time the system gesture occurs. + + + Gets the system gesture that generated this data. + + + Gets the location of the system gesture in pixel space. + + + Reserved. + + + Reserved. + + + + + + + Provides data for the TabletAdded and TabletAdded methods + + + + + + + + + Gets the Tablet object that is being added. + + + Provides data for the TabletRemoved and TabletRemoved methods + + + + + + + + + Gets the tablet context identifier for the removed Tablet. + + + diff --git a/src/MostRecentFile.cs b/src/MostRecentFile.cs new file mode 100644 index 0000000..11d620d --- /dev/null +++ b/src/MostRecentFile.cs @@ -0,0 +1,45 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Encapsulates a filename and a thumbnail. + /// + internal class MostRecentFile + { + private string fileName; + private Image thumb; + + public string FileName + { + get + { + return fileName; + } + } + + public Image Thumb + { + get + { + return thumb; + } + } + + public MostRecentFile(string fileName, Image thumb) + { + this.fileName = fileName; + this.thumb = thumb; + } + } +} diff --git a/src/MostRecentFiles.cs b/src/MostRecentFiles.cs new file mode 100644 index 0000000..b9a5102 --- /dev/null +++ b/src/MostRecentFiles.cs @@ -0,0 +1,228 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Drawing; + +namespace PaintDotNet +{ + /// + /// Data structure to manage the Most Recently Used list of files. + /// + internal class MostRecentFiles + { + private Queue files; // contains MostRecentFile instances + private int maxCount; + private const int iconSize = 56; + private bool loaded = false; + + public MostRecentFiles(int maxCount) + { + this.maxCount = maxCount; + this.files = new Queue(); + } + + public bool Loaded + { + get + { + return this.loaded; + } + } + + public int Count + { + get + { + if (!this.loaded) + { + LoadMruList(); + } + + return this.files.Count; + } + } + + public int MaxCount + { + get + { + return this.maxCount; + } + } + + public int IconSize + { + get + { + return UI.ScaleWidth(iconSize); + } + } + + public MostRecentFile[] GetFileList() + { + if (!Loaded) + { + LoadMruList(); + } + + object[] array = files.ToArray(); + MostRecentFile[] mrfArray = new MostRecentFile[array.Length]; + array.CopyTo(mrfArray, 0); + return mrfArray; + } + + public bool Contains(string fileName) + { + if (!Loaded) + { + LoadMruList(); + } + + string lcFileName = fileName.ToLower(); + + foreach (MostRecentFile mrf in files) + { + string lcMrf = mrf.FileName.ToLower(); + + if (0 == String.Compare(lcMrf, lcFileName)) + { + return true; + } + } + + return false; + } + + public void Add(MostRecentFile mrf) + { + if (!Loaded) + { + LoadMruList(); + } + + if (!Contains(mrf.FileName)) + { + files.Enqueue(mrf); + + while (files.Count > maxCount) + { + files.Dequeue(); + } + } + } + + public void Remove(string fileName) + { + if (!Loaded) + { + LoadMruList(); + } + + if (!Contains(fileName)) + { + return; + } + + Queue newQueue = new Queue(); + + foreach (MostRecentFile mrf in files) + { + if (0 != string.Compare(mrf.FileName, fileName, true)) + { + newQueue.Enqueue(mrf); + } + } + + this.files = newQueue; + } + + public void Clear() + { + if (!Loaded) + { + LoadMruList(); + } + + foreach (MostRecentFile mrf in this.GetFileList()) + { + Remove(mrf.FileName); + } + } + + public void LoadMruList() + { + try + { + this.loaded = true; + Clear(); + + for (int i = 0; i < MaxCount; ++i) + { + try + { + string mruName = "MRU" + i.ToString(); + string fileName = (string)Settings.CurrentUser.GetString(mruName); + + if (fileName != null) + { + Image thumb = Settings.CurrentUser.GetImage(mruName + "Thumb"); + + if (fileName != null && thumb != null) + { + MostRecentFile mrf = new MostRecentFile(fileName, thumb); + Add(mrf); + } + } + } + + catch + { + break; + } + } + } + + catch (Exception ex) + { + Tracing.Ping("Exception when loading MRU list: " + ex.ToString()); + Clear(); + } + } + + public void SaveMruList() + { + if (Loaded) + { + Settings.CurrentUser.SetInt32(SettingNames.MruMax, MaxCount); + MostRecentFile[] mrfArray = GetFileList(); + + for (int i = 0; i < MaxCount; ++i) + { + string mruName = "MRU" + i.ToString(); + string mruThumbName = mruName + "Thumb"; + + if (i >= mrfArray.Length) + { + Settings.CurrentUser.Delete(mruName); + Settings.CurrentUser.Delete(mruThumbName); + } + else + { + MostRecentFile mrf = mrfArray[i]; + Settings.CurrentUser.SetString(mruName, mrf.FileName); + Settings.CurrentUser.SetImage(mruThumbName, mrf.Thumb); + } + } + } + } + } +} diff --git a/src/MoveNubRenderer.cs b/src/MoveNubRenderer.cs new file mode 100644 index 0000000..6238e16 --- /dev/null +++ b/src/MoveNubRenderer.cs @@ -0,0 +1,305 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + internal class MoveNubRenderer + : CanvasControl + { + private Matrix transform; + private float transformAngle; + private int alpha; + private MoveNubShape shape; + + public MoveNubShape Shape + { + get + { + return this.shape; + } + + set + { + InvalidateOurself(); + this.shape = value; + InvalidateOurself(); + } + } + + protected override void OnLocationChanging() + { + InvalidateOurself(); + base.OnLocationChanging(); + } + + protected override void OnLocationChanged() + { + InvalidateOurself(); + base.OnLocationChanged(); + } + + protected override void OnSizeChanging() + { + InvalidateOurself(); + base.OnSizeChanging(); + } + + protected override void OnSizeChanged() + { + InvalidateOurself(); + base.OnSizeChanged(); + } + + public Matrix Transform + { + get + { + return this.transform.Clone(); + } + + set + { + InvalidateOurself(); + + if (value == null) + { + throw new ArgumentNullException(); + } + + if (this.transform != null) + { + this.transform.Dispose(); + this.transform = null; + } + + this.transform = value.Clone(); + this.transformAngle = Utility.GetAngleOfTransform(this.transform); + InvalidateOurself(); + } + } + + public int Alpha + { + get + { + return this.alpha; + } + + set + { + if (value < 0 || value > 255) + { + throw new ArgumentOutOfRangeException("value", value, "value must be [0, 255]"); + } + + if (this.alpha != value) + { + this.alpha = value; + InvalidateOurself(); + } + } + } + + private RectangleF GetOurRectangle() + { + PointF[] ptFs = new PointF[1] { this.Location }; + this.transform.TransformPoints(ptFs); + float ratio = (float)Math.Ceiling(1.0 / OwnerList.ScaleFactor.Ratio); + + float ourWidth = UI.ScaleWidth(this.Size.Width); + float ourHeight = UI.ScaleHeight(this.Size.Height); + + if (!Single.IsNaN(ratio)) + { + RectangleF rectF = new RectangleF(ptFs[0], new SizeF(0, 0)); + rectF.Inflate(ratio * ourWidth, ratio * ourHeight); + return rectF; + } + else + { + return RectangleF.Empty; + } + } + + private void InvalidateOurself() + { + InvalidateOurself(false); + } + + private void InvalidateOurself(bool force) + { + if (this.Visible || force) + { + RectangleF rectF = GetOurRectangle(); + Rectangle rect = Utility.RoundRectangle(rectF); + rect.Inflate(1, 1); + Invalidate(rect); + } + } + + public bool IsPointTouching(PointF ptF, bool pad) + { + RectangleF rectF = GetOurRectangle(); + + if (pad) + { + float padding = 2.0f * 1.0f / (float)this.OwnerList.ScaleFactor.Ratio; + rectF.Inflate(padding + 1.0f, padding + 1.0f); + } + + return rectF.Contains(ptF); + } + + public bool IsPointTouching(Point pt, bool pad) + { + RectangleF rectF = GetOurRectangle(); + + if (pad) + { + float padding = 2.0f * 1.0f / (float)this.OwnerList.ScaleFactor.Ratio; + rectF.Inflate(padding + 1.0f, padding + 1.0f); + } + + return pt.X >= rectF.Left && pt.Y >= rectF.Top && pt.X < rectF.Right && pt.Y < rectF.Bottom; + } + + protected override void OnVisibleChanged() + { + InvalidateOurself(true); + } + + protected override void OnRender(Graphics g, Point offset) + { + lock (this) + { + float ourSize = UI.ScaleWidth(Math.Min(Width, Height)); + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TranslateTransform(-offset.X, -offset.Y, MatrixOrder.Append); + + PointF ptF = (PointF)this.Location; + + ptF = Utility.TransformOnePoint(this.transform, ptF); + + ptF.X *= (float)OwnerList.ScaleFactor.Ratio; + ptF.Y *= (float)OwnerList.ScaleFactor.Ratio; + + PointF[] pts = new PointF[8] + { + new PointF(-1, -1), // up+left + new PointF(+1, -1), // up+right + new PointF(+1, +1), // down+right + new PointF(-1, +1), // down+left + + new PointF(-1, 0), // left + new PointF(+1, 0), // right + new PointF(0, -1), // up + new PointF(0, +1) // down + }; + + Utility.RotateVectors(pts, this.transformAngle); + Utility.NormalizeVectors(pts); + + using (Pen white = new Pen(Color.FromArgb(this.alpha, Color.White), -1.0f), + black = new Pen(Color.FromArgb(this.alpha, Color.Black), -1.0f)) + { + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.None; + + if (this.shape != MoveNubShape.Circle) + { + PointF[] outer = new PointF[4] + { + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[0], ourSize)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[1], ourSize)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[2], ourSize)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[3], ourSize)) + }; + + PointF[] middle = new PointF[4] + { + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[0], ourSize - 1)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[1], ourSize - 1)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[2], ourSize - 1)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[3], ourSize - 1)) + }; + + PointF[] inner = new PointF[4] + { + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[0], ourSize - 2)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[1], ourSize - 2)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[2], ourSize - 2)), + Utility.AddVectors(ptF, Utility.MultiplyVector(pts[3], ourSize - 2)) + }; + + g.DrawPolygon(white, outer); + g.DrawPolygon(black, middle); + g.DrawPolygon(white, inner); + } + else if (this.shape == MoveNubShape.Circle) + { + RectangleF rect = new RectangleF(ptF, new SizeF(0, 0)); + rect.Inflate(ourSize - 1, ourSize - 1); + g.DrawEllipse(white, rect); + rect.Inflate(-1.0f, -1.0f); + g.DrawEllipse(black, rect); + rect.Inflate(-1.0f, -1.0f); + g.DrawEllipse(white, rect); + } + + if (this.shape == MoveNubShape.Compass) + { + black.SetLineCap(LineCap.Round, LineCap.DiamondAnchor, DashCap.Flat); + black.EndCap = LineCap.ArrowAnchor; + black.StartCap = LineCap.ArrowAnchor; + white.SetLineCap(LineCap.Round, LineCap.DiamondAnchor, DashCap.Flat); + white.EndCap = LineCap.ArrowAnchor; + white.StartCap = LineCap.ArrowAnchor; + + PointF ul = Utility.AddVectors(ptF, Utility.MultiplyVector(pts[0], ourSize - 1)); + PointF ur = Utility.AddVectors(ptF, Utility.MultiplyVector(pts[1], ourSize - 1)); + PointF lr = Utility.AddVectors(ptF, Utility.MultiplyVector(pts[2], ourSize - 1)); + PointF ll = Utility.AddVectors(ptF, Utility.MultiplyVector(pts[3], ourSize - 1)); + + PointF top = Utility.MultiplyVector(Utility.AddVectors(ul, ur), 0.5f); + PointF left = Utility.MultiplyVector(Utility.AddVectors(ul, ll), 0.5f); + PointF right = Utility.MultiplyVector(Utility.AddVectors(ur, lr), 0.5f); + PointF bottom = Utility.MultiplyVector(Utility.AddVectors(ll, lr), 0.5f); + + using (SolidBrush whiteBrush = new SolidBrush(white.Color)) + { + PointF[] poly = new PointF[] { ul, ur, lr, ll }; + g.FillPolygon(whiteBrush, poly, FillMode.Winding); + } + + g.DrawLine(black, top, bottom); + g.DrawLine(black, left, right); + } + + g.PixelOffsetMode = oldPOM; + } + } + } + + public MoveNubRenderer(SurfaceBoxRendererList ownerList) + : base(ownerList) + { + this.shape = MoveNubShape.Square; + this.transform = new Matrix(); + this.transform.Reset(); + this.alpha = 255; + Size = new SizeF(5, 5); + } + } +} diff --git a/src/MoveNubShape.cs b/src/MoveNubShape.cs new file mode 100644 index 0000000..af9de99 --- /dev/null +++ b/src/MoveNubShape.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + internal enum MoveNubShape + { + Square, + Compass, + Circle, + } +} diff --git a/src/NewFileDialog.cs b/src/NewFileDialog.cs new file mode 100644 index 0000000..ddd58c7 --- /dev/null +++ b/src/NewFileDialog.cs @@ -0,0 +1,245 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class NewFileDialog + : ResizeDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + public NewFileDialog() + { + InitializeComponent(); + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileNewIcon.png").Reference, Utility.TransparentKey); + this.Text = PdnResources.GetString("NewFileDialog.Text"); // "New"; + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + ((System.ComponentModel.ISupportInitialize)(this.percentUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.resolutionUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelWidthUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelHeightUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.printWidthUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.printHeightUpDown)).BeginInit(); + this.SuspendLayout(); + // + // constrainCheckBox + // + this.constrainCheckBox.Location = new System.Drawing.Point(8, 28); + this.constrainCheckBox.Name = "constrainCheckBox"; + // + // okButton + // + this.okButton.Location = new System.Drawing.Point(123, 217); + this.okButton.Name = "okButton"; + // + // cancelButton + // + this.cancelButton.Location = new System.Drawing.Point(201, 217); + this.cancelButton.Name = "cancelButton"; + // + // percentSignLabel + // + this.percentSignLabel.Enabled = false; + this.percentSignLabel.Location = new System.Drawing.Point(520, 48); + this.percentSignLabel.Name = "percentSignLabel"; + this.percentSignLabel.Visible = false; + // + // percentUpDown + // + this.percentUpDown.Location = new System.Drawing.Point(440, 48); + this.percentUpDown.Name = "percentUpDown"; + this.percentUpDown.Visible = false; + // + // absoluteRB + // + this.absoluteRB.Enabled = false; + this.absoluteRB.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.absoluteRB.Location = new System.Drawing.Point(328, 72); + this.absoluteRB.Name = "absoluteRB"; + this.absoluteRB.Visible = false; + // + // percentRB + // + this.percentRB.Enabled = false; + this.percentRB.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.percentRB.Location = new System.Drawing.Point(328, 40); + this.percentRB.Name = "percentRB"; + this.percentRB.Visible = false; + // + // asteriskTextLabel + // + this.asteriskTextLabel.Enabled = false; + this.asteriskTextLabel.Location = new System.Drawing.Point(328, 280); + this.asteriskTextLabel.Name = "asteriskTextLabel"; + // + // asteriskLabel + // + this.asteriskLabel.Enabled = false; + this.asteriskLabel.Location = new System.Drawing.Point(592, 16); + this.asteriskLabel.Name = "asteriskLabel"; + // + // resizedImageHeader + // + this.resizedImageHeader.Name = "resizedImageHeader"; + this.resizedImageHeader.Size = new System.Drawing.Size(274, 16); + // + // resolutionLabel + // + this.resolutionLabel.Location = new System.Drawing.Point(16, 118); + this.resolutionLabel.Name = "resolutionLabel"; + // + // unitsComboBox2 + // + this.unitsComboBox2.Location = new System.Drawing.Point(184, 117); + this.unitsComboBox2.Name = "unitsComboBox2"; + // + // unitsComboBox1 + // + this.unitsComboBox1.Location = new System.Drawing.Point(184, 160); + this.unitsComboBox1.Name = "unitsComboBox1"; + // + // resolutionUpDown + // + this.resolutionUpDown.Location = new System.Drawing.Point(104, 117); + this.resolutionUpDown.Name = "resolutionUpDown"; + // + // newWidthLabel1 + // + this.newWidthLabel1.Location = new System.Drawing.Point(16, 70); + this.newWidthLabel1.Name = "newWidthLabel1"; + // + // newHeightLabel1 + // + this.newHeightLabel1.Location = new System.Drawing.Point(16, 94); + this.newHeightLabel1.Name = "newHeightLabel1"; + // + // pixelsLabel1 + // + this.pixelsLabel1.Location = new System.Drawing.Point(184, 70); + this.pixelsLabel1.Name = "pixelsLabel1"; + // + // newWidthLabel2 + // + this.newWidthLabel2.Location = new System.Drawing.Point(16, 161); + this.newWidthLabel2.Name = "newWidthLabel2"; + // + // newHeightLabel2 + // + this.newHeightLabel2.Location = new System.Drawing.Point(16, 185); + this.newHeightLabel2.Name = "newHeightLabel2"; + // + // pixelsLabel2 + // + this.pixelsLabel2.Location = new System.Drawing.Point(184, 94); + this.pixelsLabel2.Name = "pixelsLabel2"; + // + // unitsLabel1 + // + this.unitsLabel1.Location = new System.Drawing.Point(184, 186); + this.unitsLabel1.Name = "unitsLabel1"; + // + // pixelWidthUpDown + // + this.pixelWidthUpDown.Location = new System.Drawing.Point(104, 69); + this.pixelWidthUpDown.Name = "pixelWidthUpDown"; + // + // pixelHeightUpDown + // + this.pixelHeightUpDown.Location = new System.Drawing.Point(104, 93); + this.pixelHeightUpDown.Name = "pixelHeightUpDown"; + // + // printWidthUpDown + // + this.printWidthUpDown.Location = new System.Drawing.Point(104, 160); + this.printWidthUpDown.Name = "printWidthUpDown"; + // + // printHeightUpDown + // + this.printHeightUpDown.Location = new System.Drawing.Point(104, 184); + this.printHeightUpDown.Name = "printHeightUpDown"; + // + // pixelSizeHeader + // + this.pixelSizeHeader.Location = new System.Drawing.Point(6, 50); + this.pixelSizeHeader.Name = "pixelSizeHeader"; + // + // printSizeHeader + // + this.printSizeHeader.Location = new System.Drawing.Point(6, 141); + this.printSizeHeader.Name = "printSizeHeader"; + // + // resamplingLabel + // + this.resamplingLabel.Enabled = false; + this.resamplingLabel.Location = new System.Drawing.Point(320, 24); + this.resamplingLabel.Name = "resamplingLabel"; + this.resamplingLabel.Visible = false; + // + // resamplingAlgorithmComboBox + // + this.resamplingAlgorithmComboBox.Enabled = false; + this.resamplingAlgorithmComboBox.Location = new System.Drawing.Point(440, 16); + this.resamplingAlgorithmComboBox.Name = "resamplingAlgorithmComboBox"; + this.resamplingAlgorithmComboBox.Visible = false; + // + // NewFileDialog + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(279, 246); + this.Location = new System.Drawing.Point(0, 0); + this.Name = "NewFileDialog"; + this.Controls.SetChildIndex(this.printWidthUpDown, 0); + this.Controls.SetChildIndex(this.printHeightUpDown, 0); + ((System.ComponentModel.ISupportInitialize)(this.percentUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.resolutionUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelWidthUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelHeightUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.printWidthUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.printHeightUpDown)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + } +} diff --git a/src/NewFileDialog.resx b/src/NewFileDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/PaletteCollection.cs b/src/PaletteCollection.cs new file mode 100644 index 0000000..92758ae --- /dev/null +++ b/src/PaletteCollection.cs @@ -0,0 +1,577 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace PaintDotNet +{ + internal sealed class PaletteCollection + { + /// + /// The required number of colors for a palette. + /// + /// + /// If a palette is loaded with fewer colors than this, then it will be padded with entries + /// that are equal to DefaultColor. If a palette is loaded with more colors than this, then + /// the 97th through the last color will be discarded. + /// + public const int PaletteColorCount = 96; + private const char lineCommentChar = ';'; + private static readonly Encoding paletteFileEncoding = Encoding.UTF8; + + private Dictionary palettes; // maps from Name -> Palette + + public static bool ValidatePaletteName(string paletteName) + { + if (string.IsNullOrEmpty(paletteName)) + { + return false; + } + + try + { + string fileName = Path.ChangeExtension(paletteName, PalettesFileExtension); + string pathName = Path.Combine(PalettesPath, fileName); + char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); + char[] invalidPathNameChars = Path.GetInvalidPathChars(); + + if (pathName.IndexOfAny(invalidPathNameChars) != -1) + { + return false; + } + + if (fileName.IndexOfAny(invalidFileNameChars) != -1) + { + return false; + } + + return true; + } + + catch (ArgumentNullException) + { + return false; + } + + catch (ArgumentException) + { + return false; + } + } + + public static ColorBgra DefaultColor + { + get + { + return ColorBgra.White; + } + } + + public static ColorBgra[] DefaultPalette + { + get + { + return + new ColorBgra[PaletteColorCount] + { + ColorBgra.FromUInt32(0xff000000), + ColorBgra.FromUInt32(0xff404040), + ColorBgra.FromUInt32(0xffff0000), + ColorBgra.FromUInt32(0xffff6a00), + ColorBgra.FromUInt32(0xffffd800), + ColorBgra.FromUInt32(0xffb6ff00), + ColorBgra.FromUInt32(0xff4cff00), + ColorBgra.FromUInt32(0xff00ff21), + ColorBgra.FromUInt32(0xff00ff90), + ColorBgra.FromUInt32(0xff00ffff), + ColorBgra.FromUInt32(0xff0094ff), + ColorBgra.FromUInt32(0xff0026ff), + ColorBgra.FromUInt32(0xff4800ff), + ColorBgra.FromUInt32(0xffb200ff), + ColorBgra.FromUInt32(0xffff00dc), + ColorBgra.FromUInt32(0xffff006e), + ColorBgra.FromUInt32(0xffffffff), + ColorBgra.FromUInt32(0xff808080), + ColorBgra.FromUInt32(0xff7f0000), + ColorBgra.FromUInt32(0xff7f3300), + ColorBgra.FromUInt32(0xff7f6a00), + ColorBgra.FromUInt32(0xff5b7f00), + ColorBgra.FromUInt32(0xff267f00), + ColorBgra.FromUInt32(0xff007f0e), + ColorBgra.FromUInt32(0xff007f46), + ColorBgra.FromUInt32(0xff007f7f), + ColorBgra.FromUInt32(0xff004a7f), + ColorBgra.FromUInt32(0xff00137f), + ColorBgra.FromUInt32(0xff21007f), + ColorBgra.FromUInt32(0xff57007f), + ColorBgra.FromUInt32(0xff7f006e), + ColorBgra.FromUInt32(0xff7f0037), + ColorBgra.FromUInt32(0xffa0a0a0), + ColorBgra.FromUInt32(0xff303030), + ColorBgra.FromUInt32(0xffff7f7f), + ColorBgra.FromUInt32(0xffffb27f), + ColorBgra.FromUInt32(0xffffe97f), + ColorBgra.FromUInt32(0xffdaff7f), + ColorBgra.FromUInt32(0xffa5ff7f), + ColorBgra.FromUInt32(0xff7fff8e), + ColorBgra.FromUInt32(0xff7fffc5), + ColorBgra.FromUInt32(0xff7fffff), + ColorBgra.FromUInt32(0xff7fc9ff), + ColorBgra.FromUInt32(0xff7f92ff), + ColorBgra.FromUInt32(0xffa17fff), + ColorBgra.FromUInt32(0xffd67fff), + ColorBgra.FromUInt32(0xffff7fed), + ColorBgra.FromUInt32(0xffff7fb6), + ColorBgra.FromUInt32(0xffc0c0c0), + ColorBgra.FromUInt32(0xff606060), + ColorBgra.FromUInt32(0xff7f3f3f), + ColorBgra.FromUInt32(0xff7f593f), + ColorBgra.FromUInt32(0xff7f743f), + ColorBgra.FromUInt32(0xff6d7f3f), + ColorBgra.FromUInt32(0xff527f3f), + ColorBgra.FromUInt32(0xff3f7f47), + ColorBgra.FromUInt32(0xff3f7f62), + ColorBgra.FromUInt32(0xff3f7f7f), + ColorBgra.FromUInt32(0xff3f647f), + ColorBgra.FromUInt32(0xff3f497f), + ColorBgra.FromUInt32(0xff503f7f), + ColorBgra.FromUInt32(0xff6b3f7f), + ColorBgra.FromUInt32(0xff7f3f76), + ColorBgra.FromUInt32(0xff7f3f5b), + ColorBgra.FromUInt32(0x80000000), + ColorBgra.FromUInt32(0x80404040), + ColorBgra.FromUInt32(0x80ff0000), + ColorBgra.FromUInt32(0x80ff6a00), + ColorBgra.FromUInt32(0x80ffd800), + ColorBgra.FromUInt32(0x80b6ff00), + ColorBgra.FromUInt32(0x804cff00), + ColorBgra.FromUInt32(0x8000ff21), + ColorBgra.FromUInt32(0x8000ff90), + ColorBgra.FromUInt32(0x8000ffff), + ColorBgra.FromUInt32(0x800094ff), + ColorBgra.FromUInt32(0x800026ff), + ColorBgra.FromUInt32(0x804800ff), + ColorBgra.FromUInt32(0x80b200ff), + ColorBgra.FromUInt32(0x80ff00dc), + ColorBgra.FromUInt32(0x80ff006e), + ColorBgra.FromUInt32(0x80ffffff), + ColorBgra.FromUInt32(0x80808080), + ColorBgra.FromUInt32(0x807f0000), + ColorBgra.FromUInt32(0x807f3300), + ColorBgra.FromUInt32(0x807f6a00), + ColorBgra.FromUInt32(0x805b7f00), + ColorBgra.FromUInt32(0x80267f00), + ColorBgra.FromUInt32(0x80007f0e), + ColorBgra.FromUInt32(0x80007f46), + ColorBgra.FromUInt32(0x80007f7f), + ColorBgra.FromUInt32(0x80004a7f), + ColorBgra.FromUInt32(0x8000137f), + ColorBgra.FromUInt32(0x8021007f), + ColorBgra.FromUInt32(0x8057007f), + ColorBgra.FromUInt32(0x807f006e), + ColorBgra.FromUInt32(0x807f0037) + }; + } + } + + public string[] PaletteNames + { + get + { + Dictionary.KeyCollection keyCollection = this.palettes.Keys; + string[] keys = new string[keyCollection.Count]; + int index = 0; + + foreach (string key in keyCollection) + { + keys[index] = key; + ++index; + } + + return keys; + } + } + + public PaletteCollection() + { + this.palettes = new Dictionary(); + } + + public static string PalettesFileExtension + { + get + { + // seems like using .txt is just simpler: makes it obvious that it is human readable/writable, and not xml (which has high perf costs @ startup) + return ".txt"; + } + } + + public static string PalettesPath + { + get + { + string userDataPath = PdnInfo.UserDataPath; + string palettesDirName = PdnResources.GetString("ColorPalettes.UserDataSubDirName"); + string palettesPath = Path.Combine(userDataPath, palettesDirName); + return palettesPath; + } + } + + private static bool ParseColor(string colorString, out ColorBgra color) + { + bool returnVal; + + try + { + color = ColorBgra.ParseHexString(colorString); + returnVal = true; + } + + catch (Exception ex) + { + Tracing.Ping("Exception while parsing color string '" + colorString + "' :" + ex.ToString()); + color = DefaultColor; + returnVal = false; + } + + return returnVal; + } + + public static string RemoveComments(string line) + { + int commentIndex = line.IndexOf(lineCommentChar); + + if (commentIndex != -1) + { + return line.Substring(0, commentIndex); + } + else + { + return line; + } + } + + public static bool ParsePaletteLine(string line, out ColorBgra color) + { + color = DefaultColor; + + if (line == null) + { + return false; + } + + string trimmed1 = RemoveComments(line); + string trimmed = trimmed1.Trim(); + + if (trimmed.Length == 0) + { + return false; + } + + bool gotColor = ParseColor(trimmed, out color); + return gotColor; + } + + public static ColorBgra[] ParsePaletteString(string paletteString) + { + List palette = new List(); + StringReader sr = new StringReader(paletteString); + + while (true) + { + string line = sr.ReadLine(); + + if (line == null) + { + break; + } + + ColorBgra color; + bool gotColor = ParsePaletteLine(line, out color); + + if (gotColor && palette.Count < PaletteColorCount) + { + palette.Add(color); + } + } + + return palette.ToArray(); + } + + public static ColorBgra[] LoadPalette(string palettePath) + { + ColorBgra[] palette = null; + FileStream paletteFile = new FileStream(palettePath, FileMode.Open, FileAccess.Read, FileShare.Read); + + try + { + StreamReader sr = new StreamReader(paletteFile, paletteFileEncoding); + + try + { + string paletteString = sr.ReadToEnd(); + palette = ParsePaletteString(paletteString); + } + + finally + { + sr.Close(); // as per docs, this also closes paletteFile + sr = null; + paletteFile = null; + } + } + + finally + { + if (paletteFile != null) + { + paletteFile.Close(); + paletteFile = null; + } + } + + if (palette == null) + { + return new ColorBgra[0]; + } + else + { + return palette; + } + } + + private static string FormatColor(ColorBgra color) + { + return color.ToHexString(); + } + + public static string GetPaletteSaveString(ColorBgra[] palette) + { + StringWriter sw = new StringWriter(); + + string header = PdnResources.GetString("ColorPalette.SaveHeader"); + sw.WriteLine(header); + + foreach (ColorBgra color in palette) + { + string colorString = FormatColor(color); + sw.WriteLine(colorString); + } + + return sw.ToString(); + } + + public static void SavePalette(string palettePath, ColorBgra[] palette) + { + FileStream paletteFile = new FileStream(palettePath, FileMode.Create, FileAccess.Write, FileShare.Read); + + try + { + StreamWriter sw = new StreamWriter(paletteFile, paletteFileEncoding); + + try + { + string paletteString = GetPaletteSaveString(palette); + sw.WriteLine(paletteString); + } + + finally + { + sw.Close(); // as per documentation, this closes paletteFile as well + sw = null; + paletteFile = null; + } + } + + finally + { + if (paletteFile != null) + { + paletteFile.Close(); + paletteFile = null; + } + } + } + + private bool DoesPalettesPathExist() + { + string palettesPath = PalettesPath; + bool returnVal; + + try + { + returnVal = Directory.Exists(palettesPath); + } + + catch (Exception ex) + { + Tracing.Ping("Exception while querying whether palettes path, '" + palettesPath + "' exists: " + ex.ToString()); + returnVal = false; + } + + return returnVal; + } + + public static void EnsurePalettesPathExists() + { + string palettesPath = PalettesPath; + + try + { + if (!Directory.Exists(palettesPath)) + { + Directory.CreateDirectory(palettesPath); + } + } + + catch (Exception ex) + { + // Fail silently + Tracing.Ping("Exception while ensuring that " + palettesPath + " exists: " + ex.ToString()); + } + } + + public void Load() + { + if (!DoesPalettesPathExist()) + { + // can't load anything! no custom palettes exist. + // we really don't want to create this directory unless they've + // saved anything in to it. this is especially important if they + // install and then set their language. the path name is localized + // so we only want the final name to be there. + this.palettes = new Dictionary(); + return; + } + + string[] pathNames = new string[0]; + + try + { + pathNames = Directory.GetFiles(PalettesPath, "*" + PalettesFileExtension); + } + + catch (Exception ex) + { + // Trace the error, but otherwise fail silently + Tracing.Ping("Exception while retrieving list of palette filenames: " + ex.ToString()); + } + + // Now, load the palettes + Dictionary newPalettes = new Dictionary(); + + foreach (string pathName in pathNames) + { + ColorBgra[] palette = LoadPalette(pathName); + ColorBgra[] goodPalette = EnsureValidPaletteSize(palette); + string fileName = Path.GetFileName(pathName); + string paletteName = Path.ChangeExtension(fileName, null); + newPalettes.Add(paletteName, goodPalette); + } + + this.palettes = newPalettes; + } + + public void Save() + { + EnsurePalettesPathExists(); + + string palettesPath = PalettesPath; + + foreach (string paletteName in this.palettes.Keys) + { + ColorBgra[] palette = this.palettes[paletteName]; + ColorBgra[] goodPalette = EnsureValidPaletteSize(palette); + string fileName = Path.ChangeExtension(paletteName, PalettesFileExtension); + string pathName = Path.Combine(palettesPath, fileName); + SavePalette(pathName, goodPalette); + } + } + + public static ColorBgra[] EnsureValidPaletteSize(ColorBgra[] colors) + { + ColorBgra[] validPalette = new ColorBgra[PaletteColorCount]; + + for (int i = 0; i < PaletteColorCount; ++i) + { + if (i >= colors.Length) + { + validPalette[i] = DefaultColor; + } + else + { + validPalette[i] = colors[i]; + } + } + + return validPalette; + } + + public ColorBgra[] Get(string name) + { + string existingKeyName; + bool contains = Contains(name, out existingKeyName); + + if (contains) + { + ColorBgra[] colors = this.palettes[existingKeyName]; + return (ColorBgra[])colors.Clone(); + } + else + { + return null; + } + } + + public bool Contains(string name, out string existingKeyName) + { + foreach (string key in this.palettes.Keys) + { + if (string.Compare(key, name, StringComparison.InvariantCultureIgnoreCase) == 0) + { + existingKeyName = key; + return true; + } + } + + existingKeyName = null; + return false; + } + + public void AddOrUpdate(string name, ColorBgra[] colors) + { + if (colors.Length != PaletteColorCount) + { + throw new ArgumentException("palette must have exactly " + PaletteColorCount.ToString() + " colors (actual: " + colors.Length.ToString() + ")"); + } + + Delete(name); + this.palettes.Add(name, colors); + } + + public bool Delete(string name) + { + string existingKeyName; + bool contains = Contains(name, out existingKeyName); + + if (contains) + { + this.palettes.Remove(existingKeyName); + return true; + } + + return false; + } + } +} diff --git a/src/PdnMenuItem.cs b/src/PdnMenuItem.cs new file mode 100644 index 0000000..900439a --- /dev/null +++ b/src/PdnMenuItem.cs @@ -0,0 +1,308 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class PdnMenuItem + : ToolStripMenuItem, + IFormAssociate + { + private string textResourceName = null; + private const char noMnemonicChar = (char)0; + private const char mnemonicPrefix = '&'; + private bool iconsLoaded = false; + private bool namesLoaded = false; + private AppWorkspace appWorkspace; + private Keys registeredHotKey = Keys.None; + + [Browsable(false)] + public AppWorkspace AppWorkspace + { + get + { + return this.appWorkspace; + } + + set + { + if (value != this.appWorkspace) + { + OnAppWorkspaceChanging(); + this.appWorkspace = value; + OnAppWorkspaceChanged(); + } + } + } + + public Form AssociatedForm + { + get + { + if (this.appWorkspace == null) + { + return null; + } + else + { + return this.appWorkspace.FindForm(); + } + } + } + + public new Keys ShortcutKeys + { + get + { + return base.ShortcutKeys; + } + + set + { + if (ShortcutKeys != Keys.None) + { + PdnBaseForm.UnregisterFormHotKey(ShortcutKeys, OnShortcutKeyPressed); + } + + PdnBaseForm.RegisterFormHotKey(value, OnShortcutKeyPressed); + + base.ShortcutKeys = value; + } + } + + public bool HasMnemonic + { + get + { + return (Mnemonic != noMnemonicChar); + } + } + + public char Mnemonic + { + get + { + if (string.IsNullOrEmpty(this.Text)) + { + return noMnemonicChar; + } + + int mnemonicPrefixIndex = this.Text.IndexOf(mnemonicPrefix); + + if (mnemonicPrefixIndex >= 0 && mnemonicPrefixIndex < this.Text.Length - 1) + { + return this.Text[mnemonicPrefixIndex + 1]; + } + else + { + return noMnemonicChar; + } + } + } + + public void PerformClickAsync() + { + this.Owner.BeginInvoke(new Procedure(this.PerformClick)); + } + + protected virtual void OnAppWorkspaceChanging() + { + foreach (ToolStripItem item in this.DropDownItems) + { + PdnMenuItem asPMI = item as PdnMenuItem; + + if (asPMI != null) + { + asPMI.AppWorkspace = null; + } + } + } + + protected virtual void OnAppWorkspaceChanged() + { + foreach (ToolStripItem item in this.DropDownItems) + { + PdnMenuItem asPMI = item as PdnMenuItem; + + if (asPMI != null) + { + asPMI.AppWorkspace = AppWorkspace; + } + } + } + + public PdnMenuItem(string name, Image image, EventHandler eventHandler) + : base(name, image, eventHandler) + { + Constructor(); + } + + public PdnMenuItem() + { + Constructor(); + } + + private void Constructor() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + // HACK: For some reason, ToolStripMenuItem does not have an OnDropDownOpening method. + this.DropDownOpening += new EventHandler(PdnMenuItem_DropDownOpening); + } + + private bool OnShortcutKeyPressed(Keys keys) + { + PerformClick(); + return true; + } + + private bool OnAccessHotKeyPressed(Keys keys) + { + ShowDropDown(); + return true; + } + + protected override void OnTextChanged(EventArgs e) + { + if (this.registeredHotKey != Keys.None) + { + PdnBaseForm.UnregisterFormHotKey(this.registeredHotKey, OnAccessHotKeyPressed); + } + + char mnemonic = this.Mnemonic; + + if (mnemonic != noMnemonicChar && !IsOnDropDown) + { + Keys hotKey = Utility.LetterOrDigitCharToKeys(mnemonic); + PdnBaseForm.RegisterFormHotKey(Keys.Alt | hotKey, OnAccessHotKeyPressed); + } + + base.OnTextChanged(e); + } + + private void PdnMenuItem_DropDownOpening(object sender, EventArgs e) + { + OnDropDownOpening(e); + } + + protected virtual void OnDropDownOpening(EventArgs e) + { + if (!this.namesLoaded) + { + LoadNames(this.Name); + } + + if (!this.iconsLoaded) + { + LoadIcons(); + } + } + + public void LoadNames(string baseName) + { + foreach (ToolStripItem item in this.DropDownItems) + { + string itemNameBase = baseName + "." + item.Name; + string itemNameText = itemNameBase + ".Text"; + string text = PdnResources.GetString(itemNameText); + + if (text != null) + { + item.Text = text; + } + + PdnMenuItem pmi = item as PdnMenuItem; + if (pmi != null) + { + pmi.textResourceName = itemNameText; + pmi.LoadNames(itemNameBase); + } + } + + this.namesLoaded = true; + } + + public void SetIcon(string imageName) + { + this.ImageTransparentColor = Utility.TransparentKey; + this.Image = PdnResources.GetImageResource(imageName).Reference; + } + + public void SetIcon(ImageResource image) + { + this.ImageTransparentColor = Utility.TransparentKey; + this.Image = image.Reference; + } + + public void LoadIcons() + { + Type ourType = this.GetType(); + + FieldInfo[] fields = ourType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + + foreach (FieldInfo fi in fields) + { + if (fi.FieldType.IsSubclassOf(typeof(PdnMenuItem)) || + fi.FieldType == typeof(PdnMenuItem)) + { + string iconFileName = "Icons." + fi.Name[0].ToString().ToUpper() + fi.Name.Substring(1) + "Icon.png"; + PdnMenuItem mi = (PdnMenuItem)fi.GetValue(this); + Stream iconStream = PdnResources.GetResourceStream(iconFileName); + + if (iconStream != null) + { + iconStream.Dispose(); + mi.SetIcon(iconFileName); + } + else + { + Tracing.Ping(iconFileName + " not found"); + } + } + } + + this.iconsLoaded = true; + } + + protected override void OnDropDownClosed(EventArgs e) + { + foreach (ToolStripItem item in this.DropDownItems) + { + item.Enabled = true; + } + + base.OnDropDownClosed(e); + } + + protected override void OnClick(EventArgs e) + { + if (Form.ActiveForm != null) + { + Form.ActiveForm.BeginInvoke(new Procedure(PdnBaseForm.UpdateAllForms)); + } + + string featureName = this.Name ?? this.Text; + + Tracing.LogFeature(featureName); + + base.OnClick(e); + } + } +} diff --git a/src/PdnRepair/AssemblyInfo.cs b/src/PdnRepair/AssemblyInfo.cs new file mode 100644 index 0000000..a94c74a --- /dev/null +++ b/src/PdnRepair/AssemblyInfo.cs @@ -0,0 +1,25 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET Repair Utility")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] diff --git a/src/PdnRepair/PdnRepair.csproj b/src/PdnRepair/PdnRepair.csproj new file mode 100644 index 0000000..604ae31 --- /dev/null +++ b/src/PdnRepair/PdnRepair.csproj @@ -0,0 +1,65 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884} + Exe + Properties + PdnRepair + PdnRepair + + + 2.0 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + @rem Embed manifest +call "$(SolutionDir)Manifests\embedManifest.bat" "$(TargetPath)" "$(SolutionDir)Manifests\requireAdministrator.xml" +call "$(SolutionDir)Manifests\embedManifest.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" "$(SolutionDir)Manifests\requireAdministrator.xml" + +@rem Sign +call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + \ No newline at end of file diff --git a/src/PdnRepair/Program.cs b/src/PdnRepair/Program.cs new file mode 100644 index 0000000..a0d85ed --- /dev/null +++ b/src/PdnRepair/Program.cs @@ -0,0 +1,151 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// I made this little utility because I was getting a lot of crash logs that +// said the strings or resources files were missing. Reinstalling almost +// always fixed the problem. I had this happen on my own system a few times, +// but I could never figure out the cause. So what this utility does is +// run an MSI reinstall operation with a flag telling it to only replace +// missing files. The main PaintDotNet.exe can detect this situation of +// missing files and it then gives the user the ability to run this utility +// by clicking a button. +// This utility must have a UAC manifest for requiring administrator, and it +// should also be signed with Authenticode so that the UAC consent UI in +// Vista does not give horrible warnings. +// -Rick Brewster + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Threading; +using System.Text; + +namespace PdnRepair +{ + public sealed class Program + { + [DllImport("msi.dll", CharSet = CharSet.Unicode)] + internal static extern uint MsiReinstallProductW( + [MarshalAs(UnmanagedType.LPWStr)] string szProduct, + uint dwReinstallMode); + + internal const uint REINSTALLMODE_REPAIR = 0x00000001; + internal const uint REINSTALLMODE_FILEMISSING = 0x00000002; + internal const uint REINSTALLMODE_FILEOLDERVERSION = 0x00000004; + internal const uint REINSTALLMODE_FILEEQUALVERSION = 0x00000008; + internal const uint REINSTALLMODE_FILEEXACT = 0x00000010; + internal const uint REINSTALLMODE_FILEVERIFY = 0x00000020; + internal const uint REINSTALLMODE_FILEREPLACE = 0x00000040; + internal const uint REINSTALLMODE_MACHINEDATA = 0x00000080; + internal const uint REINSTALLMODE_USERDATA = 0x00000100; + internal const uint REINSTALLMODE_SHORTCUT = 0x00000200; + internal const uint REINSTALLMODE_PACKAGE = 0x00000400; + + public static int Main(string[] args) + { + int returnVal = 0; + + try + { + returnVal = MainImpl(args); + } + + catch (Exception ex) + { + Console.WriteLine(); + Console.WriteLine("--- Error: "); + Console.WriteLine(ex.ToString()); + + returnVal = -1; + } + + if (args.Length == 0) + { + Console.WriteLine(); + Console.Write("Press Enter to exit..."); + Console.ReadLine(); + } + + return returnVal; + } + + private static int MainImpl(string[] args) + { + bool success = true; + int returnVal = 0; + + Console.WriteLine("Paint.NET Repair Tool"); + Console.WriteLine(); + + if (success) + { + AppDomain domain = Thread.GetDomain(); + domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); + WindowsPrincipal principal = (WindowsPrincipal)Thread.CurrentPrincipal; + bool isAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator); + + if (!isAdmin) + { + Console.WriteLine("This utility must be run with administrator privilege."); + success = false; + returnVal = 740; // ERROR_ELEVATION_REQUIRED + } + } + + RegistryKey key = null; + if (success) + { + Console.Write(@"* Opening registry key, HKEY_LOCAL_MACHINE\SOFTWARE\Paint.NET: "); + key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Paint.NET", false); + + if (key != null) + { + Console.WriteLine("ok"); + } + else + { + Console.WriteLine("null"); + success = false; + returnVal = -1; + } + } + + string productCode = null; + if (success) + { + Console.Write("* Retrieving MSI product code GUID: "); + string productCodeString = (string)key.GetValue("ProductCode", null, RegistryValueOptions.DoNotExpandEnvironmentNames); + Guid productCodeGuid = new Guid(productCodeString); + productCode = productCodeGuid.ToString("B", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture); + Console.WriteLine(productCode); + } + + if (success) + { + Console.Write("* Attempting to repair: "); + uint dwResult = MsiReinstallProductW(productCode, REINSTALLMODE_FILEMISSING); + + if (dwResult == 0) + { + Console.WriteLine("success"); + } + else + { + Console.WriteLine("failed, dwResult=" + dwResult.ToString()); + returnVal = unchecked((int)dwResult); + } + } + + return returnVal; + } + } +} diff --git a/src/PdnRepair/app.config b/src/PdnRepair/app.config new file mode 100644 index 0000000..6c98a46 --- /dev/null +++ b/src/PdnRepair/app.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/PdnStatusBar.cs b/src/PdnStatusBar.cs new file mode 100644 index 0000000..16dfc6c --- /dev/null +++ b/src/PdnStatusBar.cs @@ -0,0 +1,224 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal sealed class PdnStatusBar + : StatusStrip, + IStatusBarProgress + { + private System.Windows.Forms.ToolStripStatusLabel contextStatusLabel; + private System.Windows.Forms.ToolStripSeparator progressStatusSeparator; + private System.Windows.Forms.ToolStripProgressBar progressStatusBar; + private System.Windows.Forms.ToolStripStatusLabel imageInfoStatusLabel; + private System.Windows.Forms.ToolStripStatusLabel cursorInfoStatusLabel; + + private string progressTextFormat = PdnResources.GetString("StatusBar.Progress.Percentage.Format"); + private ImageResource contextStatusImage; + + public string ImageInfoStatusText + { + get + { + return this.imageInfoStatusLabel.Text; + } + + set + { + this.imageInfoStatusLabel.Text = value; + Update(); + } + } + + public string ContextStatusText + { + get + { + return this.contextStatusLabel.Text; + } + + set + { + this.contextStatusLabel.Text = value; + Update(); + } + } + + public ImageResource ContextStatusImage + { + get + { + return this.contextStatusImage; + } + + set + { + this.contextStatusImage = value; + + if (this.contextStatusImage == null) + { + this.contextStatusLabel.Image = null; + } + else + { + this.contextStatusLabel.Image = this.contextStatusImage.Reference; + } + + Update(); + } + } + + public string CursorInfoText + { + get + { + return this.cursorInfoStatusLabel.Text; + } + + set + { + this.cursorInfoStatusLabel.Text = value; + Update(); + } + } + + public void ResetProgressStatusBarAsync() + { + this.BeginInvoke(new Procedure(ResetProgressStatusBar)); + } + + public void EraseProgressStatusBar() + { + try + { + this.progressStatusSeparator.Visible = false; + this.progressStatusBar.Visible = false; + this.progressStatusBar.Value = 0; + } + + catch (NullReferenceException) + { + // See bug #2212 -- appears to be a bug in the framework + } + } + + public void EraseProgressStatusBarAsync() + { + this.BeginInvoke(new Procedure(EraseProgressStatusBar)); + } + + public void ResetProgressStatusBar() + { + try + { + this.progressStatusBar.Value = 0; + this.progressStatusSeparator.Visible = true; + this.progressStatusBar.Visible = true; + } + + catch (NullReferenceException nrex) + { + Tracing.Ping(nrex.ToString()); + } + } + + public double GetProgressStatusBarValue() + { + lock (this.progressStatusBar) + { + return this.progressStatusBar.Value; + } + } + + public void SetProgressStatusBar(double percent) + { + lock (this.progressStatusBar) + { + this.progressStatusBar.Value = (int)percent; + bool visible = (percent != 100); + this.progressStatusBar.Visible = visible; + this.progressStatusSeparator.Visible = visible; + } + } + + public PdnStatusBar() + { + InitializeComponent(); + + this.cursorInfoStatusLabel.Image = PdnResources.GetImageResource("Icons.CursorXYIcon.png").Reference; + this.cursorInfoStatusLabel.Text = string.Empty; + + // imageInfo (width,height info) + this.imageInfoStatusLabel.Image = PdnResources.GetImageResource("Icons.ImageSizeIcon.png").Reference; + + // progress + this.progressStatusBar.Visible = false; + this.progressStatusSeparator.Visible = false; + this.progressStatusBar.Height -= 4; + this.progressStatusBar.ProgressBar.Style = ProgressBarStyle.Continuous; + } + + private void InitializeComponent() + { + this.contextStatusLabel = new ToolStripStatusLabel(); + this.progressStatusSeparator = new ToolStripSeparator(); + this.progressStatusBar = new ToolStripProgressBar(); + this.imageInfoStatusLabel = new ToolStripStatusLabel(); + this.cursorInfoStatusLabel = new ToolStripStatusLabel(); + SuspendLayout(); + // + // contextStatusLabel + // + this.contextStatusLabel.Name = "contextStatusLabel"; + this.contextStatusLabel.Width = UI.ScaleWidth(436); + this.contextStatusLabel.Spring = true; + this.contextStatusLabel.TextAlign = ContentAlignment.MiddleLeft; + this.contextStatusLabel.ImageAlign = ContentAlignment.MiddleLeft; + // + // progressStatusBar + // + this.progressStatusBar.Name = "progressStatusBar"; + this.progressStatusBar.Width = 130; + this.progressStatusBar.AutoSize = false; + // + // imageInfoStatusLabel + // + this.imageInfoStatusLabel.Name = "imageInfoStatusLabel"; + this.imageInfoStatusLabel.Width = UI.ScaleWidth(130); + this.imageInfoStatusLabel.TextAlign = ContentAlignment.MiddleLeft; + this.imageInfoStatusLabel.ImageAlign = ContentAlignment.MiddleLeft; + this.imageInfoStatusLabel.AutoSize = false; + // + // cursorInfoStatusLabel + // + this.cursorInfoStatusLabel.Name = "cursorInfoStatusLabel"; + this.cursorInfoStatusLabel.Width = UI.ScaleWidth(130); + this.cursorInfoStatusLabel.TextAlign = ContentAlignment.MiddleLeft; + this.cursorInfoStatusLabel.ImageAlign = ContentAlignment.MiddleLeft; + this.cursorInfoStatusLabel.AutoSize = false; + // + // PdnStatusBar + // + this.Name = "PdnStatusBar"; + this.Items.Add(this.contextStatusLabel); + this.Items.Add(this.progressStatusSeparator); + this.Items.Add(this.progressStatusBar); + this.Items.Add(new ToolStripSeparator()); + this.Items.Add(this.imageInfoStatusLabel); + this.Items.Add(new ToolStripSeparator()); + this.Items.Add(this.cursorInfoStatusLabel); + ResumeLayout(false); + } + } +} diff --git a/src/PdnToolBar.cs b/src/PdnToolBar.cs new file mode 100644 index 0000000..0e1158e --- /dev/null +++ b/src/PdnToolBar.cs @@ -0,0 +1,668 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Menus; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class PdnToolBar + : Control, + IPaintBackground + { + private const ToolStripGripStyle toolStripsGripStyle = ToolStripGripStyle.Hidden; + private DateTime ignoreShowDocumentListUntil = DateTime.MinValue; + + private AppWorkspace appWorkspace; + private PdnMainMenu mainMenu; + private ToolStripPanel toolStripPanel; + private CommonActionsStrip commonActionsStrip; + private ViewConfigStrip viewConfigStrip; + private ToolChooserStrip toolChooserStrip; + private ToolConfigStrip toolConfigStrip; + private OurDocumentStrip documentStrip; + private ArrowButton documentListButton; + private ImageListMenu imageListMenu; + private OurToolStripRenderer otsr = new OurToolStripRenderer(); + + private class OurToolStripRenderer : + ToolStripProfessionalRenderer + { + public OurToolStripRenderer() + { + RoundedEdges = false; + } + + private void PaintBackground(Graphics g, Control control, Rectangle clipRect) + { + Control parent = control; + IPaintBackground asIpb = null; + + while (true) + { + parent = parent.Parent; + + if (parent == null) + { + break; + } + + asIpb = parent as IPaintBackground; + + if (asIpb != null) + { + break; + } + } + + if (asIpb != null) + { + Rectangle screenRect = control.RectangleToScreen(clipRect); + Rectangle parentRect = parent.RectangleToClient(screenRect); + + int dx = parentRect.Left - clipRect.Left; + int dy = parentRect.Top - clipRect.Top; + + g.TranslateTransform(-dx, -dy, MatrixOrder.Append); + asIpb.PaintBackground(g, parentRect); + g.TranslateTransform(dx, dy, MatrixOrder.Append); + } + } + + protected override void OnRenderToolStripPanelBackground(ToolStripPanelRenderEventArgs e) + { + PaintBackground(e.Graphics, e.ToolStripPanel, new Rectangle(new Point(0, 0), e.ToolStripPanel.Size)); + e.Handled = true; + } + + protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) + { + if (e.ToolStrip.GetType() != typeof(ToolStrip) && + e.ToolStrip.GetType() != typeof(ToolStripEx) && + e.ToolStrip.GetType() != typeof(PdnMainMenu)) + { + base.OnRenderToolStripBackground(e); + } + else + { + PaintBackground(e.Graphics, e.ToolStrip, e.AffectedBounds); + } + } + } + + private class OurDocumentStrip : + DocumentStrip, + IPaintBackground + { + protected override void DrawItemBackground(Graphics g, Item item, Rectangle itemRect) + { + PaintBackground(g, itemRect); + } + + public void PaintBackground(Graphics g, Rectangle clipRect) + { + IPaintBackground asIpb = this.Parent as IPaintBackground; + + if (asIpb != null) + { + Rectangle newClipRect = new Rectangle( + clipRect.Left + Left, clipRect.Top + Top, + clipRect.Width, clipRect.Height); + + g.TranslateTransform(-Left, -Top, MatrixOrder.Append); + asIpb.PaintBackground(g, newClipRect); + g.TranslateTransform(Left, Top, MatrixOrder.Append); + } + } + } + + public void PaintBackground(Graphics g, Rectangle clipRect) + { + if (clipRect.Width > 0 && clipRect.Height > 0) + { + Color backColor = ProfessionalColors.MenuStripGradientEnd; + + using (SolidBrush brush = new SolidBrush(backColor)) + { + g.FillRectangle(brush, clipRect); + } + } + } + + public AppWorkspace AppWorkspace + { + get + { + return this.appWorkspace; + } + + set + { + this.appWorkspace = value; + this.mainMenu.AppWorkspace = value; + } + } + + public PdnMainMenu MainMenu + { + get + { + return this.mainMenu; + } + } + + public ToolStripPanel ToolStripContainer + { + get + { + return this.toolStripPanel; + } + } + + public CommonActionsStrip CommonActionsStrip + { + get + { + return this.commonActionsStrip; + } + } + + public ViewConfigStrip ViewConfigStrip + { + get + { + return this.viewConfigStrip; + } + } + + public ToolChooserStrip ToolChooserStrip + { + get + { + return this.toolChooserStrip; + } + } + + public ToolConfigStrip ToolConfigStrip + { + get + { + return this.toolConfigStrip; + } + } + + public DocumentStrip DocumentStrip + { + get + { + return this.documentStrip; + } + } + + public PdnToolBar() + { + SuspendLayout(); + InitializeComponent(); + + this.toolChooserStrip.SetTools(DocumentWorkspace.ToolInfos); + + this.otsr = new OurToolStripRenderer(); + this.commonActionsStrip.Renderer = otsr; + this.viewConfigStrip.Renderer = otsr; + this.toolStripPanel.Renderer = otsr; + this.toolChooserStrip.Renderer = otsr; + this.toolConfigStrip.Renderer = otsr; + this.mainMenu.Renderer = otsr; + + ResumeLayout(true); + } + + private bool computedMaxRowHeight = false; + private int maxRowHeight = -1; + + protected override void OnLayout(LayoutEventArgs e) + { + bool plentyWidthBefore = + (this.mainMenu.Width >= this.mainMenu.PreferredSize.Width) && + (this.commonActionsStrip.Width >= this.commonActionsStrip.PreferredSize.Width) && + (this.viewConfigStrip.Width >= this.viewConfigStrip.PreferredSize.Width) && + (this.toolChooserStrip.Width >= this.toolChooserStrip.PreferredSize.Width) && + (this.toolConfigStrip.Width >= this.toolConfigStrip.PreferredSize.Width); + + if (!plentyWidthBefore) + { + UI.SuspendControlPainting(this); + } + else + { + // if we don't do this then we get some terrible flickering of the right scroll arrow + UI.SuspendControlPainting(this.documentStrip); + } + + this.mainMenu.Location = new Point(0, 0); + this.mainMenu.Height = this.mainMenu.PreferredSize.Height; + this.toolStripPanel.Location = new Point(0, this.mainMenu.Bottom); + + this.toolStripPanel.RowMargin = new Padding(0); + this.mainMenu.Padding = new Padding(0, this.mainMenu.Padding.Top, 0, this.mainMenu.Padding.Bottom); + + this.commonActionsStrip.Width = this.commonActionsStrip.PreferredSize.Width; + this.viewConfigStrip.Width = this.viewConfigStrip.PreferredSize.Width; + this.toolChooserStrip.Width = this.toolChooserStrip.PreferredSize.Width; + this.toolConfigStrip.Width = this.toolConfigStrip.PreferredSize.Width; + + if (!this.computedMaxRowHeight) + { + ToolBarConfigItems oldTbci = this.toolConfigStrip.ToolBarConfigItems; + this.toolConfigStrip.ToolBarConfigItems = ToolBarConfigItems.All; + this.toolConfigStrip.PerformLayout(); + + this.maxRowHeight = + Math.Max(this.commonActionsStrip.PreferredSize.Height, + Math.Max(this.viewConfigStrip.PreferredSize.Height, + Math.Max(this.toolChooserStrip.PreferredSize.Height, this.toolConfigStrip.PreferredSize.Height))); + + this.toolConfigStrip.ToolBarConfigItems = oldTbci; + this.toolConfigStrip.PerformLayout(); + + this.computedMaxRowHeight = true; + } + + this.commonActionsStrip.Height = this.maxRowHeight; + this.viewConfigStrip.Height = this.maxRowHeight; + this.toolChooserStrip.Height = this.maxRowHeight; + this.toolConfigStrip.Height = this.maxRowHeight; + + this.commonActionsStrip.Location = new Point(0, 0); + this.viewConfigStrip.Location = new Point(this.commonActionsStrip.Right, this.commonActionsStrip.Top); + this.toolChooserStrip.Location = new Point(0, this.viewConfigStrip.Bottom); + this.toolConfigStrip.Location = new Point(this.toolChooserStrip.Right, this.toolChooserStrip.Top); + + this.toolStripPanel.Height = + Math.Max(this.commonActionsStrip.Bottom, + Math.Max(this.viewConfigStrip.Bottom, + Math.Max(this.toolChooserStrip.Bottom, + this.toolConfigStrip.Visible ? this.toolConfigStrip.Bottom : this.toolChooserStrip.Bottom))); + + // Compute how wide the toolStripContainer would like to be + int widthRow1 = + this.commonActionsStrip.Left + this.commonActionsStrip.PreferredSize.Width + this.commonActionsStrip.Margin.Horizontal + + this.viewConfigStrip.PreferredSize.Width + this.viewConfigStrip.Margin.Horizontal; + + int widthRow2 = + this.toolChooserStrip.Left + this.toolChooserStrip.PreferredSize.Width + this.toolChooserStrip.Margin.Horizontal + + this.toolConfigStrip.PreferredSize.Width + this.toolConfigStrip.Margin.Horizontal; + + int preferredMinTscWidth = Math.Max(widthRow1, widthRow2); + + // Throw in the documentListButton if necessary + bool showDlb = this.documentStrip.DocumentCount > 0; + + this.documentListButton.Visible = showDlb; + this.documentListButton.Enabled = showDlb; + + if (showDlb) + { + int documentListButtonWidth = UI.ScaleWidth(15); + this.documentListButton.Width = documentListButtonWidth; + } + else + { + this.documentListButton.Width = 0; + } + + // Figure out the DocumentStrip's size -- we actually make two passes at setting its Width + // so that we can toss in the documentListButton if necessary + if (this.documentStrip.DocumentCount == 0) + { + this.documentStrip.Width = 0; + } + else + { + this.documentStrip.Width = Math.Max( + this.documentStrip.PreferredMinClientWidth, + Math.Min(this.documentStrip.PreferredSize.Width, + ClientSize.Width - preferredMinTscWidth - this.documentListButton.Width)); + } + + this.documentStrip.Location = new Point(ClientSize.Width - this.documentStrip.Width, 0); + this.documentListButton.Location = new Point(this.documentStrip.Left - this.documentListButton.Width, 0); + + this.imageListMenu.Location = new Point(this.documentListButton.Left, this.documentListButton.Bottom - 1); + this.imageListMenu.Width = this.documentListButton.Width; + this.imageListMenu.Height = 0; + + this.documentListButton.Visible = showDlb; + this.documentListButton.Enabled = showDlb; + + // Finish setting up widths and heights + int oldDsHeight = this.documentStrip.Height; + this.documentStrip.Height = this.toolStripPanel.Bottom; + this.documentListButton.Height = this.documentStrip.Height; + + int tsWidth = ClientSize.Width - (this.documentStrip.Width + this.documentListButton.Width); + this.mainMenu.Width = tsWidth; + this.toolStripPanel.Width = tsWidth; + + this.Height = this.toolStripPanel.Bottom; + + // Now get stuff to paint right + this.documentStrip.PerformLayout(); + + if (!plentyWidthBefore) + { + UI.ResumeControlPainting(this); + Invalidate(true); + } + else + { + UI.ResumeControlPainting(this.documentStrip); + this.documentStrip.Invalidate(true); + } + + if (this.documentStrip.Width == 0) + { + this.mainMenu.Invalidate(); + } + + if (oldDsHeight != this.documentStrip.Height) + { + this.documentStrip.RefreshAllThumbnails(); + } + + base.OnLayout(e); + } + + protected override void OnResize(EventArgs e) + { + PerformLayout(); + base.OnResize(e); + } + + private void InitializeComponent() + { + this.SuspendLayout(); + this.mainMenu = new PdnMainMenu(); + this.toolStripPanel = new ToolStripPanel(); + this.commonActionsStrip = new CommonActionsStrip(); + this.viewConfigStrip = new ViewConfigStrip(); + this.toolChooserStrip = new ToolChooserStrip(); + this.toolConfigStrip = new ToolConfigStrip(); + this.documentStrip = new OurDocumentStrip(); + this.documentListButton = new ArrowButton(); + this.imageListMenu = new ImageListMenu(); + this.toolStripPanel.BeginInit(); + this.toolStripPanel.SuspendLayout(); + // + // mainMenu + // + this.mainMenu.Name = "mainMenu"; + // + // toolStripContainer + // + this.toolStripPanel.AutoSize = true; + this.toolStripPanel.Name = "toolStripPanel"; + this.toolStripPanel.TabIndex = 0; + this.toolStripPanel.TabStop = false; + this.toolStripPanel.Join(this.viewConfigStrip); + this.toolStripPanel.Join(this.commonActionsStrip); + this.toolStripPanel.Join(this.toolConfigStrip); + this.toolStripPanel.Join(this.toolChooserStrip); + // + // commonActionsStrip + // + this.commonActionsStrip.Name = "commonActionsStrip"; + this.commonActionsStrip.AutoSize = false; + this.commonActionsStrip.TabIndex = 0; + this.commonActionsStrip.Dock = DockStyle.None; + this.commonActionsStrip.GripStyle = toolStripsGripStyle; + // + // viewConfigStrip + // + this.viewConfigStrip.Name = "viewConfigStrip"; + this.viewConfigStrip.AutoSize = false; + this.viewConfigStrip.ZoomBasis = PaintDotNet.ZoomBasis.FitToWindow; + this.viewConfigStrip.TabStop = false; + this.viewConfigStrip.DrawGrid = false; + this.viewConfigStrip.TabIndex = 1; + this.viewConfigStrip.Dock = DockStyle.None; + this.viewConfigStrip.GripStyle = toolStripsGripStyle; + // + // toolChooserStrip + // + this.toolChooserStrip.Name = "toolChooserStrip"; + this.toolChooserStrip.AutoSize = false; + this.toolChooserStrip.TabIndex = 2; + this.toolChooserStrip.Dock = DockStyle.None; + this.toolChooserStrip.GripStyle = toolStripsGripStyle; + this.toolChooserStrip.ChooseDefaultsClicked += new EventHandler(ToolChooserStrip_ChooseDefaultsClicked); + // + // toolConfigStrip + // + this.toolConfigStrip.Name = "drawConfigStrip"; + this.toolConfigStrip.AutoSize = false; + this.toolConfigStrip.ShapeDrawType = PaintDotNet.ShapeDrawType.Outline; + this.toolConfigStrip.TabIndex = 3; + this.toolConfigStrip.Dock = DockStyle.None; + this.toolConfigStrip.GripStyle = toolStripsGripStyle; + this.toolConfigStrip.Layout += + delegate(object sender, LayoutEventArgs e) + { + PerformLayout(); + }; + this.toolConfigStrip.SelectionDrawModeInfoChanged += + delegate(object sender, EventArgs e) + { + BeginInvoke(new Procedure(PerformLayout)); + }; + // + // documentStrip + // + this.documentStrip.AutoSize = false; + this.documentStrip.Name = "documentStrip"; + this.documentStrip.TabIndex = 5; + this.documentStrip.ShowScrollButtons = true; + this.documentStrip.DocumentListChanged += new EventHandler(DocumentStrip_DocumentListChanged); + this.documentStrip.DocumentClicked += DocumentStrip_DocumentClicked; + this.documentStrip.ManagedFocus = true; + // + // documentListButton + // + this.documentListButton.Name = "documentListButton"; + this.documentListButton.ArrowDirection = ArrowDirection.Down; + this.documentListButton.ReverseArrowColors = true; + this.documentListButton.Click += new EventHandler(DocumentListButton_Click); + // + // imageListMenu + // + this.imageListMenu.Name = "imageListMenu"; + this.imageListMenu.Closed += new EventHandler(ImageListMenu_Closed); + this.imageListMenu.ItemClicked += ImageListMenu_ItemClicked; + // + // PdnToolBar + // + this.Controls.Add(this.documentListButton); + this.Controls.Add(this.documentStrip); + this.Controls.Add(this.toolStripPanel); + this.Controls.Add(this.mainMenu); + this.Controls.Add(this.imageListMenu); + this.toolStripPanel.ResumeLayout(false); + this.toolStripPanel.EndInit(); + this.ResumeLayout(false); + } + + private void ToolChooserStrip_ChooseDefaultsClicked(object sender, EventArgs e) + { + PdnBaseForm.UpdateAllForms(); + + WaitCursorChanger wcc = new WaitCursorChanger(this); + + using (ChooseToolDefaultsDialog dialog = new ChooseToolDefaultsDialog()) + { + EventHandler shownDelegate = null; + + shownDelegate = + delegate(object sender2, EventArgs e2) + { + wcc.Dispose(); + wcc = null; + dialog.Shown -= shownDelegate; + }; + + dialog.Shown += shownDelegate; + dialog.SetToolBarSettings(this.appWorkspace.GlobalToolTypeChoice, this.appWorkspace.AppEnvironment); + + AppEnvironment defaultAppEnv = AppEnvironment.GetDefaultAppEnvironment(); + + try + { + dialog.LoadUIFromAppEnvironment(defaultAppEnv); + } + + catch (Exception) + { + defaultAppEnv = new AppEnvironment(); + defaultAppEnv.SetToDefaults(); + dialog.LoadUIFromAppEnvironment(defaultAppEnv); + } + + dialog.ToolType = this.appWorkspace.DefaultToolType; + + DialogResult dr = dialog.ShowDialog(this); + + if (dr != DialogResult.Cancel) + { + AppEnvironment newDefaultAppEnv = dialog.CreateAppEnvironmentFromUI(); + newDefaultAppEnv.SaveAsDefaultAppEnvironment(); + this.appWorkspace.AppEnvironment.LoadFrom(newDefaultAppEnv); + this.appWorkspace.DefaultToolType = dialog.ToolType; + this.appWorkspace.GlobalToolTypeChoice = dialog.ToolType; + } + } + + if (wcc != null) + { + wcc.Dispose(); + wcc = null; + } + } + + private void DocumentListButton_Click(object sender, EventArgs e) + { + if (this.imageListMenu.IsImageListVisible) + { + HideDocumentList(); + } + else + { + ShowDocumentList(); + } + } + + public void HideDocumentList() + { + this.imageListMenu.HideImageList(); + } + + private void ImageListMenu_Closed(object sender, EventArgs e) + { + this.documentListButton.ForcedPushedAppearance = false; + + // We set this up because otherwise if the user clicks on the documentListButton, + // then first the documentListMenu closes, and then the documentClickButton's Click + // event fires. The behavior we want is to hide the menu when this Click occurs, + // but since the menu is already hidden we have no way of knowing that we should + // not show the menu. + this.ignoreShowDocumentListUntil = DateTime.Now + new TimeSpan(0, 0, 0, 0, 250); + } + + private void ImageListMenu_ItemClicked(object sender, EventArgs e) + { + DocumentWorkspace dw = (DocumentWorkspace)e.Data.Tag; + + if (!dw.IsDisposed) + { + this.documentStrip.SelectedDocument = dw; + } + } + + public void ShowDocumentList() + { + if (this.documentStrip.DocumentCount < 1) + { + return; + } + + if (DateTime.Now < this.ignoreShowDocumentListUntil) + { + return; + } + + if (this.imageListMenu.IsImageListVisible) + { + return; + } + + DocumentWorkspace[] documents = this.documentStrip.DocumentList; + Image[] thumbnails = this.documentStrip.DocumentThumbnails; + + ImageListMenu.Item[] items = new ImageListMenu.Item[this.documentStrip.DocumentCount]; + + for (int i = 0; i < items.Length; ++i) + { + bool selected = (documents[i] == this.documentStrip.SelectedDocument); + + items[i] = new ImageListMenu.Item( + thumbnails[i], + documents[i].GetFriendlyName(), + selected); + + items[i].Tag = documents[i]; + } + + Cursor.Current = Cursors.Default; + + this.documentListButton.ForcedPushedAppearance = true; + this.imageListMenu.ShowImageList(items); + } + + private void DocumentStrip_DocumentClicked(object sender, EventArgs> e) + { + if (e.Data.Second == DocumentClickAction.Select) + { + PerformLayout(); + } + } + + private void DocumentStrip_DocumentListChanged(object sender, EventArgs e) + { + PerformLayout(); + + if (this.documentStrip.DocumentCount == 0) + { + this.viewConfigStrip.Enabled = false; + this.toolChooserStrip.Enabled = false; + this.toolConfigStrip.Enabled = false; + } + else + { + this.viewConfigStrip.Enabled = true; + this.toolChooserStrip.Enabled = true; + this.toolConfigStrip.Enabled = true; + } + } + } +} diff --git a/src/PdnVersionInfo.cs b/src/PdnVersionInfo.cs new file mode 100644 index 0000000..4bfdd59 --- /dev/null +++ b/src/PdnVersionInfo.cs @@ -0,0 +1,141 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Contains information pertaining to a release of Paint.NET + /// + internal class PdnVersionInfo + { + private Version version; + private string friendlyName; + private int netFxMajorVersion; + private int netFxMinorVersion; + private int netFxServicePack; + private string infoUrl; + private string[] downloadUrls; + private string[] fullDownloadUrls; + private bool isFinal; + + public Version Version + { + get + { + return this.version; + } + } + + public string FriendlyName + { + get + { + return this.friendlyName; + } + } + + public int NetFxMajorVersion + { + get + { + return this.netFxMajorVersion; + } + } + + public int NetFxMinorVersion + { + get + { + return this.netFxMinorVersion; + } + } + + public int NetFxServicePack + { + get + { + return this.netFxServicePack; + } + } + + public string InfoUrl + { + get + { + return this.infoUrl; + } + } + + public string[] DownloadUrls + { + get + { + return (string[])this.downloadUrls.Clone(); + } + } + + public string[] FullDownloadUrls + { + get + { + return (string[])this.fullDownloadUrls.Clone(); + } + } + + public bool IsFinal + { + get + { + return this.isFinal; + } + } + + public string ChooseDownloadUrl(bool full) + { + DateTime now = DateTime.Now; + string[] urls; + + if (full) + { + urls = FullDownloadUrls; + } + else + { + urls = DownloadUrls; + } + + int index = Math.Abs(now.Second % urls.Length); + return urls[index]; + } + + public PdnVersionInfo( + Version version, + string friendlyName, + int netFxMajorVersion, + int netFxMinorVersion, + int netFxServicePack, + string infoUrl, + string[] downloadUrls, + string[] fullDownloadUrls, + bool isFinal) + { + this.version = version; + this.friendlyName = friendlyName; + this.netFxMajorVersion = netFxMajorVersion; + this.netFxMinorVersion = netFxMinorVersion; + this.netFxServicePack = netFxServicePack; + this.infoUrl = infoUrl; + this.downloadUrls = (string[])downloadUrls.Clone(); + this.fullDownloadUrls = (string[])fullDownloadUrls.Clone(); + this.isFinal = isFinal; + } + } +} diff --git a/src/PdnVersionManifest.cs b/src/PdnVersionManifest.cs new file mode 100644 index 0000000..feaac17 --- /dev/null +++ b/src/PdnVersionManifest.cs @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; + +namespace PaintDotNet +{ + internal class PdnVersionManifest + { + private string downloadPageUrl; + private PdnVersionInfo[] versionInfos; + + public string DownloadPageUrl + { + get + { + return this.downloadPageUrl; + } + } + + public PdnVersionInfo[] VersionInfos + { + get + { + return (PdnVersionInfo[])this.versionInfos; + } + } + + private class PdnVersionInfoComparer + : IComparer + { + public int Compare(object x, object y) + { + PdnVersionInfo xpvi = (PdnVersionInfo)x; + PdnVersionInfo ypvi = (PdnVersionInfo)y; + + if (xpvi.Version < ypvi.Version) + { + return -1; + } + else if (xpvi.Version == ypvi.Version) + { + return 0; + } + else // if (xpvi.Version > ypvi.Version) + { + return +1; + } + } + } + + public int GetLatestBetaVersionIndex() + { + PdnVersionInfo[] versions = VersionInfos; + Array.Sort(versions, new PdnVersionInfoComparer()); + + for (int i = versions.Length - 1; i >= 0; --i) + { + if (!versions[i].IsFinal) + { + return i; + } + } + + return -1; + } + + public int GetLatestStableVersionIndex() + { + PdnVersionInfo[] versions = VersionInfos; + Array.Sort(versions, new PdnVersionInfoComparer()); + + for (int i = versions.Length - 1; i >= 0; --i) + { + if (versions[i].IsFinal) + { + return i; + } + } + + return -1; + } + + public PdnVersionManifest(string downloadPageUrl, PdnVersionInfo[] versionInfos) + { + this.downloadPageUrl = downloadPageUrl; + this.versionInfos = (PdnVersionInfo[])versionInfos.Clone(); + } + } +} diff --git a/src/PenInfo.cs b/src/PenInfo.cs new file mode 100644 index 0000000..d781298 --- /dev/null +++ b/src/PenInfo.cs @@ -0,0 +1,280 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + /// + /// Carries information about the subset of Pen configuration details that we support. + /// Does not carry color information. + /// + [Serializable] + internal sealed class PenInfo + : ICloneable, + ISerializable + { + public const DashStyle DefaultDashStyle = DashStyle.Solid; + public const LineCap2 DefaultLineCap = LineCap2.Flat; + public const float DefaultCapScale = 1.0f; + public const float MinCapScale = 1.0f; + public const float MaxCapScale = 5.0f; + + private DashStyle dashStyle; + public DashStyle DashStyle + { + get + { + return this.dashStyle; + } + + set + { + this.dashStyle = value; + } + } + + private float width; + public float Width + { + get + { + return this.width; + } + + set + { + this.width = value; + } + } + + private LineCap2 startCap; + public LineCap2 StartCap + { + get + { + return this.startCap; + } + + set + { + this.startCap = value; + } + } + + private LineCap2 endCap; + public LineCap2 EndCap + { + get + { + return this.endCap; + } + + set + { + this.endCap = value; + } + } + + private float capScale; + private float CapScale + { + get + { + return Utility.Clamp(this.capScale, MinCapScale, MaxCapScale); + } + + set + { + this.capScale = value; + } + } + + public static bool operator==(PenInfo lhs, PenInfo rhs) + { + return ( + lhs.dashStyle == rhs.dashStyle && + lhs.width == rhs.width && + lhs.startCap == rhs.startCap && + lhs.endCap == rhs.endCap && + lhs.capScale == rhs.capScale); + } + + public static bool operator!=(PenInfo lhs, PenInfo rhs) + { + return !(lhs == rhs); + } + + public override bool Equals(object obj) + { + PenInfo rhs = obj as PenInfo; + + if (rhs == null) + { + return false; + } + + return this == rhs; + } + + public override int GetHashCode() + { + return + this.dashStyle.GetHashCode() ^ + this.width.GetHashCode() ^ + this.startCap.GetHashCode() ^ + this.endCap.GetHashCode() ^ + this.capScale.GetHashCode(); + } + + private void LineCapToLineCap2(LineCap2 cap2, out LineCap capResult, out CustomLineCap customCapResult) + { + switch (cap2) + { + case LineCap2.Flat: + capResult = LineCap.Flat; + customCapResult = null; + break; + + case LineCap2.Arrow: + capResult = LineCap.ArrowAnchor; + customCapResult = new AdjustableArrowCap(5.0f * this.capScale, 5.0f * this.capScale, false); + break; + + case LineCap2.ArrowFilled: + capResult = LineCap.ArrowAnchor; + customCapResult = new AdjustableArrowCap(5.0f * this.capScale, 5.0f * this.capScale, true); + break; + + case LineCap2.Rounded: + capResult = LineCap.Round; + customCapResult = null; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + + public Pen CreatePen(BrushInfo brushInfo, Color foreColor, Color backColor) + { + Pen pen; + + if (brushInfo.BrushType == BrushType.None) + { + pen = new Pen(foreColor, width); + } + else + { + pen = new Pen(brushInfo.CreateBrush(foreColor, backColor), width); + } + + LineCap startLineCap; + CustomLineCap startCustomLineCap; + LineCapToLineCap2(this.startCap, out startLineCap, out startCustomLineCap); + + if (startCustomLineCap != null) + { + pen.CustomStartCap = startCustomLineCap; + } + else + { + pen.StartCap = startLineCap; + } + + LineCap endLineCap; + CustomLineCap endCustomLineCap; + LineCapToLineCap2(this.endCap, out endLineCap, out endCustomLineCap); + + if (endCustomLineCap != null) + { + pen.CustomEndCap = endCustomLineCap; + } + else + { + pen.EndCap = endLineCap; + } + + pen.DashStyle = this.dashStyle; + + return pen; + } + + public PenInfo(DashStyle dashStyle, float width, LineCap2 startCap, LineCap2 endCap, float capScale) + { + this.dashStyle = dashStyle; + this.width = width; + this.capScale = capScale; + this.startCap = startCap; + this.endCap = endCap; + } + + private PenInfo(SerializationInfo info, StreamingContext context) + { + this.dashStyle = (DashStyle)info.GetValue("dashStyle", typeof(DashStyle)); + this.width = info.GetSingle("width"); + + // Save the caps as integers because we want to change the "LineCap2" name. + // Just not feeling very creative right now I guess. + try + { + this.startCap = (LineCap2)info.GetInt32("startCap"); + } + + catch (SerializationException) + { + this.startCap = DefaultLineCap; + } + + try + { + this.endCap = (LineCap2)info.GetInt32("endCap"); + } + + catch (SerializationException) + { + this.endCap = DefaultLineCap; + } + + try + { + float loadedCapScale = info.GetSingle("capScale"); + this.capScale = Utility.Clamp(loadedCapScale, MinCapScale, MaxCapScale); + } + + catch (SerializationException) + { + this.capScale = DefaultCapScale; + } + } + + public PenInfo Clone() + { + return new PenInfo(this.dashStyle, this.width, this.startCap, this.endCap, this.capScale); + } + + object ICloneable.Clone() + { + return Clone(); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("dashStyle", this.dashStyle); + info.AddValue("width", this.width); + info.AddValue("startCap", (int)this.startCap); + info.AddValue("endCap", (int)this.endCap); + info.AddValue("capScale", this.capScale); + } + } +} diff --git a/src/ProgressDialog.cs b/src/ProgressDialog.cs new file mode 100644 index 0000000..c2ea076 --- /dev/null +++ b/src/ProgressDialog.cs @@ -0,0 +1,325 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ProgressDialog + : PdnBaseForm + { + private System.Windows.Forms.ProgressBar progressBar; + private System.Windows.Forms.Label percentText; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Label descriptionLabel; + private System.ComponentModel.IContainer components; + private WaitCursorChanger waitCursorChanger; + private bool cancelled; + + private int normalHeight; + private int noButtonHeight; + private bool cancellable = true; + private bool done = false; + + public ProgressDialog() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.Value = 0.0; + + Point bottomPoint = this.PointToScreen(new Point(0, Bottom)); + Point topPoint = this.cancelButton.PointToScreen(new Point(0, 0)); + normalHeight = Height; + noButtonHeight = Height - 32; + + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + } + + public string Description + { + get + { + return descriptionLabel.Text; + } + + set + { + descriptionLabel.Text = value; + } + } + + private bool marquee = false; + public bool MarqueeMode + { + get + { + return this.marquee; + } + + set + { + this.marquee = value; + this.progressBar.Style = this.marquee ? ProgressBarStyle.Marquee : ProgressBarStyle.Blocks; + } + } + + public bool PercentTextVisible + { + get + { + return this.percentText.Visible; + } + + set + { + this.percentText.Visible = value; + } + } + + public bool Cancellable + { + get + { + return cancelButton.Visible; + } + + set + { + if (value) + { + this.Height = normalHeight; + this.Cursor = System.Windows.Forms.Cursors.Default; + } + else + { + this.Height = noButtonHeight; + this.Cursor = System.Windows.Forms.Cursors.WaitCursor; + } + + this.cancelButton.Visible = value; + cancellable = value; + } + } + + public bool Cancelled + { + get + { + return this.cancelled; + } + } + + public double Value + { + get + { + return (double)progressBar.Value; + } + + set + { + int intValue = (int)value; + string textFormat = PdnResources.GetString("ProgressDialog.PercentText.Text.Format"); + string text = string.Format(textFormat, intValue); + + if (text != percentText.Text) + { + percentText.Text = text; + progressBar.Value = Math.Max(progressBar.Minimum, Math.Min(progressBar.Maximum, intValue)); + Update(); + } + } + } + + private void SetValueHigher(object higherValue) + { + double newValue = (double)higherValue; + + if (this.Value <= newValue) + { + this.Value = newValue; + } + } + + public void ExternalFinish() + { + this.done = true; + DialogResult = DialogResult.OK; + Close(); + } + + private int tileCount = 0; + public void RenderedTileHandler(object sender, RenderedTileEventArgs e) + { + lock (this) + { + ++this.tileCount; + double newValue = 100.0 * ((double)(tileCount + 1) / (double)e.TileCount); + + if (newValue > 100.0) + { + newValue = 100.0; + } + + if (this.IsHandleCreated) + { + BeginInvoke(new WaitCallback(SetValueHigher), new object[] { newValue }); + } + } + } + + public void FinishedRenderingHandler(object sender, EventArgs e) + { + if (this.IsHandleCreated) + { + BeginInvoke(new Procedure(ExternalFinish), null); + } + } + + protected override void OnClosing(CancelEventArgs e) + { + base.OnClosing (e); + + if (!cancellable && !done) + { + e.Cancel = true; + } + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.descriptionLabel = new System.Windows.Forms.Label(); + this.percentText = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // progressBar + // + this.progressBar.Location = new System.Drawing.Point(17, 32); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(184, 16); + this.progressBar.Step = 1; + this.progressBar.TabIndex = 0; + // + // descriptionLabel + // + this.descriptionLabel.AutoEllipsis = true; + this.descriptionLabel.Location = new System.Drawing.Point(16, 8); + this.descriptionLabel.Name = "descriptionLabel"; + this.descriptionLabel.Size = new System.Drawing.Size(184, 16); + this.descriptionLabel.TabIndex = 1; + // + // percentText + // + this.percentText.Location = new System.Drawing.Point(59, 56); + this.percentText.Name = "percentText"; + this.percentText.Size = new System.Drawing.Size(100, 16); + this.percentText.TabIndex = 2; + this.percentText.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // cancelButton + // + this.cancelButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom; + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(72, 80); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 3; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // ProgressDialog + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(218, 109); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.percentText); + this.Controls.Add(this.descriptionLabel); + this.Controls.Add(this.progressBar); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ProgressDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Controls.SetChildIndex(this.progressBar, 0); + this.Controls.SetChildIndex(this.descriptionLabel, 0); + this.Controls.SetChildIndex(this.percentText, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.ResumeLayout(false); + } + #endregion + + public event EventHandler CancelClick; + protected virtual void OnCancelClick() + { + if (CancelClick != null) + { + CancelClick(this, EventArgs.Empty); + } + } + + private void CancelButton_Click(object sender, System.EventArgs e) + { + this.cancelled = true; + OnCancelClick(); + DialogResult = DialogResult.Cancel; + Close(); + } + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + this.waitCursorChanger = new WaitCursorChanger(this.Owner); + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + if (this.waitCursorChanger != null) + { + this.waitCursorChanger.Dispose(); + this.waitCursorChanger = null; + } + } + } +} diff --git a/src/ProgressDialog.resx b/src/ProgressDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/PushNullToolMode.cs b/src/PushNullToolMode.cs new file mode 100644 index 0000000..86ccf57 --- /dev/null +++ b/src/PushNullToolMode.cs @@ -0,0 +1,50 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + internal sealed class PushNullToolMode + : IDisposable + { + private DocumentWorkspace documentWorkspace; + + public PushNullToolMode(DocumentWorkspace documentWorkspace) + { + this.documentWorkspace = documentWorkspace; + this.documentWorkspace.PushNullTool(); + } + + ~PushNullToolMode() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + if (this.documentWorkspace != null) + { + this.documentWorkspace.PopNullTool(); + this.documentWorkspace = null; + } + } + } + } +} diff --git a/src/ResizeDialog.cs b/src/ResizeDialog.cs new file mode 100644 index 0000000..be84ecd --- /dev/null +++ b/src/ResizeDialog.cs @@ -0,0 +1,1624 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; +using System.Diagnostics; + +namespace PaintDotNet +{ + internal class ResizeDialog + : PdnBaseForm + { + private sealed class ResizeConstrainer + { + private Size originalPixelSize; + private double newWidth; + private double newHeight; + private MeasurementUnit units; + private double resolution; + private bool constrainToAspect; + + private double OriginalAspect + { + get + { + return (double)originalPixelSize.Width / (double)originalPixelSize.Height; + } + } + + public Size OriginalPixelSize + { + get + { + return this.originalPixelSize; + } + } + + public event EventHandler NewWidthChanged; + private void OnNewWidthChanged() + { + if (NewWidthChanged != null) + { + NewWidthChanged(this, EventArgs.Empty); + } + } + + public double NewPixelWidth + { + get + { + if (this.Units == MeasurementUnit.Pixel) + { + return this.newWidth; + } + else + { + return this.newWidth * this.resolution; + } + } + + set + { + if (this.Units == MeasurementUnit.Pixel) + { + this.NewWidth = value; + } + else + { + this.NewWidth = value / this.resolution; + } + } + } + + public double NewWidth + { + get + { + return this.newWidth; + } + + set + { + if (this.newWidth != value) + { + this.newWidth = value; + OnNewWidthChanged(); + + if (this.constrainToAspect) + { + double newNewHeight = value / OriginalAspect; + + if (this.newHeight != newNewHeight) + { + this.newHeight = newNewHeight; + OnNewHeightChanged(); + } + } + } + } + } + + public event EventHandler NewHeightChanged; + private void OnNewHeightChanged() + { + if (NewHeightChanged != null) + { + NewHeightChanged(this, EventArgs.Empty); + } + } + + public double NewPixelHeight + { + get + { + if (this.Units == MeasurementUnit.Pixel) + { + return this.newHeight; + } + else + { + return this.newHeight * this.resolution; + } + } + + set + { + if (this.Units == MeasurementUnit.Pixel) + { + this.NewHeight = value; + } + else + { + this.NewHeight = value / this.resolution; + } + } + } + + public double NewHeight + { + get + { + return this.newHeight; + } + + set + { + if (this.newHeight != value) + { + this.newHeight = value; + OnNewHeightChanged(); + + if (this.constrainToAspect) + { + double newNewWidth = value * OriginalAspect; + + if (this.newWidth != newNewWidth) + { + this.newWidth = newNewWidth; + OnNewWidthChanged(); + } + } + } + } + } + + public event EventHandler UnitsChanged; + private void OnUnitsChanged() + { + if (UnitsChanged != null) + { + UnitsChanged(this, EventArgs.Empty); + } + } + + public MeasurementUnit Units + { + get + { + return this.units; + } + + set + { + if (this.units != value) + { + switch (value) + { + default: + throw new InvalidEnumArgumentException("value is not a valid member of the MeasurementUnit enumeration"); + + // Inches or Centimers -> Pixels + case MeasurementUnit.Pixel: + this.newWidth *= this.resolution; + this.newHeight *= this.resolution; + this.units = value; + + OnUnitsChanged(); + OnNewWidthChanged(); + OnNewHeightChanged(); + break; + + case MeasurementUnit.Inch: + { + switch (this.units) + { + default: + throw new InvalidEnumArgumentException("this.units is not a valid member of the MeasurementUnit enumeration"); + + // Centimeters -> Inches + case MeasurementUnit.Centimeter: + this.newWidth = Document.CentimetersToInches(this.newWidth); + this.newHeight = Document.CentimetersToInches(this.newHeight); + this.units = value; + this.resolution = Document.InchesToCentimeters(this.resolution); + + OnUnitsChanged(); + OnResolutionChanged(); + OnNewWidthChanged(); + OnNewHeightChanged(); + break; + } + break; + } + + case MeasurementUnit.Centimeter: + { + switch (this.units) + { + default: + throw new InvalidEnumArgumentException("this.units is not a valid member of the MeasurementUnit enumeration"); + + // Inches -> Centimeters + case MeasurementUnit.Inch: + this.newWidth = Document.InchesToCentimeters(this.newWidth); + this.newHeight = Document.InchesToCentimeters(this.newHeight); + this.units = value; + this.resolution = Document.CentimetersToInches(this.resolution); + + OnUnitsChanged(); + OnResolutionChanged(); + OnNewWidthChanged(); + OnNewHeightChanged(); + break; + } + break; + } + } + } + } + } + + public event EventHandler ResolutionChanged; + private void OnResolutionChanged() + { + if (ResolutionChanged != null) + { + ResolutionChanged(this, EventArgs.Empty); + } + } + + public const double MinResolution = 0.01; + + public double Resolution + { + get + { + return this.resolution; + } + + set + { + if (value < MinResolution) + { + throw new ArgumentOutOfRangeException("value", value, "value must be >= 0.01"); + } + + if (this.resolution != value) + { + if (this.Units != MeasurementUnit.Pixel) + { + this.newWidth = ((double)this.newWidth * this.resolution) / value; + this.newHeight = ((double)this.newHeight * this.resolution) / value; + } + + this.resolution = value; + OnResolutionChanged(); + + if (this.Units != MeasurementUnit.Pixel) + { + OnNewWidthChanged(); + OnNewHeightChanged(); + } + } + } + } + + public event EventHandler ConstrainToAspectChanged; + private void OnConstrainToAspectChanged() + { + if (ConstrainToAspectChanged != null) + { + ConstrainToAspectChanged(this, EventArgs.Empty); + } + } + + public bool ConstrainToAspect + { + get + { + return this.constrainToAspect; + } + + set + { + if (this.constrainToAspect != value) + { + if (value) + { + double newNewHeight = this.newWidth / this.OriginalAspect; + + if (this.newHeight != newNewHeight) + { + this.newHeight = newNewHeight; + OnNewHeightChanged(); + } + } + + this.constrainToAspect = value; + this.OnConstrainToAspectChanged(); + } + } + } + + public void SetByPercent(double scale) + { + bool oldConstrain = this.constrainToAspect; + this.constrainToAspect = false; + this.NewPixelWidth = (double)this.OriginalPixelSize.Width * scale; + this.NewPixelHeight = (double)this.OriginalPixelSize.Height * scale; + this.constrainToAspect = true; + } + + public ResizeConstrainer(Size originalPixelSize) + { + this.constrainToAspect = false; + this.originalPixelSize = originalPixelSize; + this.units = Document.DefaultDpuUnit; + this.resolution = Document.GetDefaultDpu(this.units); + this.newWidth = (double)this.originalPixelSize.Width / this.resolution; + this.newHeight = (double)this.originalPixelSize.Height / this.resolution; + } + } + + protected System.Windows.Forms.CheckBox constrainCheckBox; + protected System.Windows.Forms.Button okButton; + protected System.Windows.Forms.Button cancelButton; + + private EventHandler upDownValueChangedDelegate; + + private int layers; + + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + protected System.Windows.Forms.Label percentSignLabel; + protected System.Windows.Forms.NumericUpDown percentUpDown; + protected System.Windows.Forms.RadioButton absoluteRB; + protected System.Windows.Forms.RadioButton percentRB; + protected System.Windows.Forms.Label asteriskTextLabel; + protected System.Windows.Forms.Label asteriskLabel; + protected PaintDotNet.HeaderLabel resizedImageHeader; + protected System.Windows.Forms.Label resolutionLabel; + protected PaintDotNet.UnitsComboBox unitsComboBox2; + protected PaintDotNet.UnitsComboBox unitsComboBox1; + protected System.Windows.Forms.NumericUpDown resolutionUpDown; + + private ResizeConstrainer constrainer; + protected System.Windows.Forms.Label newWidthLabel1; + protected System.Windows.Forms.Label newHeightLabel1; + protected System.Windows.Forms.Label pixelsLabel1; + protected System.Windows.Forms.Label newWidthLabel2; + protected System.Windows.Forms.Label newHeightLabel2; + protected System.Windows.Forms.Label pixelsLabel2; + protected System.Windows.Forms.Label unitsLabel1; + protected System.Windows.Forms.NumericUpDown pixelWidthUpDown; + protected System.Windows.Forms.NumericUpDown pixelHeightUpDown; + protected System.Windows.Forms.NumericUpDown printWidthUpDown; + protected System.Windows.Forms.NumericUpDown printHeightUpDown; + protected PaintDotNet.HeaderLabel pixelSizeHeader; + protected PaintDotNet.HeaderLabel printSizeHeader; + protected System.Windows.Forms.Label resamplingLabel; + protected System.Windows.Forms.ComboBox resamplingAlgorithmComboBox; + + /// + /// Gets or sets the image width, in units of pixels. + /// + public int ImageWidth + { + get + { + double doubleVal; + + if (!Utility.GetUpDownValueFromText(this.pixelWidthUpDown, out doubleVal)) + { + doubleVal = Math.Round(constrainer.NewPixelWidth); + } + + int intVal = (int)Utility.Clamp(doubleVal, (double)int.MinValue, (double)int.MaxValue); + + return intVal; + } + + set + { + this.constrainer.NewPixelWidth = value; + } + } + + /// + /// Gets or sets the new image height, in units of pixels. + /// + public int ImageHeight + { + get + { + double doubleVal; + + if (!Utility.GetUpDownValueFromText(this.pixelHeightUpDown, out doubleVal)) + { + doubleVal = Math.Round(constrainer.NewPixelHeight); + } + + int intVal = (int)Utility.Clamp(doubleVal, (double)int.MinValue, (double)int.MaxValue); + + return intVal; + } + + set + { + this.constrainer.NewPixelHeight = value; + } + } + + public MeasurementUnit Units + { + get + { + return this.constrainer.Units; + } + + set + { + this.constrainer.Units = value; + } + } + + public double Resolution + { + get + { + return this.constrainer.Resolution; + } + + set + { + this.constrainer.Resolution = Math.Max(ResizeConstrainer.MinResolution, value); + } + } + + private double originalDpu = Document.GetDefaultDpu(Document.DefaultDpuUnit); + private MeasurementUnit originalDpuUnit = Document.DefaultDpuUnit; + + /// + /// Gets or sets the original image width, in units of pixels. + /// + /// + /// Setting this property will reset the Resolution and Units properties. + /// + public Size OriginalSize + { + get + { + return this.constrainer.OriginalPixelSize; + } + + set + { + this.constrainer = new ResizeConstrainer(value); + SetupConstrainerEvents(); + UpdateSizeText(); + } + } + + public double OriginalDpu + { + get + { + return this.originalDpu; + } + + set + { + this.originalDpu = value; + UpdateSizeText(); + } + } + + public MeasurementUnit OriginalDpuUnit + { + get + { + return this.originalDpuUnit; + } + + set + { + this.originalDpuUnit = value; + UpdateSizeText(); + } + } + + /// + /// Gets the resampling algorithm chosen by the user, or sets the resampling algorithm to populate the UI with. + /// + public ResamplingAlgorithm ResamplingAlgorithm + { + get + { + return ((ResampleMethod)this.resamplingAlgorithmComboBox.SelectedItem).method; + } + + set + { + this.resamplingAlgorithmComboBox.SelectedItem = new ResampleMethod(value); + PopulateAsteriskLabels(); + } + } + + public bool ConstrainToAspect + { + get + { + return this.constrainer.ConstrainToAspect; + } + + set + { + this.constrainer.ConstrainToAspect = value; + } + } + + /// + /// Gets or sets the number of layers in the image. + /// + /// + /// This is used to compute the new byte size of the image that is shown to the user. + /// + public int LayerCount + { + get + { + return layers; + } + + set + { + layers = value; + UpdateSizeText(); + } + } + + private void UpdateSizeText() + { + long bytes = unchecked((long)layers * (long)ColorBgra.SizeOf * (long)constrainer.NewPixelWidth * (long)constrainer.NewPixelHeight); + string bytesText = Utility.SizeStringFromBytes(bytes); + string textFormat = PdnResources.GetString("ResizeDialog.ResizedImageHeader.Text.Format"); + this.resizedImageHeader.Text = string.Format(textFormat, bytesText); + } + + private sealed class ResampleMethod + { + public ResamplingAlgorithm method; + + public override string ToString() + { + switch (method) + { + case ResamplingAlgorithm.NearestNeighbor: + return PdnResources.GetString("ResizeDialog.ResampleMethod.NearestNeighbor"); + + case ResamplingAlgorithm.Bilinear: + return PdnResources.GetString("ResizeDialog.ResampleMethod.Bilinear"); + + case ResamplingAlgorithm.Bicubic: + return PdnResources.GetString("ResizeDialog.ResampleMethod.Bicubic"); + + case ResamplingAlgorithm.SuperSampling: + return PdnResources.GetString("ResizeDialog.ResampleMethod.SuperSampling"); + + default: + return method.ToString(); + } + } + + public override bool Equals(object obj) + { + if (obj is ResampleMethod && ((ResampleMethod)obj).method == this.method) + { + return true; + } + else + { + return false; + } + } + + public override int GetHashCode() + { + return this.method.GetHashCode(); + } + + public ResampleMethod(ResamplingAlgorithm method) + { + this.method = method; + } + } + + public ResizeDialog() + { + this.SuspendLayout(); // ResumeLayout() called in OnLoad(). This helps with layout w.r.t. visual inheritance (CanvasSizeDialog and NewFileDialog) + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.Text = PdnResources.GetString("ResizeDialog.Text"); + this.asteriskLabel.Text = PdnResources.GetString("ResizeDialog.AsteriskLabel.Text"); + this.percentSignLabel.Text = PdnResources.GetString("ResizeDialog.PercentSignLabel.Text"); + this.pixelSizeHeader.Text = PdnResources.GetString("ResizeDialog.PixelSizeHeader.Text"); + this.printSizeHeader.Text = PdnResources.GetString("ResizeDialog.PrintSizeHeader.Text"); + this.pixelsLabel1.Text = PdnResources.GetString("ResizeDialog.PixelsLabel1.Text"); + this.pixelsLabel2.Text = PdnResources.GetString("ResizeDialog.PixelsLabel2.Text"); + this.resolutionLabel.Text = PdnResources.GetString("ResizeDialog.ResolutionLabel.Text"); + this.percentRB.Text = PdnResources.GetString("ResizeDialog.PercentRB.Text"); + this.absoluteRB.Text = PdnResources.GetString("ResizeDialog.AbsoluteRB.Text"); + this.resamplingLabel.Text = PdnResources.GetString("ResizeDialog.ResamplingLabel.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.okButton.Text = PdnResources.GetString("Form.OkButton.Text"); + this.newWidthLabel1.Text = PdnResources.GetString("ResizeDialog.NewWidthLabel1.Text"); + this.newHeightLabel1.Text = PdnResources.GetString("ResizeDialog.NewHeightLabel1.Text"); + this.newWidthLabel2.Text = PdnResources.GetString("ResizeDialog.NewWidthLabel1.Text"); + this.newHeightLabel2.Text = PdnResources.GetString("ResizeDialog.NewHeightLabel1.Text"); + this.constrainCheckBox.Text = PdnResources.GetString("ResizeDialog.ConstrainCheckBox.Text"); + this.unitsLabel1.Text = unitsComboBox1.UnitsText; + + upDownValueChangedDelegate = new EventHandler(upDown_ValueChanged); + + this.constrainer = new ResizeConstrainer(new Size((int)this.pixelWidthUpDown.Value, (int)this.pixelHeightUpDown.Value)); + SetupConstrainerEvents(); + + resamplingAlgorithmComboBox.Items.Clear(); + resamplingAlgorithmComboBox.Items.Add(new ResampleMethod(ResamplingAlgorithm.Bicubic)); + resamplingAlgorithmComboBox.Items.Add(new ResampleMethod(ResamplingAlgorithm.Bilinear)); + resamplingAlgorithmComboBox.Items.Add(new ResampleMethod(ResamplingAlgorithm.NearestNeighbor)); + resamplingAlgorithmComboBox.Items.Add(new ResampleMethod(ResamplingAlgorithm.SuperSampling)); + resamplingAlgorithmComboBox.SelectedItem = new ResampleMethod(ResamplingAlgorithm.SuperSampling); + + layers = 1; + + this.percentUpDown.Enabled = false; + + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuImageResizeIcon.png").Reference, Utility.TransparentKey); + PopulateAsteriskLabels(); + OnRadioButtonCheckedChanged(this, EventArgs.Empty); + } + + private void SetupConstrainerEvents() + { + this.constrainer.ConstrainToAspectChanged += new EventHandler(OnConstrainerConstrainToAspectChanged); + this.constrainer.NewHeightChanged += new EventHandler(constrainer_NewHeightChanged); + this.constrainer.NewWidthChanged += new EventHandler(OnConstrainerNewWidthChanged); + this.constrainer.ResolutionChanged += new EventHandler(OnConstrainerResolutionChanged); + this.constrainer.UnitsChanged += new EventHandler(OnConstrainerUnitsChanged); + + constrainCheckBox.Checked = constrainer.ConstrainToAspect; + SafeSetNudValue(this.pixelWidthUpDown, this.constrainer.NewPixelWidth); + SafeSetNudValue(this.pixelHeightUpDown, this.constrainer.NewPixelHeight); + SafeSetNudValue(this.printWidthUpDown, this.constrainer.NewWidth); + SafeSetNudValue(this.printHeightUpDown, this.constrainer.NewHeight); + SafeSetNudValue(this.resolutionUpDown, this.constrainer.Resolution); + unitsComboBox1.Units = constrainer.Units; + } + + protected override void OnLoad(EventArgs e) + { + this.ResumeLayout(true); // SuspendLayout() was called in constructor + base.OnLoad(e); + this.pixelWidthUpDown.Select(); + this.pixelWidthUpDown.Select(0, pixelWidthUpDown.Text.Length); + this.PopulateAsteriskLabels(); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.constrainCheckBox = new System.Windows.Forms.CheckBox(); + this.newWidthLabel1 = new System.Windows.Forms.Label(); + this.newHeightLabel1 = new System.Windows.Forms.Label(); + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.pixelWidthUpDown = new System.Windows.Forms.NumericUpDown(); + this.pixelHeightUpDown = new System.Windows.Forms.NumericUpDown(); + this.resizedImageHeader = new PaintDotNet.HeaderLabel(); + this.asteriskLabel = new System.Windows.Forms.Label(); + this.asteriskTextLabel = new System.Windows.Forms.Label(); + this.absoluteRB = new System.Windows.Forms.RadioButton(); + this.percentRB = new System.Windows.Forms.RadioButton(); + this.pixelsLabel1 = new System.Windows.Forms.Label(); + this.percentUpDown = new System.Windows.Forms.NumericUpDown(); + this.percentSignLabel = new System.Windows.Forms.Label(); + this.resolutionLabel = new System.Windows.Forms.Label(); + this.resolutionUpDown = new System.Windows.Forms.NumericUpDown(); + this.unitsComboBox2 = new PaintDotNet.UnitsComboBox(); + this.unitsComboBox1 = new PaintDotNet.UnitsComboBox(); + this.printWidthUpDown = new System.Windows.Forms.NumericUpDown(); + this.printHeightUpDown = new System.Windows.Forms.NumericUpDown(); + this.newWidthLabel2 = new System.Windows.Forms.Label(); + this.newHeightLabel2 = new System.Windows.Forms.Label(); + this.pixelsLabel2 = new System.Windows.Forms.Label(); + this.unitsLabel1 = new System.Windows.Forms.Label(); + this.pixelSizeHeader = new PaintDotNet.HeaderLabel(); + this.printSizeHeader = new PaintDotNet.HeaderLabel(); + this.resamplingLabel = new System.Windows.Forms.Label(); + this.resamplingAlgorithmComboBox = new System.Windows.Forms.ComboBox(); + ((System.ComponentModel.ISupportInitialize)(this.pixelWidthUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelHeightUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.percentUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.resolutionUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.printWidthUpDown)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.printHeightUpDown)).BeginInit(); + this.SuspendLayout(); + // + // constrainCheckBox + // + this.constrainCheckBox.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.constrainCheckBox.Location = new System.Drawing.Point(27, 101); + this.constrainCheckBox.Name = "constrainCheckBox"; + this.constrainCheckBox.Size = new System.Drawing.Size(248, 16); + this.constrainCheckBox.TabIndex = 25; + this.constrainCheckBox.CheckedChanged += new System.EventHandler(this.constrainCheckBox_CheckedChanged); + // + // newWidthLabel1 + // + this.newWidthLabel1.Location = new System.Drawing.Point(32, 145); + this.newWidthLabel1.Name = "newWidthLabel1"; + this.newWidthLabel1.Size = new System.Drawing.Size(79, 16); + this.newWidthLabel1.TabIndex = 0; + this.newWidthLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // newHeightLabel1 + // + this.newHeightLabel1.Location = new System.Drawing.Point(32, 169); + this.newHeightLabel1.Name = "newHeightLabel1"; + this.newHeightLabel1.Size = new System.Drawing.Size(79, 16); + this.newHeightLabel1.TabIndex = 3; + this.newHeightLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // okButton + // + this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.okButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.okButton.Location = new System.Drawing.Point(142, 315); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(72, 23); + this.okButton.TabIndex = 17; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.cancelButton.Location = new System.Drawing.Point(220, 315); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(72, 23); + this.cancelButton.TabIndex = 18; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // pixelWidthUpDown + // + this.pixelWidthUpDown.Location = new System.Drawing.Point(120, 144); + this.pixelWidthUpDown.Maximum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + 0}); + this.pixelWidthUpDown.Minimum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + -2147483648}); + this.pixelWidthUpDown.Name = "pixelWidthUpDown"; + this.pixelWidthUpDown.Size = new System.Drawing.Size(72, 20); + this.pixelWidthUpDown.TabIndex = 1; + this.pixelWidthUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.pixelWidthUpDown.Value = new System.Decimal(new int[] { + 4, + 0, + 0, + 0}); + this.pixelWidthUpDown.Enter += new System.EventHandler(this.OnUpDownEnter); + this.pixelWidthUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnUpDownKeyUp); + this.pixelWidthUpDown.ValueChanged += new System.EventHandler(this.upDown_ValueChanged); + this.pixelWidthUpDown.Leave += new System.EventHandler(this.OnUpDownLeave); + // + // pixelHeightUpDown + // + this.pixelHeightUpDown.Location = new System.Drawing.Point(120, 168); + this.pixelHeightUpDown.Maximum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + 0}); + this.pixelHeightUpDown.Minimum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + -2147483648}); + this.pixelHeightUpDown.Name = "pixelHeightUpDown"; + this.pixelHeightUpDown.Size = new System.Drawing.Size(72, 20); + this.pixelHeightUpDown.TabIndex = 4; + this.pixelHeightUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.pixelHeightUpDown.Value = new System.Decimal(new int[] { + 3, + 0, + 0, + 0}); + this.pixelHeightUpDown.Enter += new System.EventHandler(this.OnUpDownEnter); + this.pixelHeightUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnUpDownKeyUp); + this.pixelHeightUpDown.ValueChanged += new System.EventHandler(this.upDown_ValueChanged); + this.pixelHeightUpDown.Leave += new System.EventHandler(this.OnUpDownLeave); + // + // resizedImageHeader + // + this.resizedImageHeader.Location = new System.Drawing.Point(6, 8); + this.resizedImageHeader.Name = "resizedImageHeader"; + this.resizedImageHeader.Size = new System.Drawing.Size(290, 16); + this.resizedImageHeader.TabIndex = 19; + this.resizedImageHeader.TabStop = false; + // + // asteriskLabel + // + this.asteriskLabel.Location = new System.Drawing.Point(275, 28); + this.asteriskLabel.Name = "asteriskLabel"; + this.asteriskLabel.Size = new System.Drawing.Size(13, 16); + this.asteriskLabel.TabIndex = 15; + this.asteriskLabel.Visible = false; + // + // asteriskTextLabel + // + this.asteriskTextLabel.Location = new System.Drawing.Point(8, 290); + this.asteriskTextLabel.Name = "asteriskTextLabel"; + this.asteriskTextLabel.Size = new System.Drawing.Size(255, 16); + this.asteriskTextLabel.TabIndex = 16; + this.asteriskTextLabel.Visible = false; + // + // absoluteRB + // + this.absoluteRB.Checked = true; + this.absoluteRB.Location = new System.Drawing.Point(8, 78); + this.absoluteRB.Name = "absoluteRB"; + this.absoluteRB.Width = 264; + this.absoluteRB.AutoSize = true; + this.absoluteRB.TabIndex = 24; + this.absoluteRB.TabStop = true; + this.absoluteRB.FlatStyle = FlatStyle.System; + this.absoluteRB.CheckedChanged += new System.EventHandler(this.OnRadioButtonCheckedChanged); + // + // percentRB + // + this.percentRB.Location = new System.Drawing.Point(8, 51); + this.percentRB.Name = "percentRB"; + this.percentRB.TabIndex = 22; + this.percentRB.AutoSize = true; + this.percentRB.Width = 10; + this.percentRB.FlatStyle = FlatStyle.System; + this.percentRB.CheckedChanged += new System.EventHandler(this.OnRadioButtonCheckedChanged); + // + // pixelsLabel1 + // + this.pixelsLabel1.Location = new System.Drawing.Point(200, 145); + this.pixelsLabel1.Name = "pixelsLabel1"; + this.pixelsLabel1.Width = 93; + this.pixelsLabel1.TabIndex = 2; + this.pixelsLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // percentUpDown + // + this.percentUpDown.Location = new System.Drawing.Point(120, 54); + this.percentUpDown.Maximum = new System.Decimal(new int[] { + 2000, + 0, + 0, + 0}); + this.percentUpDown.Name = "percentUpDown"; + this.percentUpDown.Size = new System.Drawing.Size(72, 20); + this.percentUpDown.TabIndex = 23; + this.percentUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.percentUpDown.Value = new System.Decimal(new int[] { + 100, + 0, + 0, + 0}); + this.percentUpDown.Enter += new System.EventHandler(this.OnUpDownEnter); + this.percentUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnUpDownKeyUp); + this.percentUpDown.ValueChanged += new System.EventHandler(this.upDown_ValueChanged); + // + // percentSignLabel + // + this.percentSignLabel.Location = new System.Drawing.Point(200, 55); + this.percentSignLabel.Name = "percentSignLabel"; + this.percentSignLabel.Size = new System.Drawing.Size(32, 16); + this.percentSignLabel.TabIndex = 13; + this.percentSignLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // resolutionLabel + // + this.resolutionLabel.Location = new System.Drawing.Point(32, 193); + this.resolutionLabel.Name = "resolutionLabel"; + this.resolutionLabel.Size = new System.Drawing.Size(79, 16); + this.resolutionLabel.TabIndex = 6; + this.resolutionLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // resolutionUpDown + // + this.resolutionUpDown.DecimalPlaces = 2; + this.resolutionUpDown.Location = new System.Drawing.Point(120, 192); + this.resolutionUpDown.Maximum = new System.Decimal(new int[] { + 65535, + 0, + 0, + 0}); + this.resolutionUpDown.Minimum = new System.Decimal(new int[] { + 1, + 0, + 0, + 327680}); + this.resolutionUpDown.Name = "resolutionUpDown"; + this.resolutionUpDown.Size = new System.Drawing.Size(72, 20); + this.resolutionUpDown.TabIndex = 7; + this.resolutionUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.resolutionUpDown.Value = new System.Decimal(new int[] { + 72, + 0, + 0, + 0}); + this.resolutionUpDown.Enter += new System.EventHandler(this.OnUpDownEnter); + this.resolutionUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnUpDownKeyUp); + this.resolutionUpDown.ValueChanged += new System.EventHandler(this.upDown_ValueChanged); + this.resolutionUpDown.Leave += new System.EventHandler(this.OnUpDownLeave); + // + // unitsComboBox2 + // + this.unitsComboBox2.Location = new System.Drawing.Point(200, 192); + this.unitsComboBox2.Name = "unitsComboBox2"; + this.unitsComboBox2.PixelsAvailable = false; + this.unitsComboBox2.Size = new System.Drawing.Size(88, 21); + this.unitsComboBox2.TabIndex = 8; + this.unitsComboBox2.Units = PaintDotNet.MeasurementUnit.Inch; + this.unitsComboBox2.UnitsDisplayType = PaintDotNet.UnitsDisplayType.Ratio; + this.unitsComboBox2.UnitsChanged += new System.EventHandler(this.OnUnitsComboBox2UnitsChanged); + // + // unitsComboBox1 + // + this.unitsComboBox1.Location = new System.Drawing.Point(200, 235); + this.unitsComboBox1.Name = "unitsComboBox1"; + this.unitsComboBox1.PixelsAvailable = false; + this.unitsComboBox1.Size = new System.Drawing.Size(88, 21); + this.unitsComboBox1.TabIndex = 12; + this.unitsComboBox1.Units = PaintDotNet.MeasurementUnit.Inch; + this.unitsComboBox1.UnitsChanged += new System.EventHandler(this.OnUnitsComboBox1UnitsChanged); + // + // printWidthUpDown + // + this.printWidthUpDown.DecimalPlaces = 2; + this.printWidthUpDown.Location = new System.Drawing.Point(120, 235); + this.printWidthUpDown.Maximum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + 0}); + this.printWidthUpDown.Minimum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + -2147483648}); + this.printWidthUpDown.Name = "printWidthUpDown"; + this.printWidthUpDown.Size = new System.Drawing.Size(72, 20); + this.printWidthUpDown.TabIndex = 11; + this.printWidthUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.printWidthUpDown.Value = new System.Decimal(new int[] { + 2, + 0, + 0, + 0}); + this.printWidthUpDown.Enter += new System.EventHandler(this.OnUpDownEnter); + this.printWidthUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnUpDownKeyUp); + this.printWidthUpDown.ValueChanged += new System.EventHandler(this.upDown_ValueChanged); + this.printWidthUpDown.Leave += new System.EventHandler(this.OnUpDownLeave); + // + // printHeightUpDown + // + this.printHeightUpDown.DecimalPlaces = 2; + this.printHeightUpDown.Location = new System.Drawing.Point(120, 259); + this.printHeightUpDown.Maximum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + 0}); + this.printHeightUpDown.Minimum = new System.Decimal(new int[] { + 2147483647, + 0, + 0, + -2147483648}); + this.printHeightUpDown.Name = "printHeightUpDown"; + this.printHeightUpDown.Size = new System.Drawing.Size(72, 20); + this.printHeightUpDown.TabIndex = 14; + this.printHeightUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.printHeightUpDown.Value = new System.Decimal(new int[] { + 1, + 0, + 0, + 0}); + this.printHeightUpDown.Enter += new System.EventHandler(this.OnUpDownEnter); + this.printHeightUpDown.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnUpDownKeyUp); + this.printHeightUpDown.ValueChanged += new System.EventHandler(this.upDown_ValueChanged); + this.printHeightUpDown.Leave += new System.EventHandler(this.OnUpDownLeave); + // + // newWidthLabel2 + // + this.newWidthLabel2.Location = new System.Drawing.Point(32, 236); + this.newWidthLabel2.Name = "newWidthLabel2"; + this.newWidthLabel2.Size = new System.Drawing.Size(79, 16); + this.newWidthLabel2.TabIndex = 10; + this.newWidthLabel2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // newHeightLabel2 + // + this.newHeightLabel2.Location = new System.Drawing.Point(32, 260); + this.newHeightLabel2.Name = "newHeightLabel2"; + this.newHeightLabel2.Size = new System.Drawing.Size(79, 16); + this.newHeightLabel2.TabIndex = 13; + this.newHeightLabel2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // pixelsLabel2 + // + this.pixelsLabel2.Location = new System.Drawing.Point(200, 169); + this.pixelsLabel2.Name = "pixelsLabel2"; + this.pixelsLabel2.Size = new System.Drawing.Size(93, 16); + this.pixelsLabel2.TabIndex = 5; + this.pixelsLabel2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // unitsLabel1 + // + this.unitsLabel1.Location = new System.Drawing.Point(200, 261); + this.unitsLabel1.Name = "unitsLabel1"; + this.unitsLabel1.Size = new System.Drawing.Size(94, 16); + this.unitsLabel1.TabIndex = 15; + // + // pixelSizeHeader + // + this.pixelSizeHeader.Location = new System.Drawing.Point(25, 125); + this.pixelSizeHeader.Name = "pixelSizeHeader"; + this.pixelSizeHeader.Size = new System.Drawing.Size(271, 14); + this.pixelSizeHeader.TabIndex = 26; + this.pixelSizeHeader.TabStop = false; + // + // printSizeHeader + // + this.printSizeHeader.Location = new System.Drawing.Point(25, 216); + this.printSizeHeader.Name = "printSizeHeader"; + this.printSizeHeader.Size = new System.Drawing.Size(271, 14); + this.printSizeHeader.TabIndex = 9; + this.printSizeHeader.TabStop = false; + // + // resamplingLabel + // + this.resamplingLabel.Location = new System.Drawing.Point(6, 30); + this.resamplingLabel.Name = "resamplingLabel"; + this.resamplingLabel.AutoSize = true; + this.resamplingLabel.Size = new System.Drawing.Size(88, 16); + this.resamplingLabel.TabIndex = 20; + this.resamplingLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // resamplingAlgorithmComboBox + // + this.resamplingAlgorithmComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.resamplingAlgorithmComboBox.ItemHeight = 13; + this.resamplingAlgorithmComboBox.Location = new System.Drawing.Point(120, 27); + this.resamplingAlgorithmComboBox.Name = "resamplingAlgorithmComboBox"; + this.resamplingAlgorithmComboBox.Size = new System.Drawing.Size(152, 21); + this.resamplingAlgorithmComboBox.Sorted = true; + this.resamplingAlgorithmComboBox.TabIndex = 21; + this.resamplingAlgorithmComboBox.FlatStyle = FlatStyle.System; + this.resamplingAlgorithmComboBox.SelectedIndexChanged += new System.EventHandler(this.OnResamplingAlgorithmComboBoxSelectedIndexChanged); + // + // ResizeDialog + // + this.AcceptButton = this.okButton; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(298, 344); + this.Controls.Add(this.printSizeHeader); + this.Controls.Add(this.pixelSizeHeader); + this.Controls.Add(this.unitsLabel1); + this.Controls.Add(this.pixelsLabel2); + this.Controls.Add(this.newHeightLabel2); + this.Controls.Add(this.newWidthLabel2); + this.Controls.Add(this.printHeightUpDown); + this.Controls.Add(this.printWidthUpDown); + this.Controls.Add(this.unitsComboBox1); + this.Controls.Add(this.unitsComboBox2); + this.Controls.Add(this.resolutionUpDown); + this.Controls.Add(this.resolutionLabel); + this.Controls.Add(this.resizedImageHeader); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Controls.Add(this.asteriskLabel); + this.Controls.Add(this.asteriskTextLabel); + this.Controls.Add(this.absoluteRB); + this.Controls.Add(this.percentRB); + this.Controls.Add(this.pixelWidthUpDown); + this.Controls.Add(this.pixelHeightUpDown); + this.Controls.Add(this.pixelsLabel1); + this.Controls.Add(this.newHeightLabel1); + this.Controls.Add(this.newWidthLabel1); + this.Controls.Add(this.resamplingAlgorithmComboBox); + this.Controls.Add(this.resamplingLabel); + this.Controls.Add(this.constrainCheckBox); + this.Controls.Add(this.percentUpDown); + this.Controls.Add(this.percentSignLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ResizeDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Controls.SetChildIndex(this.percentSignLabel, 0); + this.Controls.SetChildIndex(this.percentUpDown, 0); + this.Controls.SetChildIndex(this.constrainCheckBox, 0); + this.Controls.SetChildIndex(this.resamplingLabel, 0); + this.Controls.SetChildIndex(this.resamplingAlgorithmComboBox, 0); + this.Controls.SetChildIndex(this.newWidthLabel1, 0); + this.Controls.SetChildIndex(this.newHeightLabel1, 0); + this.Controls.SetChildIndex(this.pixelsLabel1, 0); + this.Controls.SetChildIndex(this.pixelHeightUpDown, 0); + this.Controls.SetChildIndex(this.pixelWidthUpDown, 0); + this.Controls.SetChildIndex(this.percentRB, 0); + this.Controls.SetChildIndex(this.absoluteRB, 0); + this.Controls.SetChildIndex(this.asteriskTextLabel, 0); + this.Controls.SetChildIndex(this.asteriskLabel, 0); + this.Controls.SetChildIndex(this.okButton, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.resizedImageHeader, 0); + this.Controls.SetChildIndex(this.resolutionLabel, 0); + this.Controls.SetChildIndex(this.resolutionUpDown, 0); + this.Controls.SetChildIndex(this.unitsComboBox2, 0); + this.Controls.SetChildIndex(this.unitsComboBox1, 0); + this.Controls.SetChildIndex(this.printWidthUpDown, 0); + this.Controls.SetChildIndex(this.printHeightUpDown, 0); + this.Controls.SetChildIndex(this.newWidthLabel2, 0); + this.Controls.SetChildIndex(this.newHeightLabel2, 0); + this.Controls.SetChildIndex(this.pixelsLabel2, 0); + this.Controls.SetChildIndex(this.unitsLabel1, 0); + this.Controls.SetChildIndex(this.pixelSizeHeader, 0); + this.Controls.SetChildIndex(this.printSizeHeader, 0); + ((System.ComponentModel.ISupportInitialize)(this.pixelWidthUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.pixelHeightUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.percentUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.resolutionUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.printWidthUpDown)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.printHeightUpDown)).EndInit(); + this.ResumeLayout(false); + + } + #endregion + + // We have to delay closing the dialog after clicking OK because otherwise the + // numbers do not 'sync up' properly when Maintain Aspect Ratio is checked. + // For example, if you type 200 for width, that means the height goes from 0 + // to 1 to 15 to 150 ... however if you press ENTER immediately after the 2nd 0 in + // 200, you get an image that is 20x15. + // See bug #1195. + private int okTimerInterval = 200; + private System.Windows.Forms.Timer okTimer = null; + + private void okButton_Click(object sender, System.EventArgs e) + { + if (this.okTimer == null) + { + this.okTimer = new Timer(); + this.okTimer.Interval = okTimerInterval; + this.okTimer.Tick += new EventHandler(okTimer_Tick); + this.okTimer.Enabled = true; + } + } + + private void okTimer_Tick(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + this.Close(); + this.okTimer.Dispose(); + } + + private void cancelButton_Click(object sender, System.EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void constrainCheckBox_CheckedChanged(object sender, System.EventArgs e) + { + this.constrainer.ConstrainToAspect = constrainCheckBox.Checked; + } + + private int ignoreUpDownValueChanged = 0; + private int getValueFromText = 0; + + private void upDown_ValueChanged(object sender, System.EventArgs e) + { + if (ignoreUpDownValueChanged > 0) + { + return; + } + + double val; + + if (sender == percentUpDown) + { + if (getValueFromText > 0) + { + if (Utility.GetUpDownValueFromText(this.percentUpDown, out val)) + { + if (val >= (double)this.percentUpDown.Minimum && + val <= (double)this.percentUpDown.Maximum) + { + this.constrainer.SetByPercent(val / 100.0); + } + } + } + else + { + this.constrainer.SetByPercent((double)this.percentUpDown.Value / 100.0); + } + } + + if (sender == pixelWidthUpDown) + { + if (getValueFromText > 0) + { + if (Utility.GetUpDownValueFromText(this.pixelWidthUpDown, out val)) + { + this.constrainer.NewPixelWidth = val; + } + } + else + { + this.constrainer.NewPixelWidth = (double)this.pixelWidthUpDown.Value; + } + } + + if (sender == pixelHeightUpDown) + { + if (getValueFromText > 0) + { + if (Utility.GetUpDownValueFromText(this.pixelHeightUpDown, out val)) + { + this.constrainer.NewPixelHeight = val; + } + } + else + { + this.constrainer.NewPixelHeight = (double)this.pixelHeightUpDown.Value; + } + } + + if (sender == printWidthUpDown) + { + if (getValueFromText > 0) + { + if (Utility.GetUpDownValueFromText(this.printWidthUpDown, out val)) + { + this.constrainer.NewWidth = val; + } + } + else + { + this.constrainer.NewWidth = (double)this.printWidthUpDown.Value; + } + } + + if (sender == printHeightUpDown) + { + if (getValueFromText > 0) + { + if (Utility.GetUpDownValueFromText(this.printHeightUpDown, out val)) + { + this.constrainer.NewHeight = val; + } + } + else + { + this.constrainer.NewHeight = (double)this.printHeightUpDown.Value; + } + } + + if (sender == resolutionUpDown) + { + if (getValueFromText > 0) + { + if (Utility.GetUpDownValueFromText(this.resolutionUpDown, out val)) + { + if (val >= ResizeConstrainer.MinResolution) + { + this.constrainer.Resolution = val; + } + } + } + else + { + if ((double)this.resolutionUpDown.Value >= ResizeConstrainer.MinResolution) + { + this.constrainer.Resolution = (double)this.resolutionUpDown.Value; + } + } + } + + UpdateSizeText(); + PopulateAsteriskLabels(); + TryToEnableOkButton(); + } + + private void OnUpDownKeyUp(object sender, System.Windows.Forms.KeyEventArgs e) + { + if (e.KeyCode != Keys.Tab) + { + double val; + + if (Utility.GetUpDownValueFromText((NumericUpDown)sender, out val)) + { + UpdateSizeText(); + + ++getValueFromText; + upDown_ValueChanged(sender, e); + --getValueFromText; + } + + TryToEnableOkButton(); + } + } + + private void OnUpDownEnter(object sender, System.EventArgs e) + { + NumericUpDown nud = (NumericUpDown)sender; + nud.Select(0, nud.Text.Length); + } + + private void OnUpDownLeave(object sender, System.EventArgs e) + { + ((NumericUpDown)sender).Value = ((NumericUpDown)sender).Value; + TryToEnableOkButton(); + } + + private void OnRadioButtonCheckedChanged(object sender, System.EventArgs e) + { + if (this.absoluteRB.Checked) + { + this.pixelWidthUpDown.Enabled = true; + this.pixelHeightUpDown.Enabled = true; + this.printWidthUpDown.Enabled = true; + this.printHeightUpDown.Enabled = true; + this.constrainCheckBox.Enabled = true; + this.unitsComboBox1.Enabled = true; + this.unitsComboBox2.Enabled = true; + this.resolutionUpDown.Enabled = true; + this.percentUpDown.Enabled = false; + } + else if (this.percentRB.Checked) + { + this.pixelWidthUpDown.Enabled = false; + this.pixelHeightUpDown.Enabled = false; + this.printWidthUpDown.Enabled = false; + this.printHeightUpDown.Enabled = false; + this.constrainCheckBox.Enabled = false; + this.unitsComboBox1.Enabled = false; + this.unitsComboBox2.Enabled = false; + this.resolutionUpDown.Enabled = false; + this.percentUpDown.Enabled = true; + this.percentUpDown.Select(); + } + } + + private void PopulateAsteriskLabels() + { + ResampleMethod rm = this.resamplingAlgorithmComboBox.SelectedItem as ResampleMethod; + + if (rm == null) + { + return; + } + + switch (rm.method) + { + default: + this.asteriskLabel.Visible = false; + this.asteriskTextLabel.Visible = false; + break; + + case ResamplingAlgorithm.SuperSampling: + if (this.ImageWidth < this.OriginalSize.Width && + this.ImageHeight < this.OriginalSize.Height) + { + this.asteriskTextLabel.Text = PdnResources.GetString("ResizeDialog.AsteriskTextLabel.SuperSampling"); + } + else + { + this.asteriskTextLabel.Text = PdnResources.GetString("ResizeDialog.AsteriskTextLabel.Bicubic"); + } + + if (this.resamplingAlgorithmComboBox.Visible) + { + this.asteriskLabel.Visible = true; + this.asteriskTextLabel.Visible = true; + } + + break; + } + } + + private void OnResamplingAlgorithmComboBoxSelectedIndexChanged(object sender, System.EventArgs e) + { + PopulateAsteriskLabels(); + } + + private void OnUnitsComboBox1UnitsChanged(object sender, System.EventArgs e) + { + this.constrainer.Units = unitsComboBox1.Units; + this.unitsLabel1.Text = unitsComboBox1.UnitsText; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void OnUnitsComboBox2UnitsChanged(object sender, System.EventArgs e) + { + this.unitsComboBox1.Units = this.unitsComboBox2.Units; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void OnConstrainerConstrainToAspectChanged(object sender, EventArgs e) + { + this.constrainCheckBox.Checked = this.constrainer.ConstrainToAspect; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void OnConstrainerNewWidthChanged(object sender, EventArgs e) + { + ++ignoreUpDownValueChanged; + + double val = 0.0; + bool result; + + result = Utility.GetUpDownValueFromText(this.pixelWidthUpDown, out val); + if (!result || val != this.constrainer.NewPixelWidth) + { + SafeSetNudValue(this.pixelWidthUpDown, this.constrainer.NewPixelWidth); + } + + result = Utility.GetUpDownValueFromText(this.printWidthUpDown, out val); + if (!result || val != this.constrainer.NewWidth) + { + SafeSetNudValue(this.printWidthUpDown, this.constrainer.NewWidth); + } + + --ignoreUpDownValueChanged; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void constrainer_NewHeightChanged(object sender, EventArgs e) + { + ++ignoreUpDownValueChanged; + + double val; + + if (Utility.GetUpDownValueFromText(this.pixelHeightUpDown, out val)) + { + if (val != this.constrainer.NewPixelHeight) + { + SafeSetNudValue(this.pixelHeightUpDown, this.constrainer.NewPixelHeight); + } + } + + if (Utility.GetUpDownValueFromText(this.printHeightUpDown, out val)) + { + if (val != this.constrainer.NewHeight) + { + SafeSetNudValue(this.printHeightUpDown, this.constrainer.NewHeight); + } + } + + --ignoreUpDownValueChanged; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void OnConstrainerResolutionChanged(object sender, EventArgs e) + { + ++ignoreUpDownValueChanged; + + double val; + + if (Utility.GetUpDownValueFromText(this.resolutionUpDown, out val)) + { + if (val != this.constrainer.Resolution) + { + SafeSetNudValue(this.resolutionUpDown, this.constrainer.Resolution); + } + } + + --ignoreUpDownValueChanged; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void OnConstrainerUnitsChanged(object sender, EventArgs e) + { + this.unitsComboBox1.Units = constrainer.Units; + this.unitsComboBox2.Units = constrainer.Units; + UpdateSizeText(); + TryToEnableOkButton(); + } + + private void TryToEnableOkButton() + { + double pixelWidth; + double pixelHeight; + double printWidth; + double printHeight; + double resolution; + double percent; + + bool b1 = Utility.GetUpDownValueFromText(this.pixelWidthUpDown, out pixelWidth); + bool b2 = Utility.GetUpDownValueFromText(this.pixelHeightUpDown, out pixelHeight); + bool b3 = Utility.GetUpDownValueFromText(this.printWidthUpDown, out printWidth); + bool b4 = Utility.GetUpDownValueFromText(this.printHeightUpDown, out printHeight); + bool b5 = Utility.GetUpDownValueFromText(this.resolutionUpDown, out resolution); + bool b6 = Utility.GetUpDownValueFromText(this.percentUpDown, out percent); + + bool b7 = (pixelWidth >= 1.0 && pixelWidth <= 65535.0); + bool b8 = (pixelHeight >= 1.0 && pixelHeight <= 65535.0); + bool b9 = (printWidth > 0.0); + bool b10 = (printHeight > 0.0); + bool b11 = (resolution >= ResizeConstrainer.MinResolution && resolution < 2000000.0); + bool b12 = (percent >= (double)this.percentUpDown.Minimum && percent <= (double)this.percentUpDown.Maximum); + + bool enable = b1 && b2 && b3 && b4 && b5 && b6 && b7 && b8 && b9 && b10 && b11 && b12; + okButton.Enabled = enable; + } + + private void SafeSetNudValue(NumericUpDown nud, double value) + { + try + { + decimal newValue = (decimal)value; + + if (newValue >= nud.Minimum && newValue <= nud.Maximum) + { + nud.Value = newValue; + } + } + + catch (OverflowException ex) + { + Tracing.Ping("Exception: " + ex.ToString()); + } + } + } +} diff --git a/src/ResizeDialog.resx b/src/ResizeDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/AssemblyInfo.cs b/src/Resources/AssemblyInfo.cs new file mode 100644 index 0000000..d31cb98 --- /dev/null +++ b/src/Resources/AssemblyInfo.cs @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET Resources")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] + +// Change this to say "Final" for final builds. Otherwise the titlebar will contain +// a long version string. Final versions should just say the ApplicationProduct +// attribute (i.e., "Paint.NET" instead of "Paint.NET (Beta 2 build: 1.0.*.*)" +// Use this to hold the current milestone title, such as "Milestone 2" or "Beta 3" +[assembly: AssemblyConfiguration("Personal")] + +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: DefaultDependency(LoadHint.Always)] +[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] diff --git a/src/Resources/Cursors/CloneStampToolCursor.cur b/src/Resources/Cursors/CloneStampToolCursor.cur new file mode 100644 index 0000000..df153ad Binary files /dev/null and b/src/Resources/Cursors/CloneStampToolCursor.cur differ diff --git a/src/Resources/Cursors/CloneStampToolCursorSetSource.cur b/src/Resources/Cursors/CloneStampToolCursorSetSource.cur new file mode 100644 index 0000000..a63625c Binary files /dev/null and b/src/Resources/Cursors/CloneStampToolCursorSetSource.cur differ diff --git a/src/Resources/Cursors/ColorPickerToolCursor.cur b/src/Resources/Cursors/ColorPickerToolCursor.cur new file mode 100644 index 0000000..7dfab44 Binary files /dev/null and b/src/Resources/Cursors/ColorPickerToolCursor.cur differ diff --git a/src/Resources/Cursors/EllipseSelectToolCursor.cur b/src/Resources/Cursors/EllipseSelectToolCursor.cur new file mode 100644 index 0000000..6b61089 Binary files /dev/null and b/src/Resources/Cursors/EllipseSelectToolCursor.cur differ diff --git a/src/Resources/Cursors/EllipseSelectToolCursorMinus.cur b/src/Resources/Cursors/EllipseSelectToolCursorMinus.cur new file mode 100644 index 0000000..e16e28e Binary files /dev/null and b/src/Resources/Cursors/EllipseSelectToolCursorMinus.cur differ diff --git a/src/Resources/Cursors/EllipseSelectToolCursorMouseDown.cur b/src/Resources/Cursors/EllipseSelectToolCursorMouseDown.cur new file mode 100644 index 0000000..9d43a36 Binary files /dev/null and b/src/Resources/Cursors/EllipseSelectToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/EllipseSelectToolCursorPlus.cur b/src/Resources/Cursors/EllipseSelectToolCursorPlus.cur new file mode 100644 index 0000000..05d0196 Binary files /dev/null and b/src/Resources/Cursors/EllipseSelectToolCursorPlus.cur differ diff --git a/src/Resources/Cursors/EllipseToolCursor.cur b/src/Resources/Cursors/EllipseToolCursor.cur new file mode 100644 index 0000000..1021e7d Binary files /dev/null and b/src/Resources/Cursors/EllipseToolCursor.cur differ diff --git a/src/Resources/Cursors/EraserToolCursor.cur b/src/Resources/Cursors/EraserToolCursor.cur new file mode 100644 index 0000000..32187d4 Binary files /dev/null and b/src/Resources/Cursors/EraserToolCursor.cur differ diff --git a/src/Resources/Cursors/EraserToolCursorMouseDown.cur b/src/Resources/Cursors/EraserToolCursorMouseDown.cur new file mode 100644 index 0000000..3f15aba Binary files /dev/null and b/src/Resources/Cursors/EraserToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/FreeformShapeToolCursor.cur b/src/Resources/Cursors/FreeformShapeToolCursor.cur new file mode 100644 index 0000000..ebad8c0 Binary files /dev/null and b/src/Resources/Cursors/FreeformShapeToolCursor.cur differ diff --git a/src/Resources/Cursors/GenericToolCursor.cur b/src/Resources/Cursors/GenericToolCursor.cur new file mode 100644 index 0000000..726cef4 Binary files /dev/null and b/src/Resources/Cursors/GenericToolCursor.cur differ diff --git a/src/Resources/Cursors/GenericToolCursorMouseDown.cur b/src/Resources/Cursors/GenericToolCursorMouseDown.cur new file mode 100644 index 0000000..3f15aba Binary files /dev/null and b/src/Resources/Cursors/GenericToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/LassoSelectToolCursor.cur b/src/Resources/Cursors/LassoSelectToolCursor.cur new file mode 100644 index 0000000..720e17f Binary files /dev/null and b/src/Resources/Cursors/LassoSelectToolCursor.cur differ diff --git a/src/Resources/Cursors/LassoSelectToolCursorMinus.cur b/src/Resources/Cursors/LassoSelectToolCursorMinus.cur new file mode 100644 index 0000000..dee5f1f Binary files /dev/null and b/src/Resources/Cursors/LassoSelectToolCursorMinus.cur differ diff --git a/src/Resources/Cursors/LassoSelectToolCursorMouseDown.cur b/src/Resources/Cursors/LassoSelectToolCursorMouseDown.cur new file mode 100644 index 0000000..d81ddeb Binary files /dev/null and b/src/Resources/Cursors/LassoSelectToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/LassoSelectToolCursorPlus.cur b/src/Resources/Cursors/LassoSelectToolCursorPlus.cur new file mode 100644 index 0000000..c2ca315 Binary files /dev/null and b/src/Resources/Cursors/LassoSelectToolCursorPlus.cur differ diff --git a/src/Resources/Cursors/LineToolCursor.cur b/src/Resources/Cursors/LineToolCursor.cur new file mode 100644 index 0000000..dcfa961 Binary files /dev/null and b/src/Resources/Cursors/LineToolCursor.cur differ diff --git a/src/Resources/Cursors/MagicWandToolCursor.cur b/src/Resources/Cursors/MagicWandToolCursor.cur new file mode 100644 index 0000000..57996d1 Binary files /dev/null and b/src/Resources/Cursors/MagicWandToolCursor.cur differ diff --git a/src/Resources/Cursors/MagicWandToolCursorMinus.cur b/src/Resources/Cursors/MagicWandToolCursorMinus.cur new file mode 100644 index 0000000..5787d20 Binary files /dev/null and b/src/Resources/Cursors/MagicWandToolCursorMinus.cur differ diff --git a/src/Resources/Cursors/MagicWandToolCursorPlus.cur b/src/Resources/Cursors/MagicWandToolCursorPlus.cur new file mode 100644 index 0000000..27d69a5 Binary files /dev/null and b/src/Resources/Cursors/MagicWandToolCursorPlus.cur differ diff --git a/src/Resources/Cursors/MoveSelectionToolCursor.cur b/src/Resources/Cursors/MoveSelectionToolCursor.cur new file mode 100644 index 0000000..92c276d Binary files /dev/null and b/src/Resources/Cursors/MoveSelectionToolCursor.cur differ diff --git a/src/Resources/Cursors/MoveToolCursor.cur b/src/Resources/Cursors/MoveToolCursor.cur new file mode 100644 index 0000000..f82e40a Binary files /dev/null and b/src/Resources/Cursors/MoveToolCursor.cur differ diff --git a/src/Resources/Cursors/PaintBrushToolCursor.cur b/src/Resources/Cursors/PaintBrushToolCursor.cur new file mode 100644 index 0000000..726cef4 Binary files /dev/null and b/src/Resources/Cursors/PaintBrushToolCursor.cur differ diff --git a/src/Resources/Cursors/PaintBrushToolCursorMouseDown.cur b/src/Resources/Cursors/PaintBrushToolCursorMouseDown.cur new file mode 100644 index 0000000..3f15aba Binary files /dev/null and b/src/Resources/Cursors/PaintBrushToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/PaintBucketToolCursor.cur b/src/Resources/Cursors/PaintBucketToolCursor.cur new file mode 100644 index 0000000..f183017 Binary files /dev/null and b/src/Resources/Cursors/PaintBucketToolCursor.cur differ diff --git a/src/Resources/Cursors/PanToolCursor.cur b/src/Resources/Cursors/PanToolCursor.cur new file mode 100644 index 0000000..6d10c84 Binary files /dev/null and b/src/Resources/Cursors/PanToolCursor.cur differ diff --git a/src/Resources/Cursors/PanToolCursorInvalid.cur b/src/Resources/Cursors/PanToolCursorInvalid.cur new file mode 100644 index 0000000..a9ba0d7 Binary files /dev/null and b/src/Resources/Cursors/PanToolCursorInvalid.cur differ diff --git a/src/Resources/Cursors/PanToolCursorMouseDown.cur b/src/Resources/Cursors/PanToolCursorMouseDown.cur new file mode 100644 index 0000000..5f3c236 Binary files /dev/null and b/src/Resources/Cursors/PanToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/PencilToolCursor.cur b/src/Resources/Cursors/PencilToolCursor.cur new file mode 100644 index 0000000..7359322 Binary files /dev/null and b/src/Resources/Cursors/PencilToolCursor.cur differ diff --git a/src/Resources/Cursors/RecoloringToolCursor.cur b/src/Resources/Cursors/RecoloringToolCursor.cur new file mode 100644 index 0000000..9c24822 Binary files /dev/null and b/src/Resources/Cursors/RecoloringToolCursor.cur differ diff --git a/src/Resources/Cursors/RecoloringToolCursorAdjustColor.cur b/src/Resources/Cursors/RecoloringToolCursorAdjustColor.cur new file mode 100644 index 0000000..ea33fe1 Binary files /dev/null and b/src/Resources/Cursors/RecoloringToolCursorAdjustColor.cur differ diff --git a/src/Resources/Cursors/RecoloringToolCursorPickColor.cur b/src/Resources/Cursors/RecoloringToolCursorPickColor.cur new file mode 100644 index 0000000..9c9f8bb Binary files /dev/null and b/src/Resources/Cursors/RecoloringToolCursorPickColor.cur differ diff --git a/src/Resources/Cursors/RectangleSelectToolCursor.cur b/src/Resources/Cursors/RectangleSelectToolCursor.cur new file mode 100644 index 0000000..ac1df0a Binary files /dev/null and b/src/Resources/Cursors/RectangleSelectToolCursor.cur differ diff --git a/src/Resources/Cursors/RectangleSelectToolCursorMinus.cur b/src/Resources/Cursors/RectangleSelectToolCursorMinus.cur new file mode 100644 index 0000000..63dc5cc Binary files /dev/null and b/src/Resources/Cursors/RectangleSelectToolCursorMinus.cur differ diff --git a/src/Resources/Cursors/RectangleSelectToolCursorMouseDown.cur b/src/Resources/Cursors/RectangleSelectToolCursorMouseDown.cur new file mode 100644 index 0000000..9d43a36 Binary files /dev/null and b/src/Resources/Cursors/RectangleSelectToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/RectangleSelectToolCursorPlus.cur b/src/Resources/Cursors/RectangleSelectToolCursorPlus.cur new file mode 100644 index 0000000..7b885d3 Binary files /dev/null and b/src/Resources/Cursors/RectangleSelectToolCursorPlus.cur differ diff --git a/src/Resources/Cursors/RectangleToolCursor.cur b/src/Resources/Cursors/RectangleToolCursor.cur new file mode 100644 index 0000000..cb33a72 Binary files /dev/null and b/src/Resources/Cursors/RectangleToolCursor.cur differ diff --git a/src/Resources/Cursors/RoundedRectangleToolCursor.cur b/src/Resources/Cursors/RoundedRectangleToolCursor.cur new file mode 100644 index 0000000..8bed9db Binary files /dev/null and b/src/Resources/Cursors/RoundedRectangleToolCursor.cur differ diff --git a/src/Resources/Cursors/ShapeToolCursor.cur b/src/Resources/Cursors/ShapeToolCursor.cur new file mode 100644 index 0000000..726cef4 Binary files /dev/null and b/src/Resources/Cursors/ShapeToolCursor.cur differ diff --git a/src/Resources/Cursors/ShapeToolCursorMouseDown.cur b/src/Resources/Cursors/ShapeToolCursorMouseDown.cur new file mode 100644 index 0000000..3f15aba Binary files /dev/null and b/src/Resources/Cursors/ShapeToolCursorMouseDown.cur differ diff --git a/src/Resources/Cursors/TextToolCursor.cur b/src/Resources/Cursors/TextToolCursor.cur new file mode 100644 index 0000000..b9ce2b4 Binary files /dev/null and b/src/Resources/Cursors/TextToolCursor.cur differ diff --git a/src/Resources/Cursors/ZoomInToolCursor.cur b/src/Resources/Cursors/ZoomInToolCursor.cur new file mode 100644 index 0000000..35f5870 Binary files /dev/null and b/src/Resources/Cursors/ZoomInToolCursor.cur differ diff --git a/src/Resources/Cursors/ZoomOutToolCursor.cur b/src/Resources/Cursors/ZoomOutToolCursor.cur new file mode 100644 index 0000000..82e7492 Binary files /dev/null and b/src/Resources/Cursors/ZoomOutToolCursor.cur differ diff --git a/src/Resources/Cursors/ZoomToolCursor.cur b/src/Resources/Cursors/ZoomToolCursor.cur new file mode 100644 index 0000000..6c09ea3 Binary files /dev/null and b/src/Resources/Cursors/ZoomToolCursor.cur differ diff --git a/src/Resources/Files/AboutCredits.rtf b/src/Resources/Files/AboutCredits.rtf new file mode 100644 index 0000000..1c573cf Binary files /dev/null and b/src/Resources/Files/AboutCredits.rtf differ diff --git a/src/Resources/Files/License.txt b/src/Resources/Files/License.txt new file mode 100644 index 0000000..3e855fc --- /dev/null +++ b/src/Resources/Files/License.txt @@ -0,0 +1,23 @@ +Paint.NET +Copyright (C) dotPDN LLC, Rick Brewster, Chris Crosetto, Tom Jackson, Michael Kelsey, Brandon Ortiz, Craig Taylor, Chris Trevino, and Luke Walker. +Portions Copyright (C) Microsoft Corporation. All Rights Reserved. + +License last updated: June 1, 2008 + +For more licensing information and answers to Frequently Asked Questions, please go to: http://www.getpaint.net/license.html + +This software is licensed as per the MIT License below, but with three (3) exceptions: + +* Exception 1: The Paint.NET logo and icon artwork are Copyright (C) Rick Brewster. They are covered by the Creative Commons Attribution-NonCommercial-NoDerivs 2.5 license which is detailed here: http://creativecommons.org/licenses/by-nc-nd/2.5/ . However, permission is granted to use the logo and icon artwork in ways that directly discuss or promote Paint.NET (e.g. blog and news posts about Paint.NET, "Made with Paint.NET" watermarks or insets). + +* Exception 2: Paint.NET makes use of certain text and graphic resources that it comes with (e.g., toolbar icon graphics, text for menu items and the status bar). These are collectively referred to as "resource assets" and are defined to include the contents of files installed by Paint.NET, or included in its source code distribution, that have a .RESOURCES, .RESX, or .PNG file extension. This also includes embedded resource files within the PaintDotNet.Resources.dll installed file. These "resource assets" are covered by the Creative Commons Attribution-NonCommercial-NoDerivs 2.5 license which is detailed here: http://creativecommons.org/licenses/by-nc-nd/2.5/ . However, permission is granted to create and distribute derivative works of the "resource assets" for the sole purpose of providing a translation to a language other than English. Some "resource assets" are included in unmodified form from external icon or image libraries and are still covered by their original, respective licenses (e.g., "Silk", "Visual Studio 2005 Image Library"). + +* Exception 3: Although the Paint.NET source code distribution includes the GPC source code, use of the GPC code in any other commercial application is not permitted without a GPC Commercial Use Licence from The University of Manchester. For more information, please refer to the GPC website at: http://www.cs.man.ac.uk/~toby/alan/software/ + +MIT License: http://www.opensource.org/licenses/mit-license.php + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/Resources/Icons/AddNoiseEffect.png b/src/Resources/Icons/AddNoiseEffect.png new file mode 100644 index 0000000..caa8261 Binary files /dev/null and b/src/Resources/Icons/AddNoiseEffect.png differ diff --git a/src/Resources/Icons/AllColorChannelsIcon.png b/src/Resources/Icons/AllColorChannelsIcon.png new file mode 100644 index 0000000..73d9214 Binary files /dev/null and b/src/Resources/Icons/AllColorChannelsIcon.png differ diff --git a/src/Resources/Icons/AlphaChannelOnlyIcon.png b/src/Resources/Icons/AlphaChannelOnlyIcon.png new file mode 100644 index 0000000..179f91e Binary files /dev/null and b/src/Resources/Icons/AlphaChannelOnlyIcon.png differ diff --git a/src/Resources/Icons/AntiAliasingDisabledIcon.png b/src/Resources/Icons/AntiAliasingDisabledIcon.png new file mode 100644 index 0000000..49dd7aa Binary files /dev/null and b/src/Resources/Icons/AntiAliasingDisabledIcon.png differ diff --git a/src/Resources/Icons/AntiAliasingEnabledIcon.png b/src/Resources/Icons/AntiAliasingEnabledIcon.png new file mode 100644 index 0000000..9c87485 Binary files /dev/null and b/src/Resources/Icons/AntiAliasingEnabledIcon.png differ diff --git a/src/Resources/Icons/AutoLevel.png b/src/Resources/Icons/AutoLevel.png new file mode 100644 index 0000000..a5aff2a Binary files /dev/null and b/src/Resources/Icons/AutoLevel.png differ diff --git a/src/Resources/Icons/BlackAndWhiteIcon.png b/src/Resources/Icons/BlackAndWhiteIcon.png new file mode 100644 index 0000000..141e11e Binary files /dev/null and b/src/Resources/Icons/BlackAndWhiteIcon.png differ diff --git a/src/Resources/Icons/BlendingEnabledIcon.png b/src/Resources/Icons/BlendingEnabledIcon.png new file mode 100644 index 0000000..553af64 Binary files /dev/null and b/src/Resources/Icons/BlendingEnabledIcon.png differ diff --git a/src/Resources/Icons/BlendingOverwriteIcon.png b/src/Resources/Icons/BlendingOverwriteIcon.png new file mode 100644 index 0000000..5537fb8 Binary files /dev/null and b/src/Resources/Icons/BlendingOverwriteIcon.png differ diff --git a/src/Resources/Icons/BlurEffect.png b/src/Resources/Icons/BlurEffect.png new file mode 100644 index 0000000..881d96d Binary files /dev/null and b/src/Resources/Icons/BlurEffect.png differ diff --git a/src/Resources/Icons/BrightnessAndContrastAdjustment.png b/src/Resources/Icons/BrightnessAndContrastAdjustment.png new file mode 100644 index 0000000..5064049 Binary files /dev/null and b/src/Resources/Icons/BrightnessAndContrastAdjustment.png differ diff --git a/src/Resources/Icons/BugWarning.png b/src/Resources/Icons/BugWarning.png new file mode 100644 index 0000000..c4e8c28 Binary files /dev/null and b/src/Resources/Icons/BugWarning.png differ diff --git a/src/Resources/Icons/BulgeEffect.png b/src/Resources/Icons/BulgeEffect.png new file mode 100644 index 0000000..b7ab2c4 Binary files /dev/null and b/src/Resources/Icons/BulgeEffect.png differ diff --git a/src/Resources/Icons/CancelIcon.png b/src/Resources/Icons/CancelIcon.png new file mode 100644 index 0000000..6ecb28d Binary files /dev/null and b/src/Resources/Icons/CancelIcon.png differ diff --git a/src/Resources/Icons/CloneStampToolIcon.png b/src/Resources/Icons/CloneStampToolIcon.png new file mode 100644 index 0000000..35dc9dc Binary files /dev/null and b/src/Resources/Icons/CloneStampToolIcon.png differ diff --git a/src/Resources/Icons/CloudsEffect.png b/src/Resources/Icons/CloudsEffect.png new file mode 100644 index 0000000..77c2890 Binary files /dev/null and b/src/Resources/Icons/CloudsEffect.png differ diff --git a/src/Resources/Icons/ColorAddOverlay.png b/src/Resources/Icons/ColorAddOverlay.png new file mode 100644 index 0000000..6aa3ffc Binary files /dev/null and b/src/Resources/Icons/ColorAddOverlay.png differ diff --git a/src/Resources/Icons/ColorPalettes.png b/src/Resources/Icons/ColorPalettes.png new file mode 100644 index 0000000..1ec8156 Binary files /dev/null and b/src/Resources/Icons/ColorPalettes.png differ diff --git a/src/Resources/Icons/ColorPickerToolIcon.png b/src/Resources/Icons/ColorPickerToolIcon.png new file mode 100644 index 0000000..4854005 Binary files /dev/null and b/src/Resources/Icons/ColorPickerToolIcon.png differ diff --git a/src/Resources/Icons/ConicalGradientIcon.png b/src/Resources/Icons/ConicalGradientIcon.png new file mode 100644 index 0000000..4fc6898 Binary files /dev/null and b/src/Resources/Icons/ConicalGradientIcon.png differ diff --git a/src/Resources/Icons/CursorXYIcon.png b/src/Resources/Icons/CursorXYIcon.png new file mode 100644 index 0000000..98fde42 Binary files /dev/null and b/src/Resources/Icons/CursorXYIcon.png differ diff --git a/src/Resources/Icons/CurvesEffect.png b/src/Resources/Icons/CurvesEffect.png new file mode 100644 index 0000000..01e933a Binary files /dev/null and b/src/Resources/Icons/CurvesEffect.png differ diff --git a/src/Resources/Icons/DentsEffectIcon.png b/src/Resources/Icons/DentsEffectIcon.png new file mode 100644 index 0000000..4115b80 Binary files /dev/null and b/src/Resources/Icons/DentsEffectIcon.png differ diff --git a/src/Resources/Icons/DesaturateEffect.png b/src/Resources/Icons/DesaturateEffect.png new file mode 100644 index 0000000..899c878 Binary files /dev/null and b/src/Resources/Icons/DesaturateEffect.png differ diff --git a/src/Resources/Icons/DragDrop.OpenOrImport.FormIcon.png b/src/Resources/Icons/DragDrop.OpenOrImport.FormIcon.png new file mode 100644 index 0000000..cf7b062 Binary files /dev/null and b/src/Resources/Icons/DragDrop.OpenOrImport.FormIcon.png differ diff --git a/src/Resources/Icons/EdgeDetectEffect.png b/src/Resources/Icons/EdgeDetectEffect.png new file mode 100644 index 0000000..5492e2f Binary files /dev/null and b/src/Resources/Icons/EdgeDetectEffect.png differ diff --git a/src/Resources/Icons/EllipseSelectToolIcon.png b/src/Resources/Icons/EllipseSelectToolIcon.png new file mode 100644 index 0000000..298ab97 Binary files /dev/null and b/src/Resources/Icons/EllipseSelectToolIcon.png differ diff --git a/src/Resources/Icons/EllipseToolIcon.png b/src/Resources/Icons/EllipseToolIcon.png new file mode 100644 index 0000000..39e51d8 Binary files /dev/null and b/src/Resources/Icons/EllipseToolIcon.png differ diff --git a/src/Resources/Icons/EmbossEffect.png b/src/Resources/Icons/EmbossEffect.png new file mode 100644 index 0000000..7c4fbc2 Binary files /dev/null and b/src/Resources/Icons/EmbossEffect.png differ diff --git a/src/Resources/Icons/EraserToolIcon.png b/src/Resources/Icons/EraserToolIcon.png new file mode 100644 index 0000000..b23af35 Binary files /dev/null and b/src/Resources/Icons/EraserToolIcon.png differ diff --git a/src/Resources/Icons/ExpandCanvasQuestion.NoTB.Image.png b/src/Resources/Icons/ExpandCanvasQuestion.NoTB.Image.png new file mode 100644 index 0000000..0fc209a Binary files /dev/null and b/src/Resources/Icons/ExpandCanvasQuestion.NoTB.Image.png differ diff --git a/src/Resources/Icons/ExpandCanvasQuestion.YesTB.Image.png b/src/Resources/Icons/ExpandCanvasQuestion.YesTB.Image.png new file mode 100644 index 0000000..d8b4a23 Binary files /dev/null and b/src/Resources/Icons/ExpandCanvasQuestion.YesTB.Image.png differ diff --git a/src/Resources/Icons/FontBoldIcon.png b/src/Resources/Icons/FontBoldIcon.png new file mode 100644 index 0000000..bcf86cc Binary files /dev/null and b/src/Resources/Icons/FontBoldIcon.png differ diff --git a/src/Resources/Icons/FontItalicIcon.png b/src/Resources/Icons/FontItalicIcon.png new file mode 100644 index 0000000..7b556c0 Binary files /dev/null and b/src/Resources/Icons/FontItalicIcon.png differ diff --git a/src/Resources/Icons/FontUnderlineIcon.png b/src/Resources/Icons/FontUnderlineIcon.png new file mode 100644 index 0000000..f3d1ba8 Binary files /dev/null and b/src/Resources/Icons/FontUnderlineIcon.png differ diff --git a/src/Resources/Icons/FragmentEffectIcon.png b/src/Resources/Icons/FragmentEffectIcon.png new file mode 100644 index 0000000..bf1bd4a Binary files /dev/null and b/src/Resources/Icons/FragmentEffectIcon.png differ diff --git a/src/Resources/Icons/FreeformShapeToolIcon.png b/src/Resources/Icons/FreeformShapeToolIcon.png new file mode 100644 index 0000000..e806712 Binary files /dev/null and b/src/Resources/Icons/FreeformShapeToolIcon.png differ diff --git a/src/Resources/Icons/FrostedGlassEffect.png b/src/Resources/Icons/FrostedGlassEffect.png new file mode 100644 index 0000000..34eb3d3 Binary files /dev/null and b/src/Resources/Icons/FrostedGlassEffect.png differ diff --git a/src/Resources/Icons/GlowEffect.png b/src/Resources/Icons/GlowEffect.png new file mode 100644 index 0000000..6af0773 Binary files /dev/null and b/src/Resources/Icons/GlowEffect.png differ diff --git a/src/Resources/Icons/GradientToolIcon.png b/src/Resources/Icons/GradientToolIcon.png new file mode 100644 index 0000000..0921b4d Binary files /dev/null and b/src/Resources/Icons/GradientToolIcon.png differ diff --git a/src/Resources/Icons/HistoryFastForwardIcon.png b/src/Resources/Icons/HistoryFastForwardIcon.png new file mode 100644 index 0000000..ae1b535 Binary files /dev/null and b/src/Resources/Icons/HistoryFastForwardIcon.png differ diff --git a/src/Resources/Icons/HistoryRewindIcon.png b/src/Resources/Icons/HistoryRewindIcon.png new file mode 100644 index 0000000..f399933 Binary files /dev/null and b/src/Resources/Icons/HistoryRewindIcon.png differ diff --git a/src/Resources/Icons/HueAndSaturationAdjustment.png b/src/Resources/Icons/HueAndSaturationAdjustment.png new file mode 100644 index 0000000..1978b8e Binary files /dev/null and b/src/Resources/Icons/HueAndSaturationAdjustment.png differ diff --git a/src/Resources/Icons/ImageFromDiskIcon.png b/src/Resources/Icons/ImageFromDiskIcon.png new file mode 100644 index 0000000..775788a Binary files /dev/null and b/src/Resources/Icons/ImageFromDiskIcon.png differ diff --git a/src/Resources/Icons/ImageSizeIcon.png b/src/Resources/Icons/ImageSizeIcon.png new file mode 100644 index 0000000..d6559bc Binary files /dev/null and b/src/Resources/Icons/ImageSizeIcon.png differ diff --git a/src/Resources/Icons/InkSketchEffectIcon.png b/src/Resources/Icons/InkSketchEffectIcon.png new file mode 100644 index 0000000..49fd114 Binary files /dev/null and b/src/Resources/Icons/InkSketchEffectIcon.png differ diff --git a/src/Resources/Icons/InvertColorsEffect.png b/src/Resources/Icons/InvertColorsEffect.png new file mode 100644 index 0000000..1db6ff4 Binary files /dev/null and b/src/Resources/Icons/InvertColorsEffect.png differ diff --git a/src/Resources/Icons/JuliaFractalEffectIcon.png b/src/Resources/Icons/JuliaFractalEffectIcon.png new file mode 100644 index 0000000..7024a04 Binary files /dev/null and b/src/Resources/Icons/JuliaFractalEffectIcon.png differ diff --git a/src/Resources/Icons/LassoSelectToolIcon.png b/src/Resources/Icons/LassoSelectToolIcon.png new file mode 100644 index 0000000..af8fad5 Binary files /dev/null and b/src/Resources/Icons/LassoSelectToolIcon.png differ diff --git a/src/Resources/Icons/LevelsEffect.png b/src/Resources/Icons/LevelsEffect.png new file mode 100644 index 0000000..3319d9d Binary files /dev/null and b/src/Resources/Icons/LevelsEffect.png differ diff --git a/src/Resources/Icons/LineToolIcon.png b/src/Resources/Icons/LineToolIcon.png new file mode 100644 index 0000000..58e062e Binary files /dev/null and b/src/Resources/Icons/LineToolIcon.png differ diff --git a/src/Resources/Icons/LinearClampedGradientIcon.png b/src/Resources/Icons/LinearClampedGradientIcon.png new file mode 100644 index 0000000..9d2904f Binary files /dev/null and b/src/Resources/Icons/LinearClampedGradientIcon.png differ diff --git a/src/Resources/Icons/LinearDiamondGradientIcon.png b/src/Resources/Icons/LinearDiamondGradientIcon.png new file mode 100644 index 0000000..5518e72 Binary files /dev/null and b/src/Resources/Icons/LinearDiamondGradientIcon.png differ diff --git a/src/Resources/Icons/LinearReflectedGradientIcon.png b/src/Resources/Icons/LinearReflectedGradientIcon.png new file mode 100644 index 0000000..947d70f Binary files /dev/null and b/src/Resources/Icons/LinearReflectedGradientIcon.png differ diff --git a/src/Resources/Icons/MagicWandToolIcon.png b/src/Resources/Icons/MagicWandToolIcon.png new file mode 100644 index 0000000..983ba9f Binary files /dev/null and b/src/Resources/Icons/MagicWandToolIcon.png differ diff --git a/src/Resources/Icons/MandelbrotFractalEffectIcon.png b/src/Resources/Icons/MandelbrotFractalEffectIcon.png new file mode 100644 index 0000000..d1eaf41 Binary files /dev/null and b/src/Resources/Icons/MandelbrotFractalEffectIcon.png differ diff --git a/src/Resources/Icons/MedianEffectIcon.png b/src/Resources/Icons/MedianEffectIcon.png new file mode 100644 index 0000000..cbbf16c Binary files /dev/null and b/src/Resources/Icons/MedianEffectIcon.png differ diff --git a/src/Resources/Icons/MenuEditCopyIcon.png b/src/Resources/Icons/MenuEditCopyIcon.png new file mode 100644 index 0000000..d0c51b4 Binary files /dev/null and b/src/Resources/Icons/MenuEditCopyIcon.png differ diff --git a/src/Resources/Icons/MenuEditCutIcon.png b/src/Resources/Icons/MenuEditCutIcon.png new file mode 100644 index 0000000..0316f4f Binary files /dev/null and b/src/Resources/Icons/MenuEditCutIcon.png differ diff --git a/src/Resources/Icons/MenuEditDeselectIcon.png b/src/Resources/Icons/MenuEditDeselectIcon.png new file mode 100644 index 0000000..214156c Binary files /dev/null and b/src/Resources/Icons/MenuEditDeselectIcon.png differ diff --git a/src/Resources/Icons/MenuEditEraseSelectionIcon.png b/src/Resources/Icons/MenuEditEraseSelectionIcon.png new file mode 100644 index 0000000..6ecb28d Binary files /dev/null and b/src/Resources/Icons/MenuEditEraseSelectionIcon.png differ diff --git a/src/Resources/Icons/MenuEditFillSelectionIcon.png b/src/Resources/Icons/MenuEditFillSelectionIcon.png new file mode 100644 index 0000000..112aa28 Binary files /dev/null and b/src/Resources/Icons/MenuEditFillSelectionIcon.png differ diff --git a/src/Resources/Icons/MenuEditInvertSelectionIcon.png b/src/Resources/Icons/MenuEditInvertSelectionIcon.png new file mode 100644 index 0000000..e5c2d3d Binary files /dev/null and b/src/Resources/Icons/MenuEditInvertSelectionIcon.png differ diff --git a/src/Resources/Icons/MenuEditPasteIcon.png b/src/Resources/Icons/MenuEditPasteIcon.png new file mode 100644 index 0000000..8e8d0b0 Binary files /dev/null and b/src/Resources/Icons/MenuEditPasteIcon.png differ diff --git a/src/Resources/Icons/MenuEditPasteInToNewImageIcon.png b/src/Resources/Icons/MenuEditPasteInToNewImageIcon.png new file mode 100644 index 0000000..a553ebb Binary files /dev/null and b/src/Resources/Icons/MenuEditPasteInToNewImageIcon.png differ diff --git a/src/Resources/Icons/MenuEditPasteInToNewLayerIcon.png b/src/Resources/Icons/MenuEditPasteInToNewLayerIcon.png new file mode 100644 index 0000000..240feec Binary files /dev/null and b/src/Resources/Icons/MenuEditPasteInToNewLayerIcon.png differ diff --git a/src/Resources/Icons/MenuEditRedoIcon.png b/src/Resources/Icons/MenuEditRedoIcon.png new file mode 100644 index 0000000..41b21ac Binary files /dev/null and b/src/Resources/Icons/MenuEditRedoIcon.png differ diff --git a/src/Resources/Icons/MenuEditSelectAllIcon.png b/src/Resources/Icons/MenuEditSelectAllIcon.png new file mode 100644 index 0000000..5eb2035 Binary files /dev/null and b/src/Resources/Icons/MenuEditSelectAllIcon.png differ diff --git a/src/Resources/Icons/MenuEditUndoIcon.png b/src/Resources/Icons/MenuEditUndoIcon.png new file mode 100644 index 0000000..79425d4 Binary files /dev/null and b/src/Resources/Icons/MenuEditUndoIcon.png differ diff --git a/src/Resources/Icons/MenuFileAcquireFromClipboardIcon.png b/src/Resources/Icons/MenuFileAcquireFromClipboardIcon.png new file mode 100644 index 0000000..8e8d0b0 Binary files /dev/null and b/src/Resources/Icons/MenuFileAcquireFromClipboardIcon.png differ diff --git a/src/Resources/Icons/MenuFileAcquireFromScannerOrCameraIcon.png b/src/Resources/Icons/MenuFileAcquireFromScannerOrCameraIcon.png new file mode 100644 index 0000000..9041e3c Binary files /dev/null and b/src/Resources/Icons/MenuFileAcquireFromScannerOrCameraIcon.png differ diff --git a/src/Resources/Icons/MenuFileCloseIcon.png b/src/Resources/Icons/MenuFileCloseIcon.png new file mode 100644 index 0000000..805d1d3 Binary files /dev/null and b/src/Resources/Icons/MenuFileCloseIcon.png differ diff --git a/src/Resources/Icons/MenuFileNewIcon.png b/src/Resources/Icons/MenuFileNewIcon.png new file mode 100644 index 0000000..eb5ba8c Binary files /dev/null and b/src/Resources/Icons/MenuFileNewIcon.png differ diff --git a/src/Resources/Icons/MenuFileOpenIcon.png b/src/Resources/Icons/MenuFileOpenIcon.png new file mode 100644 index 0000000..775788a Binary files /dev/null and b/src/Resources/Icons/MenuFileOpenIcon.png differ diff --git a/src/Resources/Icons/MenuFilePrintIcon.png b/src/Resources/Icons/MenuFilePrintIcon.png new file mode 100644 index 0000000..c1a8a4d Binary files /dev/null and b/src/Resources/Icons/MenuFilePrintIcon.png differ diff --git a/src/Resources/Icons/MenuFileSaveAsIcon.png b/src/Resources/Icons/MenuFileSaveAsIcon.png new file mode 100644 index 0000000..f3cc8d3 Binary files /dev/null and b/src/Resources/Icons/MenuFileSaveAsIcon.png differ diff --git a/src/Resources/Icons/MenuFileSaveIcon.png b/src/Resources/Icons/MenuFileSaveIcon.png new file mode 100644 index 0000000..920335e Binary files /dev/null and b/src/Resources/Icons/MenuFileSaveIcon.png differ diff --git a/src/Resources/Icons/MenuFileViewPluginLoadErrorsIcon.png b/src/Resources/Icons/MenuFileViewPluginLoadErrorsIcon.png new file mode 100644 index 0000000..cff65d7 Binary files /dev/null and b/src/Resources/Icons/MenuFileViewPluginLoadErrorsIcon.png differ diff --git a/src/Resources/Icons/MenuHelpAboutIcon.png b/src/Resources/Icons/MenuHelpAboutIcon.png new file mode 100644 index 0000000..34cd2af Binary files /dev/null and b/src/Resources/Icons/MenuHelpAboutIcon.png differ diff --git a/src/Resources/Icons/MenuHelpCheckForUpdatesIcon.png b/src/Resources/Icons/MenuHelpCheckForUpdatesIcon.png new file mode 100644 index 0000000..7b1142f Binary files /dev/null and b/src/Resources/Icons/MenuHelpCheckForUpdatesIcon.png differ diff --git a/src/Resources/Icons/MenuHelpDonateIcon.png b/src/Resources/Icons/MenuHelpDonateIcon.png new file mode 100644 index 0000000..c9916f2 Binary files /dev/null and b/src/Resources/Icons/MenuHelpDonateIcon.png differ diff --git a/src/Resources/Icons/MenuHelpForumIcon.png b/src/Resources/Icons/MenuHelpForumIcon.png new file mode 100644 index 0000000..39433cf Binary files /dev/null and b/src/Resources/Icons/MenuHelpForumIcon.png differ diff --git a/src/Resources/Icons/MenuHelpHelpTopicsIcon.png b/src/Resources/Icons/MenuHelpHelpTopicsIcon.png new file mode 100644 index 0000000..1dc0797 Binary files /dev/null and b/src/Resources/Icons/MenuHelpHelpTopicsIcon.png differ diff --git a/src/Resources/Icons/MenuHelpLanguageIcon.png b/src/Resources/Icons/MenuHelpLanguageIcon.png new file mode 100644 index 0000000..38380d8 Binary files /dev/null and b/src/Resources/Icons/MenuHelpLanguageIcon.png differ diff --git a/src/Resources/Icons/MenuHelpPdnSearchIcon.png b/src/Resources/Icons/MenuHelpPdnSearchIcon.png new file mode 100644 index 0000000..cf3d97f Binary files /dev/null and b/src/Resources/Icons/MenuHelpPdnSearchIcon.png differ diff --git a/src/Resources/Icons/MenuHelpPdnWebsiteIcon.png b/src/Resources/Icons/MenuHelpPdnWebsiteIcon.png new file mode 100644 index 0000000..aee9c97 Binary files /dev/null and b/src/Resources/Icons/MenuHelpPdnWebsiteIcon.png differ diff --git a/src/Resources/Icons/MenuHelpPluginsIcon.png b/src/Resources/Icons/MenuHelpPluginsIcon.png new file mode 100644 index 0000000..6187b15 Binary files /dev/null and b/src/Resources/Icons/MenuHelpPluginsIcon.png differ diff --git a/src/Resources/Icons/MenuHelpSendFeedbackIcon.png b/src/Resources/Icons/MenuHelpSendFeedbackIcon.png new file mode 100644 index 0000000..244f04a Binary files /dev/null and b/src/Resources/Icons/MenuHelpSendFeedbackIcon.png differ diff --git a/src/Resources/Icons/MenuHelpTutorialsIcon.png b/src/Resources/Icons/MenuHelpTutorialsIcon.png new file mode 100644 index 0000000..7d863f9 Binary files /dev/null and b/src/Resources/Icons/MenuHelpTutorialsIcon.png differ diff --git a/src/Resources/Icons/MenuImageCanvasSizeIcon.png b/src/Resources/Icons/MenuImageCanvasSizeIcon.png new file mode 100644 index 0000000..d623f20 Binary files /dev/null and b/src/Resources/Icons/MenuImageCanvasSizeIcon.png differ diff --git a/src/Resources/Icons/MenuImageCropIcon.png b/src/Resources/Icons/MenuImageCropIcon.png new file mode 100644 index 0000000..0fc209a Binary files /dev/null and b/src/Resources/Icons/MenuImageCropIcon.png differ diff --git a/src/Resources/Icons/MenuImageFlattenIcon.png b/src/Resources/Icons/MenuImageFlattenIcon.png new file mode 100644 index 0000000..36d8c2a Binary files /dev/null and b/src/Resources/Icons/MenuImageFlattenIcon.png differ diff --git a/src/Resources/Icons/MenuImageFlipHorizontalIcon.png b/src/Resources/Icons/MenuImageFlipHorizontalIcon.png new file mode 100644 index 0000000..8e6f29f Binary files /dev/null and b/src/Resources/Icons/MenuImageFlipHorizontalIcon.png differ diff --git a/src/Resources/Icons/MenuImageFlipVerticalIcon.png b/src/Resources/Icons/MenuImageFlipVerticalIcon.png new file mode 100644 index 0000000..deb0897 Binary files /dev/null and b/src/Resources/Icons/MenuImageFlipVerticalIcon.png differ diff --git a/src/Resources/Icons/MenuImageResizeIcon.png b/src/Resources/Icons/MenuImageResizeIcon.png new file mode 100644 index 0000000..7332e29 Binary files /dev/null and b/src/Resources/Icons/MenuImageResizeIcon.png differ diff --git a/src/Resources/Icons/MenuImageRotate180Icon.png b/src/Resources/Icons/MenuImageRotate180Icon.png new file mode 100644 index 0000000..db832c5 Binary files /dev/null and b/src/Resources/Icons/MenuImageRotate180Icon.png differ diff --git a/src/Resources/Icons/MenuImageRotate90CCWIcon.png b/src/Resources/Icons/MenuImageRotate90CCWIcon.png new file mode 100644 index 0000000..db832c5 Binary files /dev/null and b/src/Resources/Icons/MenuImageRotate90CCWIcon.png differ diff --git a/src/Resources/Icons/MenuImageRotate90CWIcon.png b/src/Resources/Icons/MenuImageRotate90CWIcon.png new file mode 100644 index 0000000..81c85e9 Binary files /dev/null and b/src/Resources/Icons/MenuImageRotate90CWIcon.png differ diff --git a/src/Resources/Icons/MenuLayersAddNewLayerIcon.png b/src/Resources/Icons/MenuLayersAddNewLayerIcon.png new file mode 100644 index 0000000..0f935f2 Binary files /dev/null and b/src/Resources/Icons/MenuLayersAddNewLayerIcon.png differ diff --git a/src/Resources/Icons/MenuLayersDeleteLayerIcon.png b/src/Resources/Icons/MenuLayersDeleteLayerIcon.png new file mode 100644 index 0000000..6ecb28d Binary files /dev/null and b/src/Resources/Icons/MenuLayersDeleteLayerIcon.png differ diff --git a/src/Resources/Icons/MenuLayersDuplicateLayerIcon.png b/src/Resources/Icons/MenuLayersDuplicateLayerIcon.png new file mode 100644 index 0000000..3a9211d Binary files /dev/null and b/src/Resources/Icons/MenuLayersDuplicateLayerIcon.png differ diff --git a/src/Resources/Icons/MenuLayersFlipHorizontalIcon.png b/src/Resources/Icons/MenuLayersFlipHorizontalIcon.png new file mode 100644 index 0000000..8e6f29f Binary files /dev/null and b/src/Resources/Icons/MenuLayersFlipHorizontalIcon.png differ diff --git a/src/Resources/Icons/MenuLayersFlipVerticalIcon.png b/src/Resources/Icons/MenuLayersFlipVerticalIcon.png new file mode 100644 index 0000000..deb0897 Binary files /dev/null and b/src/Resources/Icons/MenuLayersFlipVerticalIcon.png differ diff --git a/src/Resources/Icons/MenuLayersImportFromFileIcon.png b/src/Resources/Icons/MenuLayersImportFromFileIcon.png new file mode 100644 index 0000000..b63c9c2 Binary files /dev/null and b/src/Resources/Icons/MenuLayersImportFromFileIcon.png differ diff --git a/src/Resources/Icons/MenuLayersLayerPropertiesIcon.png b/src/Resources/Icons/MenuLayersLayerPropertiesIcon.png new file mode 100644 index 0000000..009cda3 Binary files /dev/null and b/src/Resources/Icons/MenuLayersLayerPropertiesIcon.png differ diff --git a/src/Resources/Icons/MenuLayersMergeLayerDownIcon.png b/src/Resources/Icons/MenuLayersMergeLayerDownIcon.png new file mode 100644 index 0000000..90fb35f Binary files /dev/null and b/src/Resources/Icons/MenuLayersMergeLayerDownIcon.png differ diff --git a/src/Resources/Icons/MenuLayersMoveLayerDownIcon.png b/src/Resources/Icons/MenuLayersMoveLayerDownIcon.png new file mode 100644 index 0000000..e42c7b6 Binary files /dev/null and b/src/Resources/Icons/MenuLayersMoveLayerDownIcon.png differ diff --git a/src/Resources/Icons/MenuLayersMoveLayerUpIcon.png b/src/Resources/Icons/MenuLayersMoveLayerUpIcon.png new file mode 100644 index 0000000..cbdd258 Binary files /dev/null and b/src/Resources/Icons/MenuLayersMoveLayerUpIcon.png differ diff --git a/src/Resources/Icons/MenuViewActualSizeIcon.png b/src/Resources/Icons/MenuViewActualSizeIcon.png new file mode 100644 index 0000000..84f79ee Binary files /dev/null and b/src/Resources/Icons/MenuViewActualSizeIcon.png differ diff --git a/src/Resources/Icons/MenuViewGridIcon.png b/src/Resources/Icons/MenuViewGridIcon.png new file mode 100644 index 0000000..c83f9e6 Binary files /dev/null and b/src/Resources/Icons/MenuViewGridIcon.png differ diff --git a/src/Resources/Icons/MenuViewRulersIcon.png b/src/Resources/Icons/MenuViewRulersIcon.png new file mode 100644 index 0000000..054c63b Binary files /dev/null and b/src/Resources/Icons/MenuViewRulersIcon.png differ diff --git a/src/Resources/Icons/MenuViewZoomInIcon.png b/src/Resources/Icons/MenuViewZoomInIcon.png new file mode 100644 index 0000000..fbf52d3 Binary files /dev/null and b/src/Resources/Icons/MenuViewZoomInIcon.png differ diff --git a/src/Resources/Icons/MenuViewZoomOutIcon.png b/src/Resources/Icons/MenuViewZoomOutIcon.png new file mode 100644 index 0000000..0c2a6a7 Binary files /dev/null and b/src/Resources/Icons/MenuViewZoomOutIcon.png differ diff --git a/src/Resources/Icons/MenuViewZoomToSelectionIcon.png b/src/Resources/Icons/MenuViewZoomToSelectionIcon.png new file mode 100644 index 0000000..d52fd5b Binary files /dev/null and b/src/Resources/Icons/MenuViewZoomToSelectionIcon.png differ diff --git a/src/Resources/Icons/MenuViewZoomToWindowIcon.png b/src/Resources/Icons/MenuViewZoomToWindowIcon.png new file mode 100644 index 0000000..b502d41 Binary files /dev/null and b/src/Resources/Icons/MenuViewZoomToWindowIcon.png differ diff --git a/src/Resources/Icons/MenuWindowColorsIcon.png b/src/Resources/Icons/MenuWindowColorsIcon.png new file mode 100644 index 0000000..6afb445 Binary files /dev/null and b/src/Resources/Icons/MenuWindowColorsIcon.png differ diff --git a/src/Resources/Icons/MenuWindowHistoryIcon.png b/src/Resources/Icons/MenuWindowHistoryIcon.png new file mode 100644 index 0000000..911da3f Binary files /dev/null and b/src/Resources/Icons/MenuWindowHistoryIcon.png differ diff --git a/src/Resources/Icons/MenuWindowLayersIcon.png b/src/Resources/Icons/MenuWindowLayersIcon.png new file mode 100644 index 0000000..86732f8 Binary files /dev/null and b/src/Resources/Icons/MenuWindowLayersIcon.png differ diff --git a/src/Resources/Icons/MenuWindowOpenMdiListIcon.png b/src/Resources/Icons/MenuWindowOpenMdiListIcon.png new file mode 100644 index 0000000..c226ff2 Binary files /dev/null and b/src/Resources/Icons/MenuWindowOpenMdiListIcon.png differ diff --git a/src/Resources/Icons/MenuWindowResetWindowLocationsIcon.png b/src/Resources/Icons/MenuWindowResetWindowLocationsIcon.png new file mode 100644 index 0000000..4f305e1 Binary files /dev/null and b/src/Resources/Icons/MenuWindowResetWindowLocationsIcon.png differ diff --git a/src/Resources/Icons/MenuWindowToolsIcon.png b/src/Resources/Icons/MenuWindowToolsIcon.png new file mode 100644 index 0000000..a6afc41 Binary files /dev/null and b/src/Resources/Icons/MenuWindowToolsIcon.png differ diff --git a/src/Resources/Icons/MinusButtonIcon.png b/src/Resources/Icons/MinusButtonIcon.png new file mode 100644 index 0000000..8ef80c8 Binary files /dev/null and b/src/Resources/Icons/MinusButtonIcon.png differ diff --git a/src/Resources/Icons/MotionBlurEffect.png b/src/Resources/Icons/MotionBlurEffect.png new file mode 100644 index 0000000..3d15b3b Binary files /dev/null and b/src/Resources/Icons/MotionBlurEffect.png differ diff --git a/src/Resources/Icons/MoveSelectionToolIcon.png b/src/Resources/Icons/MoveSelectionToolIcon.png new file mode 100644 index 0000000..3265289 Binary files /dev/null and b/src/Resources/Icons/MoveSelectionToolIcon.png differ diff --git a/src/Resources/Icons/MoveToolIcon.png b/src/Resources/Icons/MoveToolIcon.png new file mode 100644 index 0000000..03e879a Binary files /dev/null and b/src/Resources/Icons/MoveToolIcon.png differ diff --git a/src/Resources/Icons/OilPaintingEffect.png b/src/Resources/Icons/OilPaintingEffect.png new file mode 100644 index 0000000..fe1afc3 Binary files /dev/null and b/src/Resources/Icons/OilPaintingEffect.png differ diff --git a/src/Resources/Icons/OutlineEffectIcon.png b/src/Resources/Icons/OutlineEffectIcon.png new file mode 100644 index 0000000..f94a13b Binary files /dev/null and b/src/Resources/Icons/OutlineEffectIcon.png differ diff --git a/src/Resources/Icons/PaintBrushToolIcon.png b/src/Resources/Icons/PaintBrushToolIcon.png new file mode 100644 index 0000000..cfe0349 Binary files /dev/null and b/src/Resources/Icons/PaintBrushToolIcon.png differ diff --git a/src/Resources/Icons/PaintBucketIcon.png b/src/Resources/Icons/PaintBucketIcon.png new file mode 100644 index 0000000..a309f36 Binary files /dev/null and b/src/Resources/Icons/PaintBucketIcon.png differ diff --git a/src/Resources/Icons/PaintDotNet.ico b/src/Resources/Icons/PaintDotNet.ico new file mode 100644 index 0000000..776e34d Binary files /dev/null and b/src/Resources/Icons/PaintDotNet.ico differ diff --git a/src/Resources/Icons/PanToolIcon.png b/src/Resources/Icons/PanToolIcon.png new file mode 100644 index 0000000..d32776d Binary files /dev/null and b/src/Resources/Icons/PanToolIcon.png differ diff --git a/src/Resources/Icons/PencilSketchEffectIcon.png b/src/Resources/Icons/PencilSketchEffectIcon.png new file mode 100644 index 0000000..6db5297 Binary files /dev/null and b/src/Resources/Icons/PencilSketchEffectIcon.png differ diff --git a/src/Resources/Icons/PencilToolIcon.png b/src/Resources/Icons/PencilToolIcon.png new file mode 100644 index 0000000..35f4907 Binary files /dev/null and b/src/Resources/Icons/PencilToolIcon.png differ diff --git a/src/Resources/Icons/PixelateEffect.png b/src/Resources/Icons/PixelateEffect.png new file mode 100644 index 0000000..5becdcc Binary files /dev/null and b/src/Resources/Icons/PixelateEffect.png differ diff --git a/src/Resources/Icons/PlusButtonIcon.png b/src/Resources/Icons/PlusButtonIcon.png new file mode 100644 index 0000000..484b169 Binary files /dev/null and b/src/Resources/Icons/PlusButtonIcon.png differ diff --git a/src/Resources/Icons/PolarInversionEffect.png b/src/Resources/Icons/PolarInversionEffect.png new file mode 100644 index 0000000..505492c Binary files /dev/null and b/src/Resources/Icons/PolarInversionEffect.png differ diff --git a/src/Resources/Icons/PosterizeEffectIcon.png b/src/Resources/Icons/PosterizeEffectIcon.png new file mode 100644 index 0000000..953cf94 Binary files /dev/null and b/src/Resources/Icons/PosterizeEffectIcon.png differ diff --git a/src/Resources/Icons/RadialBlurEffect.png b/src/Resources/Icons/RadialBlurEffect.png new file mode 100644 index 0000000..6be9fb4 Binary files /dev/null and b/src/Resources/Icons/RadialBlurEffect.png differ diff --git a/src/Resources/Icons/RadialGradientIcon.png b/src/Resources/Icons/RadialGradientIcon.png new file mode 100644 index 0000000..45a8333 Binary files /dev/null and b/src/Resources/Icons/RadialGradientIcon.png differ diff --git a/src/Resources/Icons/RecoloringToolIcon.png b/src/Resources/Icons/RecoloringToolIcon.png new file mode 100644 index 0000000..3251f84 Binary files /dev/null and b/src/Resources/Icons/RecoloringToolIcon.png differ diff --git a/src/Resources/Icons/RectangleSelectToolIcon.png b/src/Resources/Icons/RectangleSelectToolIcon.png new file mode 100644 index 0000000..fe3fef0 Binary files /dev/null and b/src/Resources/Icons/RectangleSelectToolIcon.png differ diff --git a/src/Resources/Icons/RectangleToolIcon.png b/src/Resources/Icons/RectangleToolIcon.png new file mode 100644 index 0000000..304b48e Binary files /dev/null and b/src/Resources/Icons/RectangleToolIcon.png differ diff --git a/src/Resources/Icons/RedEyeRemoveEffect.png b/src/Resources/Icons/RedEyeRemoveEffect.png new file mode 100644 index 0000000..0aee660 Binary files /dev/null and b/src/Resources/Icons/RedEyeRemoveEffect.png differ diff --git a/src/Resources/Icons/ReduceNoiseEffectIcon.png b/src/Resources/Icons/ReduceNoiseEffectIcon.png new file mode 100644 index 0000000..59afa87 Binary files /dev/null and b/src/Resources/Icons/ReduceNoiseEffectIcon.png differ diff --git a/src/Resources/Icons/ReliefEffect.png b/src/Resources/Icons/ReliefEffect.png new file mode 100644 index 0000000..10d9ed7 Binary files /dev/null and b/src/Resources/Icons/ReliefEffect.png differ diff --git a/src/Resources/Icons/ResetIcon.png b/src/Resources/Icons/ResetIcon.png new file mode 100644 index 0000000..4f305e1 Binary files /dev/null and b/src/Resources/Icons/ResetIcon.png differ diff --git a/src/Resources/Icons/RightArrowBlue.png b/src/Resources/Icons/RightArrowBlue.png new file mode 100644 index 0000000..9c5622f Binary files /dev/null and b/src/Resources/Icons/RightArrowBlue.png differ diff --git a/src/Resources/Icons/RotateZoomIcon.png b/src/Resources/Icons/RotateZoomIcon.png new file mode 100644 index 0000000..174765a Binary files /dev/null and b/src/Resources/Icons/RotateZoomIcon.png differ diff --git a/src/Resources/Icons/RoundedRectangleToolIcon.png b/src/Resources/Icons/RoundedRectangleToolIcon.png new file mode 100644 index 0000000..5664495 Binary files /dev/null and b/src/Resources/Icons/RoundedRectangleToolIcon.png differ diff --git a/src/Resources/Icons/SavePaletteIcon.png b/src/Resources/Icons/SavePaletteIcon.png new file mode 100644 index 0000000..2ba8e5a Binary files /dev/null and b/src/Resources/Icons/SavePaletteIcon.png differ diff --git a/src/Resources/Icons/SelectionIcon.png b/src/Resources/Icons/SelectionIcon.png new file mode 100644 index 0000000..466a9c5 Binary files /dev/null and b/src/Resources/Icons/SelectionIcon.png differ diff --git a/src/Resources/Icons/SepiaEffect.png b/src/Resources/Icons/SepiaEffect.png new file mode 100644 index 0000000..39fb208 Binary files /dev/null and b/src/Resources/Icons/SepiaEffect.png differ diff --git a/src/Resources/Icons/SettingsIcon.png b/src/Resources/Icons/SettingsIcon.png new file mode 100644 index 0000000..df1f8ee Binary files /dev/null and b/src/Resources/Icons/SettingsIcon.png differ diff --git a/src/Resources/Icons/ShapeBothIcon.png b/src/Resources/Icons/ShapeBothIcon.png new file mode 100644 index 0000000..af8cec7 Binary files /dev/null and b/src/Resources/Icons/ShapeBothIcon.png differ diff --git a/src/Resources/Icons/ShapeInteriorIcon.png b/src/Resources/Icons/ShapeInteriorIcon.png new file mode 100644 index 0000000..9f82158 Binary files /dev/null and b/src/Resources/Icons/ShapeInteriorIcon.png differ diff --git a/src/Resources/Icons/ShapeOutlineIcon.png b/src/Resources/Icons/ShapeOutlineIcon.png new file mode 100644 index 0000000..f4c783c Binary files /dev/null and b/src/Resources/Icons/ShapeOutlineIcon.png differ diff --git a/src/Resources/Icons/SharpenEffect.png b/src/Resources/Icons/SharpenEffect.png new file mode 100644 index 0000000..0c33638 Binary files /dev/null and b/src/Resources/Icons/SharpenEffect.png differ diff --git a/src/Resources/Icons/SoftenPortraitEffectIcon.png b/src/Resources/Icons/SoftenPortraitEffectIcon.png new file mode 100644 index 0000000..d62d6d9 Binary files /dev/null and b/src/Resources/Icons/SoftenPortraitEffectIcon.png differ diff --git a/src/Resources/Icons/SurfaceBlurEffectIcon.png b/src/Resources/Icons/SurfaceBlurEffectIcon.png new file mode 100644 index 0000000..d171084 Binary files /dev/null and b/src/Resources/Icons/SurfaceBlurEffectIcon.png differ diff --git a/src/Resources/Icons/SwapIcon.png b/src/Resources/Icons/SwapIcon.png new file mode 100644 index 0000000..66b2f4e Binary files /dev/null and b/src/Resources/Icons/SwapIcon.png differ diff --git a/src/Resources/Icons/SwatchIcon.png b/src/Resources/Icons/SwatchIcon.png new file mode 100644 index 0000000..f534b01 Binary files /dev/null and b/src/Resources/Icons/SwatchIcon.png differ diff --git a/src/Resources/Icons/TextAlignCenterIcon.png b/src/Resources/Icons/TextAlignCenterIcon.png new file mode 100644 index 0000000..3624143 Binary files /dev/null and b/src/Resources/Icons/TextAlignCenterIcon.png differ diff --git a/src/Resources/Icons/TextAlignLeftIcon.png b/src/Resources/Icons/TextAlignLeftIcon.png new file mode 100644 index 0000000..7e1b0c1 Binary files /dev/null and b/src/Resources/Icons/TextAlignLeftIcon.png differ diff --git a/src/Resources/Icons/TextAlignRightIcon.png b/src/Resources/Icons/TextAlignRightIcon.png new file mode 100644 index 0000000..6de8bc3 Binary files /dev/null and b/src/Resources/Icons/TextAlignRightIcon.png differ diff --git a/src/Resources/Icons/TextToolIcon.png b/src/Resources/Icons/TextToolIcon.png new file mode 100644 index 0000000..0f0bb27 Binary files /dev/null and b/src/Resources/Icons/TextToolIcon.png differ diff --git a/src/Resources/Icons/TileEffect.png b/src/Resources/Icons/TileEffect.png new file mode 100644 index 0000000..2ac7208 Binary files /dev/null and b/src/Resources/Icons/TileEffect.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.FloodMode.Global.png b/src/Resources/Icons/ToolConfigStrip.FloodMode.Global.png new file mode 100644 index 0000000..d9207a5 Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.FloodMode.Global.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.FloodMode.Local.png b/src/Resources/Icons/ToolConfigStrip.FloodMode.Local.png new file mode 100644 index 0000000..25c327b Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.FloodMode.Local.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Exclude.png b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Exclude.png new file mode 100644 index 0000000..1a63d9c Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Exclude.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Intersect.png b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Intersect.png new file mode 100644 index 0000000..c6ec16c Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Intersect.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Replace.png b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Replace.png new file mode 100644 index 0000000..c98b76a Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Replace.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Union.png b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Union.png new file mode 100644 index 0000000..2c811ff Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Union.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Xor.png b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Xor.png new file mode 100644 index 0000000..a79296f Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionCombineMode.Xor.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.FixedRatio.png b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.FixedRatio.png new file mode 100644 index 0000000..2ea5508 Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.FixedRatio.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.FixedSize.png b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.FixedSize.png new file mode 100644 index 0000000..0c413e8 Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.FixedSize.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.Normal.png b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.Normal.png new file mode 100644 index 0000000..a027027 Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSplitButton.Normal.png differ diff --git a/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSwapButton.png b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSwapButton.png new file mode 100644 index 0000000..8e6f29f Binary files /dev/null and b/src/Resources/Icons/ToolConfigStrip.SelectionDrawModeSwapButton.png differ diff --git a/src/Resources/Icons/TwistEffect.png b/src/Resources/Icons/TwistEffect.png new file mode 100644 index 0000000..503e362 Binary files /dev/null and b/src/Resources/Icons/TwistEffect.png differ diff --git a/src/Resources/Icons/UnfocusEffectIcon.png b/src/Resources/Icons/UnfocusEffectIcon.png new file mode 100644 index 0000000..3846d74 Binary files /dev/null and b/src/Resources/Icons/UnfocusEffectIcon.png differ diff --git a/src/Resources/Icons/UnsavedChangesDialog.CancelButton.png b/src/Resources/Icons/UnsavedChangesDialog.CancelButton.png new file mode 100644 index 0000000..6ecb28d Binary files /dev/null and b/src/Resources/Icons/UnsavedChangesDialog.CancelButton.png differ diff --git a/src/Resources/Icons/UnsavedChangesDialog.SaveButton.png b/src/Resources/Icons/UnsavedChangesDialog.SaveButton.png new file mode 100644 index 0000000..0d0f357 Binary files /dev/null and b/src/Resources/Icons/UnsavedChangesDialog.SaveButton.png differ diff --git a/src/Resources/Icons/VignetteEffectIcon.png b/src/Resources/Icons/VignetteEffectIcon.png new file mode 100644 index 0000000..e7e70ae Binary files /dev/null and b/src/Resources/Icons/VignetteEffectIcon.png differ diff --git a/src/Resources/Icons/WarningIcon.png b/src/Resources/Icons/WarningIcon.png new file mode 100644 index 0000000..4301d62 Binary files /dev/null and b/src/Resources/Icons/WarningIcon.png differ diff --git a/src/Resources/Icons/ZoomBlurEffect.png b/src/Resources/Icons/ZoomBlurEffect.png new file mode 100644 index 0000000..8bf4186 Binary files /dev/null and b/src/Resources/Icons/ZoomBlurEffect.png differ diff --git a/src/Resources/Icons/ZoomToolIcon.png b/src/Resources/Icons/ZoomToolIcon.png new file mode 100644 index 0000000..ba1fa8d Binary files /dev/null and b/src/Resources/Icons/ZoomToolIcon.png differ diff --git a/src/Resources/Images/AnchorChooserControl.AnchorImage.png b/src/Resources/Images/AnchorChooserControl.AnchorImage.png new file mode 100644 index 0000000..7fa98cd Binary files /dev/null and b/src/Resources/Images/AnchorChooserControl.AnchorImage.png differ diff --git a/src/Resources/Images/Banner.png b/src/Resources/Images/Banner.png new file mode 100644 index 0000000..df6a442 Binary files /dev/null and b/src/Resources/Images/Banner.png differ diff --git a/src/Resources/Images/DashStyleButton.Dash.png b/src/Resources/Images/DashStyleButton.Dash.png new file mode 100644 index 0000000..7bea08f Binary files /dev/null and b/src/Resources/Images/DashStyleButton.Dash.png differ diff --git a/src/Resources/Images/DashStyleButton.DashDot.png b/src/Resources/Images/DashStyleButton.DashDot.png new file mode 100644 index 0000000..8064a20 Binary files /dev/null and b/src/Resources/Images/DashStyleButton.DashDot.png differ diff --git a/src/Resources/Images/DashStyleButton.DashDotDot.png b/src/Resources/Images/DashStyleButton.DashDotDot.png new file mode 100644 index 0000000..aeed661 Binary files /dev/null and b/src/Resources/Images/DashStyleButton.DashDotDot.png differ diff --git a/src/Resources/Images/DashStyleButton.Dot.png b/src/Resources/Images/DashStyleButton.Dot.png new file mode 100644 index 0000000..8798451 Binary files /dev/null and b/src/Resources/Images/DashStyleButton.Dot.png differ diff --git a/src/Resources/Images/DashStyleButton.Solid.png b/src/Resources/Images/DashStyleButton.Solid.png new file mode 100644 index 0000000..0ad2c20 Binary files /dev/null and b/src/Resources/Images/DashStyleButton.Solid.png differ diff --git a/src/Resources/Images/Icon50x50.png b/src/Resources/Images/Icon50x50.png new file mode 100644 index 0000000..e235719 Binary files /dev/null and b/src/Resources/Images/Icon50x50.png differ diff --git a/src/Resources/Images/ImageStrip.CloseButton.Disabled.png b/src/Resources/Images/ImageStrip.CloseButton.Disabled.png new file mode 100644 index 0000000..6ecb28d Binary files /dev/null and b/src/Resources/Images/ImageStrip.CloseButton.Disabled.png differ diff --git a/src/Resources/Images/ImageStrip.CloseButton.Hot.png b/src/Resources/Images/ImageStrip.CloseButton.Hot.png new file mode 100644 index 0000000..2c7d8fd Binary files /dev/null and b/src/Resources/Images/ImageStrip.CloseButton.Hot.png differ diff --git a/src/Resources/Images/ImageStrip.CloseButton.Normal.png b/src/Resources/Images/ImageStrip.CloseButton.Normal.png new file mode 100644 index 0000000..c9899bc Binary files /dev/null and b/src/Resources/Images/ImageStrip.CloseButton.Normal.png differ diff --git a/src/Resources/Images/ImageStrip.CloseButton.Pressed.png b/src/Resources/Images/ImageStrip.CloseButton.Pressed.png new file mode 100644 index 0000000..9d6d666 Binary files /dev/null and b/src/Resources/Images/ImageStrip.CloseButton.Pressed.png differ diff --git a/src/Resources/Images/LineCapButton.Arrow.End.png b/src/Resources/Images/LineCapButton.Arrow.End.png new file mode 100644 index 0000000..efa4838 Binary files /dev/null and b/src/Resources/Images/LineCapButton.Arrow.End.png differ diff --git a/src/Resources/Images/LineCapButton.Arrow.Start.png b/src/Resources/Images/LineCapButton.Arrow.Start.png new file mode 100644 index 0000000..1a6a52d Binary files /dev/null and b/src/Resources/Images/LineCapButton.Arrow.Start.png differ diff --git a/src/Resources/Images/LineCapButton.ArrowFilled.End.png b/src/Resources/Images/LineCapButton.ArrowFilled.End.png new file mode 100644 index 0000000..59c985c Binary files /dev/null and b/src/Resources/Images/LineCapButton.ArrowFilled.End.png differ diff --git a/src/Resources/Images/LineCapButton.ArrowFilled.Start.png b/src/Resources/Images/LineCapButton.ArrowFilled.Start.png new file mode 100644 index 0000000..71604b2 Binary files /dev/null and b/src/Resources/Images/LineCapButton.ArrowFilled.Start.png differ diff --git a/src/Resources/Images/LineCapButton.Flat.End.png b/src/Resources/Images/LineCapButton.Flat.End.png new file mode 100644 index 0000000..e1afcde Binary files /dev/null and b/src/Resources/Images/LineCapButton.Flat.End.png differ diff --git a/src/Resources/Images/LineCapButton.Flat.Start.png b/src/Resources/Images/LineCapButton.Flat.Start.png new file mode 100644 index 0000000..3124087 Binary files /dev/null and b/src/Resources/Images/LineCapButton.Flat.Start.png differ diff --git a/src/Resources/Images/LineCapButton.Rounded.End.png b/src/Resources/Images/LineCapButton.Rounded.End.png new file mode 100644 index 0000000..11f434b Binary files /dev/null and b/src/Resources/Images/LineCapButton.Rounded.End.png differ diff --git a/src/Resources/Images/LineCapButton.Rounded.Start.png b/src/Resources/Images/LineCapButton.Rounded.Start.png new file mode 100644 index 0000000..ddbb4bb Binary files /dev/null and b/src/Resources/Images/LineCapButton.Rounded.Start.png differ diff --git a/src/Resources/Images/PayPalDonate.gif b/src/Resources/Images/PayPalDonate.gif new file mode 100644 index 0000000..d017250 Binary files /dev/null and b/src/Resources/Images/PayPalDonate.gif differ diff --git a/src/Resources/Images/RoundedEdgeLL.png b/src/Resources/Images/RoundedEdgeLL.png new file mode 100644 index 0000000..9135627 Binary files /dev/null and b/src/Resources/Images/RoundedEdgeLL.png differ diff --git a/src/Resources/Images/RoundedEdgeLR.png b/src/Resources/Images/RoundedEdgeLR.png new file mode 100644 index 0000000..74f0b6d Binary files /dev/null and b/src/Resources/Images/RoundedEdgeLR.png differ diff --git a/src/Resources/Images/RoundedEdgeUL.png b/src/Resources/Images/RoundedEdgeUL.png new file mode 100644 index 0000000..d0e740b Binary files /dev/null and b/src/Resources/Images/RoundedEdgeUL.png differ diff --git a/src/Resources/Images/RoundedEdgeUR.png b/src/Resources/Images/RoundedEdgeUR.png new file mode 100644 index 0000000..f7f9baa Binary files /dev/null and b/src/Resources/Images/RoundedEdgeUR.png differ diff --git a/src/Resources/Images/TransparentLogo.png b/src/Resources/Images/TransparentLogo.png new file mode 100644 index 0000000..29583c5 Binary files /dev/null and b/src/Resources/Images/TransparentLogo.png differ diff --git a/src/Resources/InvariantStrings.cs b/src/Resources/InvariantStrings.cs new file mode 100644 index 0000000..a2b2245 --- /dev/null +++ b/src/Resources/InvariantStrings.cs @@ -0,0 +1,68 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// Contains strings that must be the same no matter what locale the UI is running with. + /// + public static class InvariantStrings + { + // {0} is "All Rights Reserved" + // Legal has advised that's the only part of this string that should be localizable. + public const string CopyrightFormat = + "Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. Portions Copyright © Microsoft Corporation. {0}"; + + public const string FeedbackEmail = + "" <-- You must specify an e-mail address for users to send feedback to.; + + public const string CrashlogEmail = + "" <-- You must specify a contact e-mail address to be placed in the crash log.; + + public const string WebsiteUrl = + "" <-- You must specify a URL for the application's website.; + + public const string WebsitePageHelpMenu = "/redirect/main_hm.html"; + + public const string ForumPageHelpPage = "/redirect/forum_hm.html"; + + public const string PluginsPageHelpPage = "/redirect/plugins_hm.html"; + + public const string TutorialsPageHelpPage = "/redirect/tutorials_hm.html"; + + public const string DonatePageHelpMenu = "/redirect/donate_hm.html"; + + public const string SearchEngineHelpMenu = "/redirect/search_hm.html"; + + public const string DonateUrlSetup = + "" <-- You must specify a destination URL for the donate button in the setup wizard.; + + public const string ExpiredPage = "redirect/pdnexpired.html"; + + public const string EffectsSubDir = "Effects"; + + public const string FileTypesSubDir = "FileTypes"; + + public const string DllExtension = ".dll"; + + // Fallback strings are used in case the resources file is unavailable. + public const string CrashLogHeaderTextFormatFallback = + @"This text file was created because Paint.NET crashed. +Please e-mail this file to {0} so we can diagnose and fix the problem. +"; + + public const string StartupUnhandledErrorFormatFallback = + "There was an unhandled error, and Paint.NET must be closed. Refer to the file '{0}', which has been placed on your desktop, for more information."; + + public const string SingleInstanceMonikerName = + "" <-- You must specify a moniker name (only letters, no symbols, no spaces); + } +} diff --git a/src/Resources/PdnInfo.cs b/src/Resources/PdnInfo.cs new file mode 100644 index 0000000..731cd4f --- /dev/null +++ b/src/Resources/PdnInfo.cs @@ -0,0 +1,451 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.IO; +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// A few utility functions specific to PaintDotNet.exe + /// + public static class PdnInfo + { + private static Icon appIcon; + public static Icon AppIcon + { + get + { + if (appIcon == null) + { + Stream stream = PdnResources.GetResourceStream("Icons.PaintDotNet.ico"); + appIcon = new Icon(stream); + stream.Close(); + } + + return appIcon; + } + } + + /// + /// Gets the full path to where user customization files should be stored. + /// + /// + /// User data files should include settings or customizations that don't go into data files such as *.PDN. + /// An example of a user data file is a color palette. + /// + public static string UserDataPath + { + get + { + string myDocsPath = SystemLayer.Shell.GetVirtualPath(PaintDotNet.SystemLayer.VirtualFolderName.UserDocuments, true); + string userDataDirName = PdnResources.GetString("SystemLayer.UserDataDirName"); + string userDataPath = Path.Combine(myDocsPath, userDataDirName); + return userDataPath; + } + } + + private static StartupTestType startupTest = StartupTestType.None; + public static StartupTestType StartupTest + { + get + { + return startupTest; + } + + set + { + startupTest = value; + } + } + + private static bool isTestMode = false; + public static bool IsTestMode + { + get + { + return isTestMode; + } + + set + { + isTestMode = value; + } + } + + public static DateTime BuildTime + { + get + { + Version version = GetVersion(); + + DateTime time = new DateTime(2000, 1, 1, 0, 0, 0); + time = time.AddDays(version.Build); + time = time.AddSeconds(version.Revision * 2); + + return time; + } + } + + private static string GetAppConfig() + { + object[] attributes = typeof(PdnInfo).Assembly.GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false); + AssemblyConfigurationAttribute aca = (AssemblyConfigurationAttribute)attributes[0]; + return aca.Configuration; + } + + private static readonly bool isFinalBuild = GetIsFinalBuild(); + + private static bool GetIsFinalBuild() + { + return !(GetAppConfig().IndexOf("Final") == -1); + } + + public static bool IsFinalBuild + { + get + { + return isFinalBuild; + } + } + + public static bool IsDebugBuild + { + get + { +#if DEBUG + return true; +#else + return false; +#endif + } + } + + // Pre-release builds expire after this many days. (debug+"final" also equals expiration) + public const int BetaExpireTimeDays = 60; + + public static DateTime ExpirationDate + { + get + { + if (PdnInfo.IsFinalBuild && !IsDebugBuild) + { + return DateTime.MaxValue; + } + else + { + return PdnInfo.BuildTime + new TimeSpan(BetaExpireTimeDays, 0, 0, 0); + } + } + } + + public static bool IsExpired + { + get + { + if (!PdnInfo.IsFinalBuild || PdnInfo.IsDebugBuild) + { + if (DateTime.Now > PdnInfo.ExpirationDate) + { + return true; + } + } + + return false; + } + } + + /// + /// Checks if the build is expired, and displays a dialog box that takes the user to + /// the Paint.NET website if necessary. + /// + /// true if the user should be allowed to continue, false if the build has expired + public static bool HandleExpiration(IWin32Window owner) + { + if (IsExpired) + { + string expiredMessage = PdnResources.GetString("ExpiredDialog.Message"); + + DialogResult result = MessageBox.Show(expiredMessage, PdnInfo.GetProductName(true), + MessageBoxButtons.OKCancel); + + if (result == DialogResult.OK) + { + string expiredRedirect = InvariantStrings.ExpiredPage; + PdnInfo.LaunchWebSite(owner, expiredRedirect); + } + + return false; + } + + return true; + } + + public static string GetApplicationDir() + { + string appPath = Application.StartupPath; + return appPath; + } + + /// + /// For final builds, returns a string such as "Paint.NET v2.6" + /// For non-final builds, returns a string such as "Paint.NET v2.6 Beta 2" + /// + /// + public static string GetProductName() + { + return GetProductName(!IsFinalBuild); + } + + public static string GetProductName(bool withTag) + { + string bareProductName = GetBareProductName(); + string productNameFormat = PdnResources.GetString("Application.ProductName.Format"); + string tag; + + if (withTag) + { + string tagFormat = PdnResources.GetString("Application.ProductName.Tag.Format"); + tag = string.Format(tagFormat, GetAppConfig()); + } + else + { + tag = string.Empty; + } + + string version = GetVersionNumberString(GetVersion(), 2); + + string productName = string.Format( + productNameFormat, + bareProductName, + version, + tag); + + return productName; + } + + /// + /// Returns the bare product name, e.g. "Paint.NET" + /// + public static string GetBareProductName() + { + return PdnResources.GetString("Application.ProductName.Bare"); + } + + private static string copyrightString = null; + public static string GetCopyrightString() + { + if (copyrightString == null) + { + string format = InvariantStrings.CopyrightFormat; + string allRightsReserved = PdnResources.GetString("Application.Copyright.AllRightsReserved"); + copyrightString = string.Format(CultureInfo.CurrentCulture, format, allRightsReserved); + } + + return copyrightString; + } + + public static Version GetVersion() + { + return new Version(Application.ProductVersion); + } + + private static string GetConfigurationString() + { + object[] attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyConfigurationAttribute), false); + AssemblyConfigurationAttribute aca = (AssemblyConfigurationAttribute)attributes[0]; + return aca.Configuration; + } + + /// + /// Returns a full version string of the form: ApplicationConfiguration + BuildType + BuildVersion + /// i.e.: "Beta 2 Debug build 1.0.*.*" + /// + /// + public static string GetVersionString() + { + string buildType = +#if DEBUG + "Debug"; +#else + "Release"; +#endif + + string versionFormat = PdnResources.GetString("PdnInfo.VersionString.Format"); + + string versionText = string.Format( + versionFormat, + GetConfigurationString(), + buildType, + GetVersionNumberString(GetVersion(), 4)); + + return versionText; + } + + /// + /// Returns a string for just the version number, i.e. "3.01" + /// + /// + public static string GetVersionNumberString(Version version, int fieldCount) + { + if (fieldCount < 1 || fieldCount > 4) + { + throw new ArgumentOutOfRangeException("fieldCount", "must be in the range [1, 4]"); + } + + StringBuilder sb = new StringBuilder(); + + sb.Append(version.Major.ToString()); + + if (fieldCount >= 2) + { + sb.AppendFormat(".{0}", version.Minor.ToString("D2")); + } + + if (fieldCount >= 3) + { + sb.AppendFormat(".{0}", version.Build.ToString()); + } + + if (fieldCount == 4) + { + sb.AppendFormat(".{0}", version.Revision.ToString()); + } + + return sb.ToString(); + } + + /// + /// Returns a version string that is presentable without the Paint.NET name. example: "version 2.5 Beta 5" + /// + /// + public static string GetFriendlyVersionString() + { + Version version = PdnInfo.GetVersion(); + string versionFormat = PdnResources.GetString("PdnInfo.FriendlyVersionString.Format"); + string configFormat = PdnResources.GetString("PdnInfo.FriendlyVersionString.ConfigWithSpace.Format"); + string config = string.Format(configFormat, GetConfigurationString()); + string configText; + + if (PdnInfo.IsFinalBuild) + { + configText = string.Empty; + } + else + { + configText = config; + } + + string versionText = string.Format(versionFormat, GetVersionNumberString(version, 2), configText); + return versionText; + } + + /// + /// Returns the application name, with the version string. i.e., "Paint.NET v2.5 (Beta 2 Debug build 1.0.*.*)" + /// + /// + public static string GetFullAppName() + { + string fullAppNameFormat = PdnResources.GetString("PdnInfo.FullAppName.Format"); + string fullAppName = string.Format(fullAppNameFormat, PdnInfo.GetProductName(false), GetVersionString()); + return fullAppName; + } + + /// + /// For final builds, this returns PdnInfo.GetProductName() (i.e., "Paint.NET v2.2") + /// For non-final builds, this returns GetFullAppName() + /// + /// + public static string GetAppName() + { + if (PdnInfo.IsFinalBuild && !PdnInfo.IsDebugBuild) + { + return PdnInfo.GetProductName(false); + } + else + { + return GetFullAppName(); + } + } + + public static void LaunchWebSite(IWin32Window owner) + { + LaunchWebSite(owner, null); + } + + public static void LaunchWebSite(IWin32Window owner, string page) + { + string webSite = InvariantStrings.WebsiteUrl; + + Uri baseUri = new Uri(webSite); + Uri uri; + + if (page == null) + { + uri = baseUri; + } + else + { + uri = new Uri(baseUri, page); + } + + string url = uri.ToString(); + + if (url.IndexOf("@") == -1) + { + OpenUrl(owner, url); + } + } + + public static bool OpenUrl(IWin32Window owner, string url) + { + bool result = SystemLayer.Shell.LaunchUrl(owner, url); + + if (!result) + { + string messageFormat = PdnResources.GetString("LaunchLink.Error.Format"); + string message = string.Format(messageFormat, url); + MessageBox.Show(owner, message, PdnInfo.GetBareProductName(), MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + return result; + } + + public static string GetNgenPath() + { + return GetNgenPath(false); + } + + public static string GetNgenPath(bool force32bit) + { + string fxDir; + + if (UIntPtr.Size == 8 && !force32bit) + { + fxDir = "Framework64"; + } + else + { + fxDir = "Framework"; + } + + string fxPathBase = @"%WINDIR%\Microsoft.NET\" + fxDir + @"\v"; + string fxPath = fxPathBase + Environment.Version.ToString(3) + @"\"; + string fxPathExp = System.Environment.ExpandEnvironmentVariables(fxPath); + string ngenExe = Path.Combine(fxPathExp, "ngen.exe"); + + return ngenExe; + } + } +} diff --git a/src/Resources/PdnResources.cs b/src/Resources/PdnResources.cs new file mode 100644 index 0000000..f643c07 --- /dev/null +++ b/src/Resources/PdnResources.cs @@ -0,0 +1,432 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public static class PdnResources + { + private static ResourceManager resourceManager; + private const string ourNamespace = "PaintDotNet"; + private static Assembly ourAssembly; + private static string[] localeDirs; + private static CultureInfo pdnCulture; + private static string resourcesDir; + + public static string ResourcesDir + { + get + { + if (resourcesDir == null) + { + resourcesDir = Path.GetDirectoryName(typeof(PdnResources).Assembly.Location); + } + + return resourcesDir; + } + + set + { + resourcesDir = value; + Initialize(); + } + } + + public static CultureInfo Culture + { + get + { + return pdnCulture; + } + + set + { + System.Threading.Thread.CurrentThread.CurrentUICulture = value; + Initialize(); + } + } + + private static void Initialize() + { + resourceManager = CreateResourceManager(); + ourAssembly = Assembly.GetExecutingAssembly(); + pdnCulture = CultureInfo.CurrentUICulture; + localeDirs = GetLocaleDirs(); + } + + static PdnResources() + { + Initialize(); + } + + public static void SetNewCulture(string newLocaleName) + { + // TODO, HACK: post-3.0 we must refactor and have an actual user data manager that can handle all this renaming + string oldUserDataPath = PdnInfo.UserDataPath; + string oldPaletteDirName = PdnResources.GetString("ColorPalettes.UserDataSubDirName"); + // END HACK + + CultureInfo newCI = new CultureInfo(newLocaleName); + Settings.CurrentUser.SetString("LanguageName", newLocaleName); + Culture = newCI; + + // TODO, HACK: finish up renaming + string newUserDataPath = PdnInfo.UserDataPath; + string newPaletteDirName = PdnResources.GetString("ColorPalettes.UserDataSubDirName"); + + // 1. rename user data dir from old localized name to new localized name + if (oldUserDataPath != newUserDataPath) + { + try + { + Directory.Move(oldUserDataPath, newUserDataPath); + } + + catch (Exception) + { + } + } + + // 2. rename palette dir from old localized name (in new localized user data path) to new localized name + string oldPalettePath = Path.Combine(newUserDataPath, oldPaletteDirName); + string newPalettePath = Path.Combine(newUserDataPath, newPaletteDirName); + + if (oldPalettePath != newPalettePath) + { + try + { + Directory.Move(oldPalettePath, newPalettePath); + } + + catch (Exception) + { + } + } + // END HACK + } + + public static string[] GetInstalledLocales() + { + const string left = "PaintDotNet.Strings.3"; + const string right = ".resources"; + string ourDir = ResourcesDir; + string fileSpec = left + "*" + right; + string[] pathNames = Directory.GetFiles(ourDir, fileSpec); + List locales = new List(); + + for (int i = 0; i < pathNames.Length; ++i) + { + string pathName = pathNames[i]; + string dirName = Path.GetDirectoryName(pathName); + string fileName = Path.GetFileName(pathName); + string sansRight = fileName.Substring(0, fileName.Length - right.Length); + string sansLeft = sansRight.Substring(left.Length); + + string locale; + + if (sansLeft.Length > 0 && sansLeft[0] == '.') + { + locale = sansLeft.Substring(1); + } + else if (sansLeft.Length == 0) + { + locale = "en-US"; + } + else + { + locale = sansLeft; + } + + try + { + // Ensure this locale can create a valid CultureInfo object. + CultureInfo ci = new CultureInfo(locale); + } + + catch (Exception) + { + // Skip past invalid locales -- don't let them crash us + continue; + } + + locales.Add(locale); + } + + return locales.ToArray(); + } + + public static string[] GetLocaleNameChain() + { + List names = new List(); + CultureInfo ci = pdnCulture; + + while (ci.Name != string.Empty) + { + names.Add(ci.Name); + ci = ci.Parent; + } + + return names.ToArray(); + } + + private static string[] GetLocaleDirs() + { + const string rootDirName = "Resources"; + string appDir = ResourcesDir; + string rootDir = Path.Combine(appDir, rootDirName); + List dirs = new List(); + + CultureInfo ci = pdnCulture; + + while (ci.Name != string.Empty) + { + string localeDir = Path.Combine(rootDir, ci.Name); + + if (Directory.Exists(localeDir)) + { + dirs.Add(localeDir); + } + + ci = ci.Parent; + } + + return dirs.ToArray(); + } + + private static ResourceManager CreateResourceManager() + { + const string stringsFileName = "PaintDotNet.Strings.3"; + ResourceManager rm = ResourceManager.CreateFileBasedResourceManager(stringsFileName, ResourcesDir, null); + return rm; + } + + public static ResourceManager Strings + { + get + { + return resourceManager; + } + } + + public static string GetString(string stringName) + { + string theString = resourceManager.GetString(stringName, pdnCulture); + + if (theString == null) + { + Debug.WriteLine(stringName + " not found"); + } + + return theString; + } + + public static Stream GetResourceStream(string fileName) + { + Stream stream = null; + + for (int i = 0; i < localeDirs.Length; ++i) + { + string filePath = Path.Combine(localeDirs[i], fileName); + + if (File.Exists(filePath)) + { + stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + break; + } + } + + if (stream == null) + { + string fullName = ourNamespace + "." + fileName; + stream = ourAssembly.GetManifestResourceStream(fullName); + } + + return stream; + } + + public static Image GetImageBmpOrPng(string fileNameNoExt) + { + // using Path.ChangeExtension is not what we want; quite often filenames are "Icons.BlahBlahBlah" + string fileNameBmp = fileNameNoExt + ".bmp"; + Image image = GetImage(fileNameBmp); + + if (image == null) + { + string fileNamePng = fileNameNoExt + ".png"; + image = GetImage(fileNamePng); + } + + return image; + } + + public static Image GetImage(string fileName) + { + Stream stream = GetResourceStream(fileName); + + Image image = null; + if (stream != null) + { + image = LoadImage(stream); + } + + return image; + } + + private sealed class PdnImageResource + : ImageResource + { + private string name; + private static Dictionary images; + + protected override Image Load() + { + return PdnResources.GetImage(this.name); + } + + public static ImageResource Get(string name) + { + ImageResource ir; + + if (!images.TryGetValue(name, out ir)) + { + ir = new PdnImageResource(name); + images.Add(name, ir); + } + + return ir; + } + + static PdnImageResource() + { + images = new Dictionary(); + } + + private PdnImageResource(string name) + : base() + { + this.name = name; + } + + private PdnImageResource(Image image) + : base(image) + { + this.name = null; + } + } + + public static ImageResource GetImageResource(string fileName) + { + return PdnImageResource.Get(fileName); + } + + public static Icon GetIcon(string fileName) + { + Stream stream = GetResourceStream(fileName); + Icon icon = null; + + if (stream != null) + { + icon = new Icon(stream); + } + + return icon; + } + + public static Icon GetIconFromImage(string fileName) + { + Stream stream = GetResourceStream(fileName); + + Icon icon = null; + + if (stream != null) + { + Image image = LoadImage(stream); + icon = Icon.FromHandle(((Bitmap)image).GetHicon()); + image.Dispose(); + stream.Close(); + } + + return icon; + } + + private static bool CheckForSignature(Stream input, byte[] signature) + { + long oldPos = input.Position; + byte[] inputSig = new byte[signature.Length]; + int amountRead = input.Read(inputSig, 0, inputSig.Length); + + bool foundSig = false; + if (amountRead == signature.Length) + { + foundSig = true; + + for (int i = 0; i < signature.Length; ++i) + { + foundSig &= (signature[i] == inputSig[i]); + } + } + + input.Position = oldPos; + return foundSig; + } + + public static bool IsGdiPlusImageAllowed(Stream input) + { + byte[] wmfSig = new byte[] { 0xd7, 0xcd, 0xc6, 0x9a }; + byte[] emfSig = new byte[] { 0x01, 0x00, 0x00, 0x00 }; + + // Check for and explicitely block WMF and EMF images + return !(CheckForSignature(input, emfSig) || CheckForSignature(input, wmfSig)); + } + + public static Image LoadImage(string fileName) + { + using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return LoadImage(stream); + } + } + + /// + /// Loads an image from the given stream. The stream must be seekable. + /// + /// The Stream to load the image from. + public static Image LoadImage(Stream input) + { + /* + if (!IsGdiPlusImageAllowed(input)) + { + throw new IOException("File format is not supported"); + } + */ + + Image image = Image.FromStream(input); + + if (image.RawFormat == ImageFormat.Wmf || image.RawFormat == ImageFormat.Emf) + { + image.Dispose(); + throw new IOException("File format isn't supported"); + } + + return image; + } + } +} diff --git a/src/Resources/Resources.csproj b/src/Resources/Resources.csproj new file mode 100644 index 0000000..dd5b468 --- /dev/null +++ b/src/Resources/Resources.csproj @@ -0,0 +1,515 @@ + + + Local + 9.0.21022 + 2.0 + {0B173113-1F9B-4939-A62F-A176336F13AC} + Debug + AnyCPU + + + + + PaintDotNet.Resources + + + JScript + Grid + IE50 + false + Library + PaintDotNet + OnBuildSuccess + + + + + + + 2.0 + + + bin\Debug\ + true + 278921216 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + bin\Release\ + true + 278921216 + false + + + TRACE + + + true + 512 + false + + + true + false + false + true + 4 + pdbonly + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + + System + + + System.Drawing + + + System.Windows.Forms + + + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + {80572820-93A5-4278-A513-D902BEA2639C} + SystemLayer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/Resources/StartupTestType.cs b/src/Resources/StartupTestType.cs new file mode 100644 index 0000000..8a79d7c --- /dev/null +++ b/src/Resources/StartupTestType.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + public enum StartupTestType + { + None, + Timed, + WorkingSet, + } +} diff --git a/src/RotateNubRenderer.cs b/src/RotateNubRenderer.cs new file mode 100644 index 0000000..76dd24a --- /dev/null +++ b/src/RotateNubRenderer.cs @@ -0,0 +1,124 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + internal class RotateNubRenderer + : SurfaceBoxGraphicsRenderer + { + private const int size = 6; + private PointF location; + private float angle; + + public PointF Location + { + get + { + return this.location; + } + + set + { + InvalidateOurself(); + this.location = value; + InvalidateOurself(); + } + } + + public float Angle + { + get + { + return this.angle; + } + + set + { + InvalidateOurself(); + this.angle = value; + InvalidateOurself(); + } + } + + private RectangleF GetOurRectangle() + { + RectangleF rectF = new RectangleF(this.Location, new SizeF(0, 0)); + float ratio = 1.0f / (float)OwnerList.ScaleFactor.Ratio; + float ourSize = UI.ScaleWidth(size); + rectF.Inflate(ratio * ourSize, ratio * ourSize); + return rectF; + } + + private void InvalidateOurself() + { + RectangleF rectF = GetOurRectangle(); + Rectangle rect = Utility.RoundRectangle(rectF); + rect.Inflate(2, 2); + Invalidate(rect); + } + + public bool IsPointTouching(Point pt) + { + RectangleF rectF = GetOurRectangle(); + Rectangle rect = Utility.RoundRectangle(rectF); + return pt.X >= rect.Left && pt.Y >= rect.Top && pt.X < rect.Right && pt.Y < rect.Bottom; + } + + protected override void OnVisibleChanged() + { + InvalidateOurself(); + } + + public override void RenderToGraphics(Graphics g, Point offset) + { + // We round these values to the nearest integer to avoid an interesting rendering + // anomaly (or bug? what a surprise ... GDI+) where the nub appears to rotate + // off-center, or the 'screw-line' is off-center + float centerX = this.Location.X * (float)OwnerList.ScaleFactor.Ratio; + float centerY = this.Location.Y * (float)OwnerList.ScaleFactor.Ratio; + Point center = new Point((int)Math.Round(centerX), (int)Math.Round(centerY)); + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TranslateTransform(-center.X, -center.Y, MatrixOrder.Append); + g.RotateTransform(this.angle, MatrixOrder.Append); + g.TranslateTransform(center.X - offset.X, center.Y - offset.Y, MatrixOrder.Append); + + float ourSize = UI.ScaleWidth(size); + + using (Pen white = new Pen(Color.FromArgb(128, Color.White), -1.0f), + black = new Pen(Color.FromArgb(128, Color.Black), -1.0f)) + { + RectangleF rectF = new RectangleF(center, new SizeF(0, 0)); + rectF.Inflate(ourSize - 3, ourSize - 3); + + g.DrawEllipse(white, Rectangle.Truncate(rectF)); + rectF.Inflate(1, 1); + g.DrawEllipse(black, Rectangle.Truncate(rectF)); + rectF.Inflate(1, 1); + g.DrawEllipse(white, Rectangle.Truncate(rectF)); + + rectF.Inflate(-2, -2); + g.DrawLine(white, rectF.X + rectF.Width / 2.0f - 1.0f, rectF.Top, rectF.X + rectF.Width / 2.0f - 1.0f, rectF.Bottom); + g.DrawLine(white, rectF.X + rectF.Width / 2.0f + 1.0f, rectF.Top, rectF.X + rectF.Width / 2.0f + 1.0f, rectF.Bottom); + g.DrawLine(black, rectF.X + rectF.Width / 2.0f, rectF.Top, rectF.X + rectF.Width / 2.0f, rectF.Bottom); + } + } + + public RotateNubRenderer(SurfaceBoxRendererList ownerList) + : base(ownerList) + { + this.location = new Point(0, 0); + } + } +} diff --git a/src/RotateType.cs b/src/RotateType.cs new file mode 100644 index 0000000..b64728f --- /dev/null +++ b/src/RotateType.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum RotateType + { + Clockwise90, + CounterClockwise90, + Rotate180, + } +} diff --git a/src/SaveConfigDialog.cs b/src/SaveConfigDialog.cs new file mode 100644 index 0000000..b860205 --- /dev/null +++ b/src/SaveConfigDialog.cs @@ -0,0 +1,915 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class SaveConfigDialog + : PdnBaseDialog + { + private static readonly Size unscaledMinSize = new Size(600, 350); + + private static class SettingNames + { + // We store the bounds of the window relative to its owner. + public const string Left = "SaveConfigDialog.Left"; + public const string Top = "SaveConfigDialog.Top"; + public const string Width = "SaveConfigDialog.Width"; + public const string Height = "SaveConfigDialog.Height"; + public const string WindowState = "SaveConfigDialog.WindowState"; + } + + private void LoadPositions() + { + Size minSize = UI.ScaleSize(unscaledMinSize); + + Form owner = Owner; + + Rectangle ownerWindowBounds; + if (owner != null) + { + ownerWindowBounds = owner.Bounds; + } + else + { + ownerWindowBounds = Screen.PrimaryScreen.WorkingArea; + } + + // Determine what our default relative bounds should be + // These are client bounds that are relative to our owner's window bounds. + // Or if we have no window, then this is for the primary monitor. + Rectangle defaultRelativeClientBounds = new Rectangle( + (ownerWindowBounds.Width - minSize.Width) / 2, + (ownerWindowBounds.Height - minSize.Height) / 2, + minSize.Width, + minSize.Height); + + // Load the relative client bounds for the dialog. This is a client bounds that is + // relative to the owner's window bounds. + Rectangle relativeClientBounds; + FormWindowState newFws; + + try + { + string newFwsString = Settings.CurrentUser.GetString(SettingNames.WindowState, FormWindowState.Normal.ToString()); + newFws = (FormWindowState)Enum.Parse(typeof(FormWindowState), newFwsString); + + int newLeft = Settings.CurrentUser.GetInt32(SettingNames.Left, defaultRelativeClientBounds.Left); + int newTop = Settings.CurrentUser.GetInt32(SettingNames.Top, defaultRelativeClientBounds.Top); + int newWidth = Math.Max(minSize.Width, Settings.CurrentUser.GetInt32(SettingNames.Width, defaultRelativeClientBounds.Width)); + int newHeight = Math.Max(minSize.Height, Settings.CurrentUser.GetInt32(SettingNames.Height, defaultRelativeClientBounds.Height)); + + relativeClientBounds = new Rectangle(newLeft, newTop, newWidth, newHeight); + } + + catch (Exception) + { + relativeClientBounds = defaultRelativeClientBounds; + newFws = FormWindowState.Normal; + } + + // Convert to client bounds from from client bounds that are relative to the owner's window bounds. + // This will be our proposed client bounds. + Rectangle proposedClientBounds = new Rectangle( + relativeClientBounds.Left + ownerWindowBounds.Left, + relativeClientBounds.Top + owner.Top, + relativeClientBounds.Width, + relativeClientBounds.Height); + + // Keep the default client bounds around as well + Rectangle defaultClientBounds = new Rectangle( + defaultRelativeClientBounds.Left + ownerWindowBounds.Left, + defaultRelativeClientBounds.Top + ownerWindowBounds.Top, + defaultRelativeClientBounds.Width, + defaultRelativeClientBounds.Height); + + // Start applying the values. + SuspendLayout(); + + try + { + Rectangle newClientBounds = ValidateAndAdjustNewBounds(owner, proposedClientBounds, defaultClientBounds); + Rectangle newWindowBounds = ClientBoundsToWindowBounds(newClientBounds); + Bounds = newWindowBounds; + WindowState = newFws; + } + + finally + { + ResumeLayout(true); + } + } + + private Rectangle ValidateAndAdjustNewBounds(Form owner, Rectangle newClientBounds, Rectangle defaultClientBounds) + { + Rectangle returnBounds; + + // Ensure that the bounds they want are in bounds on any of the user's monitors + // Although first convert from client bounds to window bounds + Rectangle newWindowBounds = ClientBoundsToWindowBounds(newClientBounds); + bool intersects = false; + + foreach (Screen screen in Screen.AllScreens) + { + intersects |= screen.Bounds.IntersectsWith(newWindowBounds); + } + + // If the newClientBounds aren't visible anywhere, go with the defaultClientBounds + Rectangle newClientBounds2; + + if (intersects) + { + newClientBounds2 = newClientBounds; + } + else + { + newClientBounds2 = defaultClientBounds; + } + + // Now make sure that the bounds are forced to be on the same screen as the owner window + Screen ourScreen; + if (owner != null) + { + ourScreen = Screen.FromControl(owner); + } + else + { + ourScreen = Screen.PrimaryScreen; + } + + Rectangle newWindowBounds2 = ClientBoundsToWindowBounds(newClientBounds2); + Rectangle onScreenWindowBounds = EnsureRectIsOnScreen(ourScreen, newWindowBounds2); + Rectangle finalNewClientBounds = WindowBoundsToClientBounds(onScreenWindowBounds); + + returnBounds = finalNewClientBounds; + + return returnBounds; + } + + private void SavePositions() + { + if (WindowState != FormWindowState.Minimized) + { + if (WindowState != FormWindowState.Maximized) + { + Form owner = Owner; + Point origin; + + if (owner != null) + { + Rectangle ownerWindowBounds = owner.Bounds; + origin = ownerWindowBounds.Location; + } + else + { + origin = new Point(0, 0); + } + + // Save our client rectangle relative to our parent window's bounds (including non-client) + + Rectangle ourClientBounds = WindowBoundsToClientBounds(this.Bounds); + + int relativeLeft = ourClientBounds.Left - origin.X; + int relativeTop = ourClientBounds.Top - origin.Y; + + Settings.CurrentUser.SetInt32(SettingNames.Left, relativeLeft); + Settings.CurrentUser.SetInt32(SettingNames.Top, relativeTop); + Settings.CurrentUser.SetInt32(SettingNames.Width, ourClientBounds.Width); + Settings.CurrentUser.SetInt32(SettingNames.Height, ourClientBounds.Height); + } + + Settings.CurrentUser.SetString(SettingNames.WindowState, WindowState.ToString()); + } + } + + protected override void OnSizeChanged(EventArgs e) + { + if (IsShown) + { + SavePositions(); + } + + base.OnSizeChanged(e); + } + + protected override void OnResize(EventArgs e) + { + if (IsShown) + { + SavePositions(); + } + + base.OnResize(e); + } + + protected override void OnClosing(CancelEventArgs e) + { + if (IsShown) + { + SavePositions(); + } + + base.OnClosing(e); + } + + private string fileSizeTextFormat; + private System.Threading.Timer fileSizeTimer; + private const int timerDelayTime = 100; + + private Cursor handIcon = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur")); + private Cursor handIconMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur")); + private Hashtable fileTypeToSaveToken = new Hashtable(); + private System.ComponentModel.IContainer components = null; + private FileType fileType; + private System.Windows.Forms.Button defaultsButton; + private Document document; + private bool disposeDocument = false; + private HeaderLabel previewHeader; + private PaintDotNet.DocumentView documentView; + private PaintDotNet.SaveConfigWidget saveConfigWidget; + private System.Windows.Forms.Panel saveConfigPanel; + + private PaintDotNet.HeaderLabel settingsHeader; + + private Surface scratchSurface; + public Surface ScratchSurface + { + set + { + if (this.scratchSurface != null) + { + throw new InvalidOperationException("May only set ScratchSurface once, and only before the dialog is shown"); + } + + this.scratchSurface = value; + } + } + + public event ProgressEventHandler Progress; + protected virtual void OnProgress(int percent) + { + if (Progress != null) + { + Progress(this, new ProgressEventArgs((double)percent)); + } + } + + /// + /// Gets or sets the Document instance that is to be saved. + /// If this is changed after the dialog is shown, the results are undefined. + /// + [Browsable(false)] + public Document Document + { + get + { + return this.document; + } + + set + { + this.document = value; + } + } + + + [Browsable(false)] + public FileType FileType + { + get + { + return fileType; + } + + set + { + if (this.fileType != null && this.fileType.Name == value.Name) + { + return; + } + + if (this.fileType != null) + { + fileTypeToSaveToken[this.fileType] = this.SaveConfigToken; + } + + this.fileType = value; + SaveConfigToken token = (SaveConfigToken)fileTypeToSaveToken[this.fileType]; + + if (token == null) + { + token = this.fileType.GetLastSaveConfigToken(); + } + + // Make sure the token is of the expected type by checking it against the 'default' token from this file type + SaveConfigToken defaultToken = this.fileType.CreateDefaultSaveConfigToken(); + if (token.GetType() != defaultToken.GetType()) + { + token = null; + } + + if (token == null) + { + token = this.fileType.CreateDefaultSaveConfigToken(); + } + + SaveConfigWidget newWidget = this.fileType.CreateSaveConfigWidget(); + newWidget.Token = token; + newWidget.Location = this.saveConfigWidget.Location; + this.TokenChangedHandler(this, EventArgs.Empty); + this.saveConfigWidget.TokenChanged -= new EventHandler(TokenChangedHandler); + SuspendLayout(); + this.saveConfigPanel.Controls.Remove(this.saveConfigWidget); + this.saveConfigWidget = newWidget; + this.saveConfigPanel.Controls.Add(this.saveConfigWidget); + ResumeLayout(true); + this.saveConfigWidget.TokenChanged += new EventHandler(TokenChangedHandler); + + if (this.saveConfigWidget is NoSaveConfigWidget) + { + this.defaultsButton.Enabled = false; + } + else + { + this.defaultsButton.Enabled = true; + } + } + } + + [Browsable(false)] + public SaveConfigToken SaveConfigToken + { + get + { + return this.saveConfigWidget.Token; + } + + set + { + this.saveConfigWidget.Token = value; + } + } + + protected override void OnLoad(EventArgs e) + { + if (this.scratchSurface == null) + { + throw new InvalidOperationException("ScratchSurface was never set: it is null"); + } + + LoadPositions(); + + base.OnLoad(e); + } + + public SaveConfigDialog() + { + this.fileSizeTimer = new System.Threading.Timer(new System.Threading.TimerCallback(FileSizeTimerCallback), + null, 1000, System.Threading.Timeout.Infinite); + + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.Text = PdnResources.GetString("SaveConfigDialog.Text"); + this.fileSizeTextFormat = PdnResources.GetString("SaveConfigDialog.PreviewHeader.Text.Format"); + this.settingsHeader.Text = PdnResources.GetString("SaveConfigDialog.SettingsHeader.Text"); + this.defaultsButton.Text = PdnResources.GetString("SaveConfigDialog.DefaultsButton.Text"); + this.previewHeader.Text = PdnResources.GetString("SaveConfigDialog.PreviewHeader.Text"); + + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileSaveIcon.png").Reference); + + this.documentView.Cursor = handIcon; + + //this.MinimumSize = this.Size; + } + + protected override void OnLayout(LayoutEventArgs levent) + { + // Bottom-right Buttons + int buttonsBottomMargin = UI.ScaleHeight(8); + int buttonsRightMargin = UI.ScaleWidth(8); + int buttonsHMargin = UI.ScaleWidth(8); + + this.baseCancelButton.Location = new Point( + ClientSize.Width - this.baseOkButton.Width - buttonsRightMargin, + ClientSize.Height - buttonsBottomMargin - this.baseCancelButton.Height); + + this.baseOkButton.Location = new Point( + this.baseCancelButton.Left - buttonsHMargin - this.baseOkButton.Width, + ClientSize.Height - buttonsBottomMargin - this.baseOkButton.Height); + + int previewBottomMargin = UI.ScaleHeight(8); + + // Set up layout properties + int topMargin = UI.ScaleHeight(6); + int leftMargin = UI.ScaleWidth(8); + int leftColumWidth = UI.ScaleWidth(200); + int columHMargin = UI.ScaleWidth(8); + int rightMargin = UI.ScaleWidth(8); + int vMargin = UI.ScaleHeight(4); + int rightColumnX = leftMargin + leftColumWidth + columHMargin; + int rightColumnWidth = ClientSize.Width - rightColumnX - rightMargin; + int defaultsButtonTopMargin = UI.ScaleHeight(12); + int headerXAdjustment = -3; + + // Left column + this.settingsHeader.Location = new Point(leftMargin + headerXAdjustment, topMargin); + this.settingsHeader.Width = leftColumWidth - headerXAdjustment; + this.settingsHeader.PerformLayout(); + + this.saveConfigPanel.Location = new Point(leftMargin, this.settingsHeader.Bottom + vMargin); + this.saveConfigPanel.Width = leftColumWidth; + this.saveConfigPanel.PerformLayout(); + + //this.saveConfigWidget.Location = new Point(0, 0); + this.saveConfigWidget.Width = this.saveConfigPanel.Width - SystemInformation.VerticalScrollBarWidth; + + // Right column + this.previewHeader.Location = new Point(rightColumnX + headerXAdjustment, topMargin); + this.previewHeader.Width = rightColumnWidth - headerXAdjustment; + this.previewHeader.PerformLayout(); + + this.documentView.Location = new Point(rightColumnX, this.previewHeader.Bottom + vMargin); + this.documentView.Size = new Size( + rightColumnWidth, + this.baseCancelButton.Top - previewBottomMargin - this.documentView.Top); + + // Finish up setting the height on the left side + this.saveConfigPanel.Height = this.documentView.Bottom - this.saveConfigPanel.Top - + this.defaultsButton.Height - defaultsButtonTopMargin; + + this.saveConfigWidget.PerformLayout(); + + int saveConfigHeight = Math.Min(this.saveConfigPanel.Height, this.saveConfigWidget.Height); + + this.defaultsButton.PerformLayout(); + + this.defaultsButton.Location = new Point( + leftMargin + (leftColumWidth - this.defaultsButton.Width) / 2, + this.saveConfigPanel.Top + saveConfigHeight + defaultsButtonTopMargin); + + MinimumSize = UI.ScaleSize(unscaledMinSize); + + base.OnLayout(levent); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.disposeDocument && this.documentView.Document != null) + { + Document disposeMe = this.documentView.Document; + this.documentView.Document = null; + disposeMe.Dispose(); + } + + CleanupTimer(); + + if (this.handIcon != null) + { + this.handIcon.Dispose(); + this.handIcon = null; + } + + if (this.handIconMouseDown != null) + { + this.handIconMouseDown.Dispose(); + this.handIconMouseDown = null; + } + + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.saveConfigPanel = new System.Windows.Forms.Panel(); + this.defaultsButton = new System.Windows.Forms.Button(); + this.saveConfigWidget = new PaintDotNet.SaveConfigWidget(); + this.previewHeader = new PaintDotNet.HeaderLabel(); + this.documentView = new PaintDotNet.DocumentView(); + this.settingsHeader = new PaintDotNet.HeaderLabel(); + this.SuspendLayout(); + // + // baseOkButton + // + this.baseOkButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.baseOkButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.baseOkButton.Name = "baseOkButton"; + this.baseOkButton.TabIndex = 2; + this.baseOkButton.Click += new System.EventHandler(this.BaseOkButton_Click); + // + // baseCancelButton + // + this.baseCancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.baseCancelButton.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.baseCancelButton.Name = "baseCancelButton"; + this.baseCancelButton.TabIndex = 3; + this.baseCancelButton.Click += new System.EventHandler(this.BaseCancelButton_Click); + // + // saveConfigPanel + // + this.saveConfigPanel.AutoScroll = true; + this.saveConfigPanel.Name = "saveConfigPanel"; + this.saveConfigPanel.TabIndex = 0; + this.saveConfigPanel.TabStop = false; + // + // defaultsButton + // + this.defaultsButton.Name = "defaultsButton"; + this.defaultsButton.AutoSize = true; + this.defaultsButton.FlatStyle = FlatStyle.System; + this.defaultsButton.TabIndex = 1; + this.defaultsButton.Click += new System.EventHandler(this.DefaultsButton_Click); + // + // saveConfigWidget + // + this.saveConfigWidget.Name = "saveConfigWidget"; + this.saveConfigWidget.TabIndex = 9; + this.saveConfigWidget.Token = null; + // + // previewHeader + // + this.previewHeader.Name = "previewHeader"; + this.previewHeader.RightMargin = 0; + this.previewHeader.TabIndex = 11; + this.previewHeader.TabStop = false; + this.previewHeader.Text = "Header"; + // + // documentView + // + this.documentView.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.documentView.Document = null; + this.documentView.Name = "documentView"; + this.documentView.PanelAutoScroll = true; + this.documentView.RulersEnabled = false; + this.documentView.TabIndex = 12; + this.documentView.TabStop = false; + this.documentView.DocumentMouseMove += new System.Windows.Forms.MouseEventHandler(this.DocumentView_DocumentMouseMove); + this.documentView.DocumentMouseDown += new System.Windows.Forms.MouseEventHandler(this.DocumentView_DocumentMouseDown); + this.documentView.DocumentMouseUp += new System.Windows.Forms.MouseEventHandler(this.DocumentView_DocumentMouseUp); + this.documentView.Visible = false; + // + // settingsHeader + // + this.settingsHeader.Name = "settingsHeader"; + this.settingsHeader.TabIndex = 13; + this.settingsHeader.TabStop = false; + this.settingsHeader.Text = "Header"; + // + // SaveConfigDialog + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.Controls.Add(this.defaultsButton); + this.Controls.Add(this.settingsHeader); + this.Controls.Add(this.previewHeader); + this.Controls.Add(this.documentView); + this.Controls.Add(this.saveConfigPanel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable; + this.MinimizeBox = false; + this.MaximizeBox = true; + this.Name = "SaveConfigDialog"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show; + this.StartPosition = FormStartPosition.Manual; + this.Controls.SetChildIndex(this.saveConfigPanel, 0); + this.Controls.SetChildIndex(this.documentView, 0); + this.Controls.SetChildIndex(this.baseOkButton, 0); + this.Controls.SetChildIndex(this.baseCancelButton, 0); + this.Controls.SetChildIndex(this.previewHeader, 0); + this.Controls.SetChildIndex(this.settingsHeader, 0); + this.Controls.SetChildIndex(this.defaultsButton, 0); + this.ResumeLayout(false); + } + #endregion + + private void DefaultsButton_Click(object sender, System.EventArgs e) + { + this.SaveConfigToken = this.FileType.CreateDefaultSaveConfigToken(); + } + + private void TokenChangedHandler(object sender, EventArgs e) + { + QueueFileSizeTextUpdate(); + } + + private void QueueFileSizeTextUpdate() + { + callbackDoneEvent.Reset(); + + string computing = PdnResources.GetString("SaveConfigDialog.FileSizeText.Text.Computing"); + this.previewHeader.Text = string.Format(this.fileSizeTextFormat, computing); + this.fileSizeTimer.Change(timerDelayTime, 0); + OnProgress(0); + } + + private volatile bool callbackBusy = false; + private ManualResetEvent callbackDoneEvent = new ManualResetEvent(true); + + private void UpdateFileSizeAndPreview(string tempFileName) + { + if (this.IsDisposed) + { + return; + } + + if (tempFileName == null) + { + string error = PdnResources.GetString("SaveConfigDialog.FileSizeText.Text.Error"); + this.previewHeader.Text = string.Format(this.fileSizeTextFormat, error); + } + else + { + FileInfo fi = new FileInfo(tempFileName); + long fileSize = fi.Length; + this.previewHeader.Text = string.Format(fileSizeTextFormat, Utility.SizeStringFromBytes(fileSize)); + this.documentView.Visible = true; + + // note: see comments for DocumentView.SuspendRefresh() for why we do these two backwards + this.documentView.ResumeRefresh(); + + Document disposeMe = null; + try + { + if (this.disposeDocument && this.documentView.Document != null) + { + disposeMe = this.documentView.Document; + } + + if (this.fileType.IsReflexive(this.SaveConfigToken)) + { + this.documentView.Document = this.Document; + this.documentView.Document.Invalidate(); + this.disposeDocument = false; + } + else + { + FileStream stream = new FileStream(tempFileName, FileMode.Open, FileAccess.Read, FileShare.Read); + + Document previewDoc; + + try + { + Utility.GCFullCollect(); + previewDoc = fileType.Load(stream); + } + + catch + { + previewDoc = null; + TokenChangedHandler(this, EventArgs.Empty); + } + + stream.Close(); + + if (previewDoc != null) + { + this.documentView.Document = previewDoc; + this.disposeDocument = true; + } + + Utility.GCFullCollect(); + } + + try + { + fi.Delete(); + } + + catch + { + } + } + + finally + { + this.documentView.SuspendRefresh(); + + if (disposeMe != null) + { + disposeMe.Dispose(); + } + } + } + } + + private void SetFileSizeProgress(int percent) + { + string computingFormat = PdnResources.GetString("SaveConfigDialog.FileSizeText.Text.Computing.Format"); + string computing = string.Format(computingFormat, percent); + this.previewHeader.Text = string.Format(this.fileSizeTextFormat, computing); + int newPercent = Utility.Clamp(percent, 0, 100); + OnProgress(newPercent); + } + + private void FileSizeProgressEventHandler(object state, ProgressEventArgs e) + { + if (IsHandleCreated) + { + this.BeginInvoke(new Procedure(SetFileSizeProgress), new object[] { (int)e.Percent }); + } + } + + private void FileSizeTimerCallback(object state) + { + try + { + if (!this.IsHandleCreated) + { + return; + } + + if (callbackBusy) + { + this.Invoke(new Procedure(QueueFileSizeTextUpdate)); + } + else + { +#if !DEBUG + try + { +#endif + FileSizeTimerCallbackImpl(state); +#if !DEBUG + } + + // Catch rare instance where BeginInvoke gets called after the form's window handle is destroyed + catch (InvalidOperationException) + { + + } +#endif + } + } + + catch (Exception) + { + // Handle rare race condition where this method just fails because the form is gone + } + } + + private void FileSizeTimerCallbackImpl(object state) + { + if (this.fileSizeTimer == null) + { + return; + } + + this.callbackBusy = true; + +#if !DEBUG + try + { +#endif + if (this.Document != null) + { + string tempName = Path.GetTempFileName(); + FileStream stream = new FileStream(tempName, FileMode.Create, FileAccess.Write, FileShare.Read); + + this.FileType.Save( + this.Document, + stream, + this.SaveConfigToken, + this.scratchSurface, + new ProgressEventHandler(FileSizeProgressEventHandler), + true); + + stream.Flush(); + stream.Close(); + + this.BeginInvoke(new Procedure(UpdateFileSizeAndPreview), new object[] { tempName }); + } +#if !DEBUG + } + + catch + { + this.BeginInvoke(new Procedure(UpdateFileSizeAndPreview), new object[] { null } ); + } + + finally + { +#endif + this.callbackDoneEvent.Set(); + this.callbackBusy = false; +#if !DEBUG + } +#endif + } + + private void CleanupTimer() + { + if (this.fileSizeTimer != null) + { + Do.TryBool(() => this.fileSizeTimer.Dispose()); // get crash reports here sometimes, go figure + this.fileSizeTimer = null; + } + } + + private void BaseOkButton_Click(object sender, System.EventArgs e) + { + using (new WaitCursorChanger(this)) + { + this.callbackDoneEvent.WaitOne(); + } + + CleanupTimer(); + } + + private void BaseCancelButton_Click(object sender, EventArgs e) + { + using (new WaitCursorChanger(this)) + { + callbackDoneEvent.WaitOne(); + } + + CleanupTimer(); + } + + private bool documentMouseDown = false; + private Point lastMouseXY; + private void DocumentView_DocumentMouseDown(object sender, MouseEventArgs e) + { + if (e is StylusEventArgs) + { + return; + } + + if (e.Button == MouseButtons.Left) + { + documentMouseDown = true; + documentView.Cursor = handIconMouseDown; + lastMouseXY = new Point(e.X, e.Y); + } + } + + private void DocumentView_DocumentMouseMove(object sender, MouseEventArgs e) + { + if (e is StylusEventArgs) + { + return; + } + + if (documentMouseDown) + { + Point mouseXY = new Point(e.X, e.Y); + Size delta = new Size(mouseXY.X - lastMouseXY.X, mouseXY.Y - lastMouseXY.Y); + + if (delta.Width != 0 || delta.Height != 0) + { + PointF scrollPos = documentView.DocumentScrollPositionF; + PointF newScrollPos = new PointF(scrollPos.X - delta.Width, scrollPos.Y - delta.Height); + + documentView.DocumentScrollPositionF = newScrollPos; + documentView.Update(); + + lastMouseXY = mouseXY; + lastMouseXY.X -= delta.Width; + lastMouseXY.Y -= delta.Height; + } + } + } + + private void DocumentView_DocumentMouseUp(object sender, MouseEventArgs e) + { + if (e is StylusEventArgs) + { + return; + } + + documentMouseDown = false; + documentView.Cursor = handIcon; + } + } +} diff --git a/src/SaveConfigDialog.resx b/src/SaveConfigDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/SavePaletteDialog.cs b/src/SavePaletteDialog.cs new file mode 100644 index 0000000..574786c --- /dev/null +++ b/src/SavePaletteDialog.cs @@ -0,0 +1,278 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class SavePaletteDialog + : PdnBaseForm + { + private Label typeANameLabel; + private TextBox textBox; + private ListBox listBox; + private Button saveButton; + private Label palettesLabel; + private Button cancelButton; + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + public SavePaletteDialog() + { + InitializeComponent(); + } + + protected override void OnLoad(EventArgs e) + { + ValidatePaletteName(); + base.OnLoad(e); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.Icon != null) + { + Icon icon = this.Icon; + this.Icon = null; + icon.Dispose(); + icon = null; + } + + if (this.components != null) + { + this.components.Dispose(); + this.components = null; + } + } + + base.Dispose(disposing); + } + + public override void LoadResources() + { + this.Text = PdnResources.GetString("SavePaletteDialog.Text"); + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileSaveAsIcon.png").Reference); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + this.saveButton.Text = PdnResources.GetString("Form.SaveButton.Text"); + this.typeANameLabel.Text = PdnResources.GetString("SavePaletteDialog.TypeANameLabel.Text"); + this.palettesLabel.Text = PdnResources.GetString("SavePaletteDialog.PalettesLabel.Text"); + base.LoadResources(); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.typeANameLabel = new System.Windows.Forms.Label(); + this.textBox = new System.Windows.Forms.TextBox(); + this.listBox = new System.Windows.Forms.ListBox(); + this.saveButton = new System.Windows.Forms.Button(); + this.palettesLabel = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // typeANameLabel + // + this.typeANameLabel.AutoSize = true; + this.typeANameLabel.Location = new System.Drawing.Point(5, 8); + this.typeANameLabel.Margin = new System.Windows.Forms.Padding(0); + this.typeANameLabel.Name = "typeANameLabel"; + this.typeANameLabel.Size = new System.Drawing.Size(50, 13); + this.typeANameLabel.TabIndex = 0; + this.typeANameLabel.Text = "infoLabel"; + // + // textBox + // + this.textBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest; + this.textBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; + this.textBox.Location = new System.Drawing.Point(8, 25); + this.textBox.Name = "textBox"; + this.textBox.Size = new System.Drawing.Size(288, 20); + this.textBox.TabIndex = 2; + this.textBox.Validating += new System.ComponentModel.CancelEventHandler(this.TextBox_Validating); + this.textBox.TextChanged += new System.EventHandler(this.TextBox_TextChanged); + // + // palettesLabel + // + this.palettesLabel.AutoSize = true; + this.palettesLabel.Location = new System.Drawing.Point(5, 50); + this.palettesLabel.Margin = new System.Windows.Forms.Padding(0); + this.palettesLabel.Name = "palettesLabel"; + this.palettesLabel.Size = new System.Drawing.Size(35, 13); + this.palettesLabel.TabIndex = 5; + this.palettesLabel.Text = "label1"; + // + // listBox + // + this.listBox.FormattingEnabled = true; + this.listBox.Location = new System.Drawing.Point(8, 67); + this.listBox.Name = "listBox"; + this.listBox.Size = new System.Drawing.Size(289, 108); + this.listBox.Sorted = true; + this.listBox.TabIndex = 3; + this.listBox.SelectedIndexChanged += new System.EventHandler(this.ListBox_SelectedIndexChanged); + // + // saveButton + // + this.saveButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.saveButton.Location = new System.Drawing.Point(8, 185); + this.saveButton.Name = "saveButton"; + this.saveButton.Size = new System.Drawing.Size(75, 23); + this.saveButton.TabIndex = 4; + this.saveButton.Text = "button1"; + this.saveButton.UseVisualStyleBackColor = true; + this.saveButton.FlatStyle = FlatStyle.System; + this.saveButton.Click += new System.EventHandler(this.SaveButton_Click); + // + // cancelButton + // + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(89, 185); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 6; + this.cancelButton.Text = "button1"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.FlatStyle = FlatStyle.System; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // SavePaletteDialog + // + this.AcceptButton = this.saveButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(310, 217); + this.Controls.Add(this.palettesLabel); + this.Controls.Add(this.listBox); + this.Controls.Add(this.textBox); + this.Controls.Add(this.saveButton); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.typeANameLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SavePaletteDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "SavePaletteDialog"; + this.Controls.SetChildIndex(this.typeANameLabel, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.saveButton, 0); + this.Controls.SetChildIndex(this.textBox, 0); + this.Controls.SetChildIndex(this.listBox, 0); + this.Controls.SetChildIndex(this.palettesLabel, 0); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public string PaletteName + { + get + { + return this.textBox.Text; + } + + set + { + this.textBox.Text = value; + } + } + + public string[] PaletteNames + { + set + { + this.listBox.Items.Clear(); + + AutoCompleteStringCollection acsc = new AutoCompleteStringCollection(); + + foreach (string paletteName in value) + { + acsc.Add(paletteName); + this.listBox.Items.Add(paletteName); + } + + this.textBox.AutoCompleteCustomSource = acsc; + this.textBox.AutoCompleteSource = AutoCompleteSource.CustomSource; + } + } + + private void SaveButton_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.OK; + Close(); + } + + private void CancelButton_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + Close(); + } + + private void ListBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (this.listBox.SelectedItem != null) + { + this.textBox.Text = this.listBox.SelectedItem.ToString(); + this.textBox.Focus(); + this.listBox.SelectedItem = null; + } + } + + private void ValidatePaletteName() + { + if (!PaletteCollection.ValidatePaletteName(this.textBox.Text)) + { + this.saveButton.Enabled = false; + + if (!string.IsNullOrEmpty(this.textBox.Text)) + { + this.textBox.BackColor = Color.Red; + } + } + else + { + this.saveButton.Enabled = true; + this.textBox.BackColor = SystemColors.Window; + } + } + + private void TextBox_Validating(object sender, CancelEventArgs e) + { + ValidatePaletteName(); + } + + private void TextBox_TextChanged(object sender, EventArgs e) + { + ValidatePaletteName(); + } + } +} \ No newline at end of file diff --git a/src/SavePaletteDialog.resx b/src/SavePaletteDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/SaveProgressDialog.cs b/src/SaveProgressDialog.cs new file mode 100644 index 0000000..813f8b8 --- /dev/null +++ b/src/SaveProgressDialog.cs @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class SaveProgressDialog + : CallbackWithProgressDialog + { + private FileType fileType; + private Document document; + private Stream stream; + private SaveConfigToken saveConfigToken; + private Surface scratchSurface; + + private void SaveCallback() + { + fileType.Save( + this.document, + this.stream, + this.saveConfigToken, + this.scratchSurface, + new ProgressEventHandler(ProgressHandler), + true); + } + + public SaveProgressDialog(Control owner) + : base(owner, + PdnResources.GetString("SaveProgressDialog.Title"), + PdnResources.GetString("SaveProgressDialog.Description")) + { + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.MenuFileSaveIcon.png").Reference, Utility.TransparentKey); + } + + public void Save(Stream dstStream, Document srcDocument, FileType dstFileType, SaveConfigToken parameters, Surface saveScratchSurface) + { + this.document = srcDocument; + this.fileType = dstFileType; + this.stream = dstStream; + this.saveConfigToken = parameters; + this.scratchSurface = saveScratchSurface; + DialogResult dr = this.ShowDialog(false, !dstFileType.SavesWithProgress , new ThreadStart(SaveCallback)); + } + + private void ProgressHandler(object sender, ProgressEventArgs e) + { + if (!MarqueeMode) + { + Progress = (int)e.Percent; + } + } + } +} diff --git a/src/SelectionDrawMode.cs b/src/SelectionDrawMode.cs new file mode 100644 index 0000000..f37de9d --- /dev/null +++ b/src/SelectionDrawMode.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal enum SelectionDrawMode + { + Normal = 0, + FixedRatio, + FixedSize + } +} diff --git a/src/SelectionDrawModeInfo.cs b/src/SelectionDrawModeInfo.cs new file mode 100644 index 0000000..b3668ad --- /dev/null +++ b/src/SelectionDrawModeInfo.cs @@ -0,0 +1,139 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace PaintDotNet +{ + [Serializable] + internal sealed class SelectionDrawModeInfo + : ICloneable, + IDeserializationCallback + { + private SelectionDrawMode drawMode; + private double width; + private double height; + private MeasurementUnit units; + + public SelectionDrawMode DrawMode + { + get + { + return this.drawMode; + } + } + + public double Width + { + get + { + return this.width; + } + } + + public double Height + { + get + { + return this.height; + } + } + + public MeasurementUnit Units + { + get + { + return this.units; + } + } + + public override bool Equals(object obj) + { + SelectionDrawModeInfo asSDMI = obj as SelectionDrawModeInfo; + + if (asSDMI == null) + { + return false; + } + + return (asSDMI.drawMode == this.drawMode) && (asSDMI.width == this.width) && (asSDMI.height == this.height) && (asSDMI.units == this.units); + } + + public override int GetHashCode() + { + return unchecked(this.drawMode.GetHashCode() ^ this.width.GetHashCode() ^ this.height.GetHashCode() & this.units.GetHashCode()); + } + + public SelectionDrawModeInfo(SelectionDrawMode drawMode, double width, double height, MeasurementUnit units) + { + this.drawMode = drawMode; + this.width = width; + this.height = height; + this.units = units; + } + + public static SelectionDrawModeInfo CreateDefault() + { + return new SelectionDrawModeInfo(SelectionDrawMode.Normal, 4.0, 3.0, MeasurementUnit.Inch); + } + + public SelectionDrawModeInfo CloneWithNewDrawMode(SelectionDrawMode newDrawMode) + { + return new SelectionDrawModeInfo(newDrawMode, this.width, this.height, this.units); + } + + public SelectionDrawModeInfo CloneWithNewWidth(double newWidth) + { + return new SelectionDrawModeInfo(this.drawMode, newWidth, this.height, this.units); + } + + public SelectionDrawModeInfo CloneWithNewHeight(double newHeight) + { + return new SelectionDrawModeInfo(this.drawMode, this.width, newHeight, this.units); + } + + public SelectionDrawModeInfo CloneWithNewWidthAndHeight(double newWidth, double newHeight) + { + return new SelectionDrawModeInfo(this.drawMode, newWidth, newHeight, this.units); + } + + public SelectionDrawModeInfo Clone() + { + return new SelectionDrawModeInfo(this.drawMode, this.width, this.height, this.units); + } + + public SelectionDrawModeInfo CloneWithNewUnits(MeasurementUnit newUnits) + { + return new SelectionDrawModeInfo(this.drawMode, this.width, this.height, newUnits); + } + + object ICloneable.Clone() + { + return Clone(); + } + + void IDeserializationCallback.OnDeserialization(object sender) + { + switch (this.units) + { + case MeasurementUnit.Centimeter: + case MeasurementUnit.Inch: + case MeasurementUnit.Pixel: + break; + + default: + this.units = MeasurementUnit.Pixel; + break; + } + } + } +} diff --git a/src/SelectionRenderer.cs b/src/SelectionRenderer.cs new file mode 100644 index 0000000..f7e8e4e --- /dev/null +++ b/src/SelectionRenderer.cs @@ -0,0 +1,651 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class SelectionRenderer + : SurfaceBoxGraphicsRenderer + { + private const int dancingAntsInterval = 60; + private const double maxCpuTime = 0.2; // max 20% CPU time + private Color tintColor = Color.FromArgb(32, 32, 32, 255); + private static Pen outlinePen1 = null; + private static Pen outlinePen2 = null; + private UserControl2 ownerControl; + private System.Windows.Forms.Timer selectionTimer; + private bool enableOutlineAnimation = true; + private System.ComponentModel.IContainer components = null; + private bool invertedTinting = false; + private bool render = true; // when false, we do not Render() + private PdnGraphicsPath selectedPath; + private Selection selection; + private PdnGraphicsPath zoomInSelectedPath; + private int dancingAntsT = 0; + private int whiteOpacity = 255; + private int lastTickMod = 0; + private Rectangle[] simplifiedRegionForTimer = null; + private double coolOffTimeTickCount = 0.0; + + /// + /// This variable is used to accumulate an invalidation region. It is initialized + /// upon responding to the SelectedPathChanging event that is raised by the + /// DocumentEnvironment. Then, when the SelectedPathChanged event is raised, the + /// full region that needs to be redrawn is accounted for. + /// + private PdnRegion selectionRedrawInterior = PdnRegion.CreateEmpty(); + private PdnGraphicsPath selectionRedrawOutline = new PdnGraphicsPath(); + private DateTime lastFullInvalidate = DateTime.Now; + + protected override void OnVisibleChanged() + { + this.selectionTimer.Enabled = this.Visible; + + if (this.selection != null) + { + Rectangle rect = this.selection.GetBounds(); + Invalidate(rect); + } + } + + public override void OnDestinationSizeChanged() + { + lock (SyncRoot) + { + this.simplifiedRegionForTimer = null; + } + + base.OnDestinationSizeChanged(); + } + + public override void OnSourceSizeChanged() + { + lock (SyncRoot) + { + this.simplifiedRegionForTimer = null; + } + + base.OnSourceSizeChanged(); + } + + public bool EnableOutlineAnimation + { + get + { + return this.enableOutlineAnimation; + } + + set + { + if (this.enableOutlineAnimation != value) + { + this.enableOutlineAnimation = value; + Invalidate(); + } + } + } + + public bool InvertedTinting + { + get + { + return this.invertedTinting; + } + + set + { + if (this.invertedTinting != value) + { + this.invertedTinting = value; + Invalidate(); + } + } + } + + public Color TintColor + { + get + { + return this.tintColor; + } + + set + { + if (value != this.tintColor) + { + this.tintColor = value; + + if (this.interiorBrush != null) + { + this.interiorBrush.Dispose(); + this.interiorBrush = null; + } + + Invalidate(); + } + } + } + + private void OnSelectionChanging(object sender, EventArgs e) + { + this.render = false; + + if (!this.selectionTimer.Enabled) + { + this.selectionTimer.Enabled = true; + } + } + + private void OnSelectionChanged(object sender, EventArgs e) + { + this.render = true; + PdnGraphicsPath path = this.selection.CreatePath(); //this.selection.GetPathReadOnly(); + + if (this.selectedPath == null) + { + Invalidate(); + } + else + { + this.selectedPath.Dispose(); // + this.selectedPath = null; + } + + bool fullInvalidate = false; + this.selectedPath = path; + + // HACK: Sometimes the selection leaves behind artifacts. So do a full invalidate + // every 1 second. + if (this.selectedPath.PointCount > 10 && (DateTime.Now - lastFullInvalidate > new TimeSpan(0, 0, 0, 1, 0))) + { + fullInvalidate = true; + } + + // if we're moving to a simpler selection region ... + if (this.selectedPath == null)// || this.selectedPath.PointCount == 0) + { + // then invalidate everything + fullInvalidate = true; + } + else + { + // otherwise, be intelligent about it and only redraw the 'new' area + PdnRegion xorMe = new PdnRegion(this.selectedPath); + selectionRedrawInterior.Xor(xorMe); + xorMe.Dispose(); + } + + float ratio = 1.0f / (float)OwnerList.ScaleFactor.Ratio; + int ratioInt = (int)Math.Ceiling(ratio); + + if (this.Visible && (this.EnableSelectionOutline || this.EnableSelectionTinting)) + { + using (PdnRegion simplified = Utility.SimplifyAndInflateRegion(selectionRedrawInterior, Utility.DefaultSimplificationFactor, 2 * ratioInt)) + { + Invalidate(simplified); + } + } + + if (fullInvalidate) + { + Rectangle rect = Rectangle.Inflate(Rectangle.Truncate(selectionRedrawOutline.GetBounds2()), 1, 1); + Invalidate(rect); + lastFullInvalidate = DateTime.Now; + } + + + this.selectionRedrawInterior.Dispose(); + this.selectionRedrawInterior = null; + + if (this.zoomInSelectedPath != null) + { + this.zoomInSelectedPath.Dispose(); + this.zoomInSelectedPath = null; + } + + this.simplifiedRegionForTimer = null; + + // prepare for next call + if (this.selectedPath != null && !this.selectedPath.IsEmpty) + { + this.selectionRedrawOutline = (PdnGraphicsPath)this.selectedPath.Clone(); + this.selectionRedrawInterior = new PdnRegion(this.selectedPath); + } + else + { + if (invertedTinting) + { + this.selectionRedrawInterior = new PdnRegion(new Rectangle(0, 0, this.SourceSize.Width, this.SourceSize.Height)); + } + else + { + this.selectionRedrawInterior = new PdnRegion(); + this.selectionRedrawInterior.MakeEmpty(); + } + + Invalidate(); + this.selectionRedrawOutline = new PdnGraphicsPath(); + } + } + + /// + /// When we zoom in, we want to "stair-step" the selected path. + /// + /// + private PdnGraphicsPath GetZoomInPath() + { + lock (this.SyncRoot) + { + if (this.zoomInSelectedPath == null) + { + if (this.selectedPath == null) + { + this.zoomInSelectedPath = new PdnGraphicsPath(); + } + else + { + this.zoomInSelectedPath = this.selection.CreatePixelatedPath(); + } + } + + return this.zoomInSelectedPath; + } + } + + private PdnGraphicsPath GetAppropriateRenderPath() + { + if (OwnerList.ScaleFactor.Ratio >= 1.01) + { + return GetZoomInPath(); + } + else + { + return this.selectedPath; + } + } + + private Timing timer = new Timing(); + private double renderTime = 0.0; + + public override bool ShouldRender() + { + return (this.render && (this.EnableSelectionOutline || this.EnableSelectionTinting)); + } + + public override void RenderToGraphics(Graphics g, Point offset) + { + double start = timer.GetTickCountDouble(); + + lock (SyncRoot) + { + PdnGraphicsPath path = GetAppropriateRenderPath(); + + if (path == null || path.IsEmpty) + { + this.render = false; // will be reset next time selection changes + } + else + { + g.TranslateTransform(-offset.X, -offset.Y); + DrawSelection(g, path); + } + + double end = timer.GetTickCountDouble(); + this.renderTime += (end - start); + } + } + + public SelectionRenderer(SurfaceBoxRendererList ownerList, Selection selection) + : this(ownerList, selection, null) + { + } + + public SelectionRenderer(SurfaceBoxRendererList ownerList, Selection selection, UserControl2 ownerControl) + : base(ownerList) + { + this.ownerControl = ownerControl; + this.selection = selection; + this.selection.Changing += new EventHandler(OnSelectionChanging); + this.selection.Changed += new EventHandler(OnSelectionChanged); + this.components = new System.ComponentModel.Container(); + this.selectionTimer = new System.Windows.Forms.Timer(this.components); + this.selectionTimer.Enabled = true; + this.selectionTimer.Interval = dancingAntsInterval / 2; + this.selectionTimer.Tick += new System.EventHandler(this.SelectionTimer_Tick); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (this.components != null) + { + this.components.Dispose(); + this.components = null; + } + + if (this.selectionTimer != null) + { + this.selectionTimer.Dispose(); + this.selectionTimer = null; + } + + if (this.zoomInSelectedPath != null) + { + this.zoomInSelectedPath.Dispose(); + this.zoomInSelectedPath = null; + } + } + + base.Dispose (disposing); + } + + private Brush interiorBrush; + private Brush InteriorBrush + { + get + { + if (interiorBrush == null) + { + interiorBrush = new SolidBrush(tintColor); + } + + return interiorBrush; + } + } + + private bool enableSelectionOutline = true; + public bool EnableSelectionOutline + { + get + { + return enableSelectionOutline; + } + + set + { + if (this.enableSelectionOutline != value) + { + enableSelectionOutline = value; + Invalidate(); + } + } + } + + private bool enableSelectionTinting = true; + public bool EnableSelectionTinting + { + get + { + return enableSelectionTinting; + } + + set + { + if (enableSelectionTinting != value) + { + enableSelectionTinting = value; + Invalidate(); + } + } + } + + /// + /// This is a silly function name. + /// + public void ResetOutlineWhiteOpacity() + { + if (this.whiteOpacity > 0) + { + Invalidate(); + } + + this.whiteOpacity = 0; + } + + private void DrawSelectionOutline(Graphics g, PdnGraphicsPath outline) + { + if (outline == null) + { + return; + } + + if (outlinePen1 == null) + { + outlinePen1 = new Pen(Color.FromArgb(160, Color.Black), 1.0f); + outlinePen1.Alignment = PenAlignment.Outset; + outlinePen1.LineJoin = LineJoin.Bevel; + outlinePen1.Width = -1; + } + + if (outlinePen2 == null) + { + outlinePen2 = new Pen(Color.White, 1.0f); + outlinePen2.Alignment = PenAlignment.Outset; + outlinePen2.LineJoin = LineJoin.Bevel; + outlinePen2.MiterLimit = 2; + outlinePen2.Width = -1; + outlinePen2.DashStyle = DashStyle.Dash; + outlinePen2.DashPattern = new float[] { 4, 4 }; + outlinePen2.Color = Color.White; + outlinePen2.DashOffset = 4.0f; + } + + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.None; + + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.AntiAlias; + + outline.Draw(g, outlinePen1); + + float offset = (float)((double)dancingAntsT / OwnerList.ScaleFactor.Ratio); + outlinePen2.DashOffset += offset; + + if (whiteOpacity != 0) + { + outlinePen2.Color = Color.FromArgb(whiteOpacity, Color.White); + outline.Draw(g, outlinePen2); + } + + outlinePen2.DashOffset -= offset; + + g.SmoothingMode = oldSM; + g.PixelOffsetMode = oldPOM; + } + + private void DrawSelectionTinting(Graphics g, PdnGraphicsPath outline) + { + if (outline == null) + { + return; + } + + CompositingMode oldCM = g.CompositingMode; + g.CompositingMode = CompositingMode.SourceOver; + + SmoothingMode oldSM = g.SmoothingMode; + g.SmoothingMode = SmoothingMode.AntiAlias; + + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.None; + + Region oldClipRegion = null; + RectangleF outlineBounds = outline.GetBounds(); + + if (outlineBounds.Left < 0 || + outlineBounds.Top < 0 || + outlineBounds.Right >= this.SourceSize.Width || + outlineBounds.Bottom >= this.SourceSize.Height) + { + oldClipRegion = g.Clip; + + Region newClipRegion = oldClipRegion.Clone(); + newClipRegion.Intersect(new Rectangle(0, 0, this.SourceSize.Width, this.SourceSize.Height)); + g.Clip = newClipRegion; + newClipRegion.Dispose(); + } + + g.FillPath(InteriorBrush, outline); + + if (oldClipRegion != null) + { + g.Clip = oldClipRegion; + oldClipRegion.Dispose(); + } + + g.PixelOffsetMode = oldPOM; + g.SmoothingMode = oldSM; + g.CompositingMode = oldCM; + } + + private void DrawSelection(Graphics gdiG, PdnGraphicsPath outline) + { + if (outline == null) + { + return; + } + + float ratio = (float)OwnerList.ScaleFactor.Ratio; + gdiG.ScaleTransform(ratio, ratio); + + if (EnableSelectionTinting) + { + PdnGraphicsPath outline2; + + if (invertedTinting) + { + outline2 = (PdnGraphicsPath)outline.Clone(); + outline2.AddRectangle(new Rectangle(-1, -1, this.SourceSize.Width + 1, this.SourceSize.Height + 1)); + outline2.CloseAllFigures(); + } + else + { + outline2 = outline; + } + + DrawSelectionTinting(gdiG, outline2); + + if (invertedTinting) + { + outline2.Dispose(); + } + } + + if (EnableSelectionOutline) + { + DrawSelectionOutline(gdiG, outline); + } + + gdiG.ScaleTransform(1 / ratio, 1 / ratio); + } + + private void SelectionTimer_Tick(object sender, System.EventArgs e) + { + if (this.IsDisposed || this.ownerControl.IsDisposed) + { + return; + } + + if (this.selectedPath == null || this.selectedPath.IsEmpty) + { + this.selectionTimer.Enabled = false; + return; + } + + if (!this.enableOutlineAnimation) + { + return; + } + + if (this.timer.GetTickCountDouble() < this.coolOffTimeTickCount) + { + return; + } + + if (this.ownerControl != null && this.ownerControl.IsMouseCaptured()) + { + return; + } + + Form form = this.ownerControl.FindForm(); + if (form != null && form.WindowState == FormWindowState.Minimized) + { + return; + } + + int presentTickMod = (int)((Utility.GetTimeMs() / dancingAntsInterval) % 2); + + if (presentTickMod != lastTickMod) + { + lastTickMod = presentTickMod; + dancingAntsT = unchecked(dancingAntsT + 1); + + if (this.simplifiedRegionForTimer == null) + { + using (PdnGraphicsPath invalidPath = (PdnGraphicsPath)selectedPath.Clone()) + { + invalidPath.CloseAllFigures(); + + float ratio = 1.0f / (float)OwnerList.ScaleFactor.Ratio; + int inflateAmount = (int)Math.Ceiling(ratio); + + this.simplifiedRegionForTimer = Utility.SimplifyTrace(invalidPath, 50); + Utility.InflateRectanglesInPlace(this.simplifiedRegionForTimer, inflateAmount); + } + } + + try + { + foreach (Rectangle rect in this.simplifiedRegionForTimer) + { + Invalidate(rect); + } + } + + catch (ObjectDisposedException) + { + try + { + this.selectionTimer.Enabled = false; + } + + catch (Exception) + { + // Ignore error + } + } + + if (this.ownerControl == null || (this.ownerControl != null && !this.ownerControl.IsMouseCaptured())) + { + whiteOpacity = Math.Min(whiteOpacity + 16, 255); + } + } + + // If it takes "too long" to render the dancing ants, then we institute + // a cooling-off period during which we will not render the ants. + // This will curb the CPU usage by a few percent, which will avoid us + // monopolizing the CPU. + double maxRenderTime = (double)dancingAntsInterval * maxCpuTime; + + if (renderTime > maxRenderTime) + { + double coolOffTime = renderTime / maxRenderTime; + this.coolOffTimeTickCount = timer.GetTickCountDouble() + coolOffTime; + } + + this.renderTime = 0.0; + } + } +} diff --git a/src/SettingNames.cs b/src/SettingNames.cs new file mode 100644 index 0000000..dd61f95 --- /dev/null +++ b/src/SettingNames.cs @@ -0,0 +1,321 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; + +namespace PaintDotNet +{ + /// + /// Symbolic constants for our settings. Settings are stored in CurrentUser unless + /// otherwise specified. + /// + internal sealed class SettingNames + { + /// + /// The width of the main window (MainForm). + /// + /// + /// Written on app close, and read on app startup. + /// + public const string Width = "Width"; + + /// + /// The height of the main window (MainForm). + /// + /// + /// Written on app close, and read on app startup. + /// + public const string Height = "Height"; + + /// + /// The y-coordinate of the top edge of the main window (MainForm). + /// + /// + /// Written on app close, and read on app startup. + /// + public const string Top = "Top"; + + /// + /// The x-coordinate of the left edge of the main window (MainForm). + /// + /// + /// Written on app close, and read on app startup. + /// + public const string Left = "Left"; + + /// + /// The maximum number of items to store in the MRU list (File -> Open Recent). + /// + /// + /// Read or written whenever the File -> Open Recent menu is opened. + /// + public const string MruMax = "MRUMax"; + + /// + /// The window state of the main window (MainForm). This can be either Maximized or Normal, + /// and corresponds to the FormWindowState enumeration. + /// + /// + /// Written on app close, and read on app startup. + /// + public const string WindowState = "WindowState"; + + /// + /// The state of whether rulers are enabled in the DocumentWorkspace. + /// + /// + /// Written to whenever the value is changed, and read on app startup. + /// + public const string Rulers = "Rulers"; + + /// + /// The unit of measurement the user has selected via the WorkspaceOptionsConfigWidget. + /// + /// + /// Written to whenever the value is changed, and read on app startup. + /// + public const string Units = "Units"; + + /// + /// The type of font smoothing the user has chosen in the TextConfigStrip. + /// + /// + /// Written to whenever the value is changed, and read on app startup. + /// + public const string FontSmoothing = "FontSmoothing"; + + /// + /// The last unit of measurement the user selected via the WorkspaceOptionsConfigWidget + /// that was NOT pixels. + /// + /// + /// Written whenever the user changes the setting, and read on app startup. + /// + public const string LastNonPixelUnits = "LastNonPixelUnits"; + + public static MeasurementUnit GetLastNonPixelUnits() + { + string stringValue = Settings.CurrentUser.GetString(LastNonPixelUnits, MeasurementUnit.Inch.ToString()); + MeasurementUnit units; + + try + { + units = (MeasurementUnit)Enum.Parse(typeof(MeasurementUnit), stringValue, true); + } + + catch + { + units = MeasurementUnit.Inch; + } + + return units; + } + + /// + /// The state of whether the grid is enabled in the DocumentWorkspace. + /// + /// + /// Written to whenever the value is changed, and read on app startup. + /// + public const string DrawGrid = "DrawGrid"; + + /// + /// The state of whether translucent windows are enabled (Window -> Translucent). + /// + /// + /// This setting is read whenever the Window menu is opened, and written to + /// whenever the user changes the setting. + /// + public const string TranslucentWindows = "TranslucentWindows"; + + /// + /// The state of whether the Tools floating form is visible. + /// + /// + /// Written on app close, and read on app startup. + /// + public const string ToolsFormVisible = "ToolsForm.Visible"; + + /// + /// The state of whether the Colors floating form is visible. + /// + /// + /// Written on app close, and read on app startup. + /// + public const string ColorsFormVisible = "ColorsForm.Visible"; + + /// + /// The state of whether the History floating form is visible. + /// + /// + /// Written on app close, and read on app startup. + /// + public const string HistoryFormVisible = "HistoryForm.Visible"; + + /// + /// The state of whether the Layers floating form is visible. + /// + /// + /// Written on app close, and read on app startup. + /// + public const string LayersFormVisible = "LayersForm.Visible"; + + /// + /// The last resampling algorithm that was selected in the Resize dialog. + /// + /// + /// Read from whenever a Resize dialog is shown to the user, and written + /// to whenever the user closes the Resize dialog without cancelling it. + /// + public const string LastResamplingMethod = "LastResamplingMethod"; + + /// + /// The last state of the "Maintain Aspect" check box in the Resize dialog. + /// + /// + /// Read from whenever a dialog is shown to the user and written to whenever the + /// user closes the dialog without cancelling it. + /// + public const string LastMaintainAspectRatio = "LastMaintainAspectRatio"; + + /// + /// The last state of the "Maintain Aspect" check box in the Canvas Size dialog. + /// + /// + /// Read from whenever the dialog is shown to the user and written to whenever the + /// user closes the dialog without cancelling it. + /// + public const string LastMaintainAspectRatioCS = "LastMaintainAspectRatioCS"; + + /// + /// The last state of the anchor edge in the Canvas Size dialog. + /// + /// + /// Read from whenever the dialog is shown to the user and written to whenever the + /// user closes the dialog without cancelling it. + /// + public const string LastCanvasSizeAnchorEdge = "LastCanvasSizeAnchorEdge"; + + public static AnchorEdge GetLastCanvasSizeAnchorEdge() + { + string stringValue = Settings.CurrentUser.GetString(LastCanvasSizeAnchorEdge, AnchorEdge.TopLeft.ToString()); + AnchorEdge edge; + + try + { + edge = (AnchorEdge)Enum.Parse(typeof(AnchorEdge), stringValue, true); + } + + catch + { + edge = AnchorEdge.TopLeft; + } + + return edge; + } + + /// + /// The last state of the "Maintain Aspect" check box in the New File dialog. + /// + /// + /// Read from whenever the dialog is shown to the user and written to whenever the + /// user closes the dialog without cancelling it. + /// + public const string LastMaintainAspectRatioNF = "LastMaintainAspectRatioNF"; + + /// + /// The last directory that was visible in a Open, Save, or Import dialog that + /// the user did not cancel. + /// + /// + /// Read from whenever an Open, Save, or Import dialog is shown to the user. + /// Written to whenever one of those dialogs is closed without cancelling it. + /// + public const string LastFileDialogDirectory = "LastFileDialogDirectory"; + + /// + /// The state of whether Paint.NET should automatically check for updates once per week. + /// + public const string AutoCheckForUpdates = "CHECKFORUPDATES"; + + /// + /// Whether or not Paint.NET should inform the user of pre-release versions (Betas) of Paint.NET. + /// + /// + /// This is a SystemWide setting and may not be changed by non-admins. + /// + public const string AlsoCheckForBetas = "CHECKFORBETAS"; + + /// + /// The last time that we checked for updates. This is a 64-bit value that matches + /// the Ticks property of the DateTime structure. + /// + /// + /// This is a CurrentUser setting. + /// + public const string LastUpdateCheckTimeTicks = "LastUpdateCheckTimeTicks"; + + /// + /// After installation of the MSI, we set this registry key to keep track of where the + /// file was stored. This string must be a file name (not path information), and always + /// refers to a file in the %TEMP% directory. The filename must end with a .exe or .msi + /// extension. + /// + /// + /// This is a CurrentUser setting. + /// + public const string UpdateMsiFileName = "UpdateMsiFileName"; + + /// + /// Determines the resources file that is used to load strings from. Defaults to the + /// system locale. + /// + public const string LanguageName = "LanguageName"; + + /// + /// Written to the registry to advertise what directory Paint.NET is installed to. + /// + /// + /// This is a SystemWide setting and may not be changed by non-admins. + /// + public const string InstallDirectory = "TARGETDIR"; + + /// + /// The current palette is saved here, in the same format used for the palette files. + /// + /// + /// If this setting is missing or invalid, the default palette will be used. + /// Read from at app startup, and written to whenever the current palette is changed. + /// + public const string CurrentPalette = "CurrentPalette"; + + /// + /// The Type name of the default (startup) tool. + /// + /// + /// Read from at app startup, and written to whenever the default is saved + /// from the ChooseToolDefaultsDialog form. + /// + public const string DefaultToolTypeName = "DefaultToolTypeName"; + + /// + /// A serialized AppEnvironment that is loaded into the toolbar at startup. + /// + /// + /// Read from at app startup, and written to whenever the default is saved + /// from the ChooseToolDefaultsDialog form. + /// + public const string DefaultAppEnvironment = "DefaultAppEnvironment"; + + private SettingNames() + { + } + } +} diff --git a/src/ShapeDrawType.cs b/src/ShapeDrawType.cs new file mode 100644 index 0000000..0dfc27c --- /dev/null +++ b/src/ShapeDrawType.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; + +namespace PaintDotNet +{ + [Flags] + internal enum ShapeDrawType + { + Interior = 1, + Outline = 2, + Both = 3 + } +} diff --git a/src/SharpZipLib/bin/ICSharpCode.SharpZipLib.dll b/src/SharpZipLib/bin/ICSharpCode.SharpZipLib.dll new file mode 100644 index 0000000..bcdb00e Binary files /dev/null and b/src/SharpZipLib/bin/ICSharpCode.SharpZipLib.dll differ diff --git a/src/ShellExtension/ClassFactory.cpp b/src/ShellExtension/ClassFactory.cpp new file mode 100644 index 0000000..c88cf76 --- /dev/null +++ b/src/ShellExtension/ClassFactory.cpp @@ -0,0 +1,137 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma strict_gs_check(on) +#pragma warning (disable: 4530) +#include "ClassFactory.h" +#include "PdnShell.h" +#include "PdnGuid.h" +#include "PdnShellExtension.h" + +CClassFactory::CClassFactory(CLSID clsid) +{ + Constructor(clsid); + return; +} + +void CClassFactory::Constructor(CLSID clsid) +{ + m_clsidObject = clsid; + m_lRefCount = 1; + InterlockedIncrement(&g_lRefCount); + return; +} + +CClassFactory::~CClassFactory(void) +{ + Destructor(); + return; +} + +void CClassFactory::Destructor(void) +{ + InterlockedDecrement (&g_lRefCount); + return; +} + +STDMETHODIMP CClassFactory::QueryInterface(REFIID riid, LPVOID *ppReturn) +{ + TraceEnter(); + TraceOut("riid=%S", GuidToString(riid)); + *ppReturn = NULL; + + if (IsEqualCLSID (riid, IID_IUnknown)) + { + *ppReturn = this; + } + else if (IsEqualCLSID (riid, IID_IClassFactory)) + { + *ppReturn = (IClassFactory *)this; + } + + if (*ppReturn != NULL) + { + (*(LPUNKNOWN *)ppReturn)->AddRef(); + TraceLeaveHr(S_OK); + return S_OK; + } + + TraceLeaveHr(E_NOINTERFACE); + return E_NOINTERFACE; +} + +STDMETHODIMP_(DWORD) CClassFactory::AddRef() +{ + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(DWORD) CClassFactory::Release() +{ + DWORD dwNewRC = InterlockedDecrement(&m_lRefCount); + + if (0 == dwNewRC) + { + delete this; + return 0; + } + else + { + return dwNewRC; + } +} + +STDMETHODIMP CClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) +{ + void *pvResult = NULL; + HRESULT hr = S_OK; + + TraceEnter(); + TraceOut("riid=%S", GuidToString(riid)); + + if (SUCCEEDED(hr)) + { + if (NULL == ppvObject) + { + hr = E_INVALIDARG; + } + } + + if (SUCCEEDED(hr)) + { + if (pUnkOuter != NULL) + { + hr = CLASS_E_NOAGGREGATION; + } + } + + CPdnShellExtension *pShellExtension = NULL; + if (SUCCEEDED(hr)) + { + pShellExtension = new CPdnShellExtension(); + + if (NULL == pShellExtension) + { + hr = E_OUTOFMEMORY; + } + } + + if (SUCCEEDED(hr)) + { + hr = pShellExtension->QueryInterface(riid, ppvObject); + pShellExtension->Release(); + } + + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CClassFactory::LockServer(BOOL fLock) +{ + return E_NOTIMPL; +} diff --git a/src/ShellExtension/ClassFactory.h b/src/ShellExtension/ClassFactory.h new file mode 100644 index 0000000..dc61a82 --- /dev/null +++ b/src/ShellExtension/ClassFactory.h @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +class CClassFactory + : public IClassFactory +{ +public: + CClassFactory (CLSID clsid); + ~CClassFactory (); + + // IUnknown methods + STDMETHODIMP QueryInterface (REFIID riid, void **ppvObject); + STDMETHODIMP_(DWORD) AddRef (); + STDMETHODIMP_(DWORD) Release (); + + // IClassFactory methods + STDMETHODIMP CreateInstance (IUnknown *pUnkOuter, REFIID riid, void **ppvObject); + STDMETHODIMP LockServer (BOOL fLock); + + void Constructor (CLSID ClassID); + void Destructor (void); + +protected: + LONG m_lRefCount; + +private: + CLSID m_clsidObject; +}; + diff --git a/src/ShellExtension/MemoryStream.cpp b/src/ShellExtension/MemoryStream.cpp new file mode 100644 index 0000000..0c44c81 --- /dev/null +++ b/src/ShellExtension/MemoryStream.cpp @@ -0,0 +1,367 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma strict_gs_check(on) +#include "MemoryStream.h" +#include "PdnShell.h" +#include + +CMemoryStream::CMemoryStream(BYTE *pbBuffer, int nSize) + : m_pbBuffer(pbBuffer), + m_nSize(nSize), + m_nPos(0) +{ + TraceEnter(); + TraceOut("m_nSize=%d", m_nSize); + m_lRefCount = 1; + + SYSTEMTIME st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &m_ftCreation); + SystemTimeToFileTime(&st, &m_ftModified); + SystemTimeToFileTime(&st, &m_ftAccessed); + TraceLeave(); +} + +CMemoryStream::~CMemoryStream() +{ +} + +// IUnknown methods +STDMETHODIMP CMemoryStream::QueryInterface(REFIID iid, void **ppvObject) +{ + TraceEnter(); + + TraceOut("%S", GuidToString(iid)); + + if (NULL == ppvObject) + { + return E_INVALIDARG; + } + + if (IsEqualCLSID(iid, IID_IStream)) + { + AddRef(); + *ppvObject = this; + return S_OK; + } + else + { + return E_NOINTERFACE; + } +} + +STDMETHODIMP_(DWORD) CMemoryStream::AddRef() +{ + TraceEnter(); + return InterlockedIncrement(&m_lRefCount); +} + +STDMETHODIMP_(DWORD) CMemoryStream::Release() +{ + TraceEnter(); + DWORD dwNewRefCount = InterlockedDecrement(&m_lRefCount); + + if (0 == dwNewRefCount) + { + delete this; + } + + return dwNewRefCount; +} + +// IStream members +STDMETHODIMP CMemoryStream::Clone(IStream **ppstm) +{ + TraceEnter(); + if (NULL == ppstm) + { + return E_INVALIDARG; + } + + CMemoryStream *pMemoryStream = new CMemoryStream(m_pbBuffer, m_nSize); + + if (NULL == pMemoryStream) + { + return E_INVALIDARG; + } + + LARGE_INTEGER dlibMove; + dlibMove.QuadPart = m_nPos; + DWORD dwOrigin = STREAM_SEEK_SET; + ULARGE_INTEGER dlibNewPosition; + + HRESULT hr = pMemoryStream->Seek(dlibMove, dwOrigin, &dlibNewPosition); + + if (FAILED(hr)) + { + pMemoryStream->Release(); + return hr; + } + else + { + *ppstm = (IStream *)pMemoryStream; + return S_OK; + } +} + +STDMETHODIMP CMemoryStream::Commit(DWORD grfCommitFlags) +{ + HRESULT hr = S_OK; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CMemoryStream::CopyTo(IStream *pstm, + ULARGE_INTEGER cb, + ULARGE_INTEGER *pcbRead, + ULARGE_INTEGER *pcbWritten) +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CMemoryStream::LockRegion(ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, + DWORD dwLockType) +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CMemoryStream::Read(void *pv, + ULONG cb, + ULONG *pcbRead) +{ + TraceEnter(); + + if (NULL == pv) + { + TraceLeaveHr(STG_E_INVALIDPOINTER); + return STG_E_INVALIDPOINTER; + } + + TraceOut("pv=%p, cb=%u, pcbRead=%p, m_nPos=%d", pv, cb, pcbRead, m_nPos); + + BYTE *pbEnd = m_pbBuffer + m_nSize; + BYTE *pbReqStart = m_pbBuffer + m_nPos; + BYTE *pbReqEnd = pbReqStart + cb; + ULONG nBytesToGet = (ULONG)(min(pbEnd - pbReqStart, pbReqEnd - pbReqStart)); + + memcpy(pv, pbReqStart, nBytesToGet); + + if (NULL != pcbRead) + { + *pcbRead = nBytesToGet; + } + + m_nPos += nBytesToGet; + + TraceOut("m_nPos=%d", m_nPos); + + SYSTEMTIME st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &m_ftAccessed); + + TraceLeaveHr(S_OK); + return S_OK; +} + +STDMETHODIMP CMemoryStream::Write(const void *pv, + ULONG cb, + ULONG *pcbWritten) +{ + TraceEnter(); + + if (NULL == pv) + { + return STG_E_INVALIDPOINTER; + } + + TraceOut("pv=%p, cb=%u, pcbWritten=%p", pv, cb, pcbWritten); + + BYTE *pbEnd = m_pbBuffer + m_nSize; + BYTE *pbReqStart = m_pbBuffer + m_nPos; + BYTE *pbReqEnd = pbReqStart + cb; + ULONG nBytesToSet = (ULONG)(min(pbEnd, pbReqEnd) - pbReqStart); + + memcpy(pbReqStart, pv, nBytesToSet); + + if (NULL != pcbWritten) + { + *pcbWritten = nBytesToSet; + } + + m_nPos += nBytesToSet; + + TraceOut("m_nPos=%d", m_nPos); + + SYSTEMTIME st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &m_ftModified); + + if (nBytesToSet < cb) + { + TraceLeaveHr(STG_E_MEDIUMFULL); + return STG_E_MEDIUMFULL; + } + else + { + TraceLeaveHr(S_OK); + return S_OK; + } +} + +STDMETHODIMP CMemoryStream::Revert() +{ + HRESULT hr = S_OK; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CMemoryStream::Seek(LARGE_INTEGER dlibMove, + DWORD dwOrigin, + ULARGE_INTEGER *plibNewPosition) +{ + HRESULT hr = S_OK; + TraceEnter(); + + TraceOut("dlibMove=%Ld, dwOrigin=%d", dlibMove.QuadPart, dwOrigin); + + if (dlibMove.QuadPart > INT_MAX || dlibMove.QuadPart < INT_MIN) + { + hr = STG_E_INVALIDFUNCTION; + } + else + { + int nMove = (int)dlibMove.QuadPart; + TraceOut("nMove = %d", nMove); + int nNewPos = 0; + + switch (dwOrigin) + { + case STREAM_SEEK_SET: + if (nMove >= 0 && nMove < m_nSize) + { + m_nPos = nMove; + + if (NULL != plibNewPosition) + { + plibNewPosition->QuadPart = (__int64)m_nPos; + } + } + else + { + hr = STG_E_INVALIDFUNCTION; + } + + break; + + case STREAM_SEEK_CUR: + nNewPos = m_nPos + nMove; + + if (nNewPos >= 0 && nNewPos < m_nSize) + { + m_nPos = nNewPos; + + if (NULL != plibNewPosition) + { + plibNewPosition->QuadPart = (__int64)m_nPos; + } + } + else + { + hr = STG_E_INVALIDFUNCTION; + } + + break; + + case STREAM_SEEK_END: + nNewPos = m_nSize - 1 + nMove; + + if (nNewPos >= 0 && nNewPos < m_nSize) + { + m_nPos = nNewPos; + + if (NULL != plibNewPosition) + { + plibNewPosition->QuadPart = (__int64)m_nPos; + } + } + else + { + hr = STG_E_INVALIDFUNCTION; + } + + break; + + default: + hr = STG_E_INVALIDFUNCTION; + break; + } + } + + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CMemoryStream::SetSize(ULARGE_INTEGER libNewSize) +{ + return E_NOTIMPL; +} + +STDMETHODIMP CMemoryStream::Stat(STATSTG *pstatstg, + DWORD grfStatFlag) +{ + TraceEnter(); + + TraceOut("grfStatFlag=%u", grfStatFlag); + + if (NULL == pstatstg) + { + return STG_E_INVALIDPOINTER; + } + + ZeroMemory(pstatstg, sizeof(*pstatstg)); + + if (grfStatFlag != STATFLAG_NONAME) + { + WCHAR wszName[64]; + wsprintfW(wszName, L"%p", m_pbBuffer); + size_t nSize = 1 + wcslen(wszName); + pstatstg->pwcsName = (LPOLESTR)CoTaskMemAlloc(nSize); + wcscpy_s(pstatstg->pwcsName, nSize, wszName); + } + + pstatstg->cbSize.QuadPart = (ULONGLONG)m_nSize; + pstatstg->type = STGTY_STREAM; + pstatstg->ctime = m_ftCreation; + pstatstg->mtime = m_ftModified; + pstatstg->atime = m_ftAccessed; + pstatstg->grfMode = STGM_READ; + pstatstg->grfLocksSupported = 0; + pstatstg->clsid = CLSID_NULL; + pstatstg->grfStateBits = 0; + pstatstg->reserved = 0; + + return S_OK; +} + +STDMETHODIMP CMemoryStream::UnlockRegion(ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, + DWORD dwLockType) +{ + return E_NOTIMPL; +} diff --git a/src/ShellExtension/MemoryStream.h b/src/ShellExtension/MemoryStream.h new file mode 100644 index 0000000..b0fdbf0 --- /dev/null +++ b/src/ShellExtension/MemoryStream.h @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +class CMemoryStream + : public IStream +{ +public: + CMemoryStream(BYTE *pbBuffer, int nSize); + ~CMemoryStream(); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject); + STDMETHODIMP_(DWORD) AddRef(); + STDMETHODIMP_(DWORD) Release(); + + // ISequentialStream members + STDMETHODIMP Read(void *pv, + ULONG cb, + ULONG *pcbRead); + + STDMETHODIMP Write(const void *pv, + ULONG cb, + ULONG *pcbWritten); + + // IStream members + STDMETHODIMP Clone(IStream **ppstm); + STDMETHODIMP Commit(DWORD grfCommitFlags); + + STDMETHODIMP CopyTo(IStream *pstm, + ULARGE_INTEGER cb, + ULARGE_INTEGER *pcbRead, + ULARGE_INTEGER *pcbWritten); + + STDMETHODIMP LockRegion(ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, + DWORD dwLockType); + + + STDMETHODIMP Revert(); + + STDMETHODIMP Seek(LARGE_INTEGER dlibMove, + DWORD dwOrigin, + ULARGE_INTEGER *plibNewPosition); + + STDMETHODIMP SetSize(ULARGE_INTEGER libNewSize); + + STDMETHODIMP Stat(STATSTG *pstatstg, + DWORD grfStatFlag); + + STDMETHODIMP UnlockRegion(ULARGE_INTEGER libOffset, + ULARGE_INTEGER cb, + DWORD dwLockType); + +protected: + volatile LONG m_lRefCount; + +private: + BYTE *m_pbBuffer; + int m_nSize; + int m_nPos; + FILETIME m_ftCreation; + FILETIME m_ftModified; + FILETIME m_ftAccessed; +}; diff --git a/src/ShellExtension/PdnGuid.h b/src/ShellExtension/PdnGuid.h new file mode 100644 index 0000000..e43cf1e --- /dev/null +++ b/src/ShellExtension/PdnGuid.h @@ -0,0 +1,12 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// {D292F82A-50BE-4351-96CC-E86F3F8049DA} +DEFINE_GUID(CLSID_PdnShellExtension, 0xd292f82a, 0x50be, 0x4351, 0x96, 0xcc, 0xe8, 0x6f, 0x3f, 0x80, 0x49, 0xda); +#define CLSID_PDNSHELLEXTENSION "{D292F82A-50BE-4351-96CC-E86F3F8049DA}" diff --git a/src/ShellExtension/PdnShell.cpp b/src/ShellExtension/PdnShell.cpp new file mode 100644 index 0000000..b5f8ea8 --- /dev/null +++ b/src/ShellExtension/PdnShell.cpp @@ -0,0 +1,153 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma strict_gs_check(on) +#pragma warning (disable: 4530) +#include +#include +#include "PdnShell.h" +#include "ClassFactory.h" + +#pragma data_seg(".text") +#define INITGUID +#include +#include +#include "PdnGuid.h" +#pragma data_seg() + +#include +using namespace std; + +HINSTANCE g_hInstance; +volatile LONG g_lRefCount; + +#ifndef NDEBUG +void TraceOut(const char *szFormat, ...) +{ + va_list marker; + va_start(marker, szFormat); + + char buffer[2048]; + _vsnprintf_s(buffer, sizeof(buffer), (sizeof(buffer) / sizeof(buffer[0])) - 1, szFormat, marker); + + OutputDebugStringA(buffer); + + FILE *flog = NULL; + errno_t err = fopen_s(&flog, "C:\\log.txt", "a"); + + if (0 == err && NULL != flog) + { + fprintf(flog, "%s\n", buffer); + fclose(flog); + } +} +#endif + +// Only used when calling TraceOut +const WCHAR *GuidToString(GUID guid) +{ + static WCHAR szGuid[128]; + StringFromGUID2(guid, szGuid, 128); + return szGuid; +} + +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReasonForCall, LPVOID lpvReserved) +{ + TraceEnter(); + TraceOut("hInstance=%p, dwReasonForCall=%u, lpvReserved=%p", hInstance, dwReasonForCall, lpvReserved); + + switch (dwReasonForCall) + { + case DLL_PROCESS_ATTACH: + g_hInstance = hInstance; + break; + + case DLL_THREAD_ATTACH: + break; + + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + g_hInstance = NULL; + break; + } + + TraceLeave(); + return TRUE; +} + +STDAPI DllCanUnloadNow() +{ + HRESULT hr; + + TraceEnter(); + + if (0 == g_lRefCount) + { + hr = S_OK; + } + else + { + hr = S_FALSE; + } + + TraceLeaveHr(hr); + return hr; +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv) +{ + TraceEnter(); + CClassFactory *pFactory = NULL; + HRESULT hr = S_OK; + + TraceOut("rclsid=%s, riid=%s, ppv=%p", GuidToString(rclsid), GuidToString(riid), ppv); + + if (SUCCEEDED(hr)) + { + if (NULL == ppv) + { + hr = E_INVALIDARG; + } + } + + // TODO: Why aren't we checking riid at all? + + if (SUCCEEDED(hr)) + { + TraceOut("Calling IsEqualCLSID(rclsid, CLSID_PdnShellExtension"); + if (!IsEqualCLSID(rclsid, CLSID_PdnShellExtension)) + { + hr = CLASS_E_CLASSNOTAVAILABLE; + } + } + + if (SUCCEEDED(hr)) + { + TraceOut("Creating CClassFactory instance"); + pFactory = new CClassFactory(rclsid); + + if (NULL == pFactory) + { + hr = E_OUTOFMEMORY; + } + } + + if (SUCCEEDED(hr)) + { + hr = pFactory->QueryInterface(riid, ppv); + pFactory->Release(); + } + + TraceLeaveHr(hr); + return hr; +} + + diff --git a/src/ShellExtension/PdnShell.def b/src/ShellExtension/PdnShell.def new file mode 100644 index 0000000..e202444 --- /dev/null +++ b/src/ShellExtension/PdnShell.def @@ -0,0 +1,4 @@ +LIBRARY ShellExtension +EXPORTS +DllCanUnloadNow PRIVATE +DllGetClassObject PRIVATE diff --git a/src/ShellExtension/PdnShell.h b/src/ShellExtension/PdnShell.h new file mode 100644 index 0000000..d41a786 --- /dev/null +++ b/src/ShellExtension/PdnShell.h @@ -0,0 +1,30 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifdef PDNSHELL_EXPORTS +#define PDNSHELL_API __declspec(dllexport) +#else +#define PDNSHELL_API __declspec(dllimport) +#endif + +extern HINSTANCE g_hInstance; +extern volatile LONG g_lRefCount; + +#ifdef NDEBUG +#define TraceOut if(0) +#else +extern void TraceOut(const char *szFormat, ...); +#endif + +#define TraceEnter() TraceOut("enter: %s", __FUNCTION__); +#define TraceLeave() TraceOut("leave: %s", __FUNCTION__); +#define TraceLeaveHr(hr) TraceOut("leave: %s, hr=0x%x", __FUNCTION__, hr); +extern const WCHAR *GuidToString(GUID guid); diff --git a/src/ShellExtension/PdnShellExtension.cpp b/src/ShellExtension/PdnShellExtension.cpp new file mode 100644 index 0000000..f88ae93 --- /dev/null +++ b/src/ShellExtension/PdnShellExtension.cpp @@ -0,0 +1,887 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma strict_gs_check(on) +#pragma warning (disable: 4530) +#include "PdnShellExtension.h" +#include +#pragma comment(lib, "shlwapi.lib") +#include +#pragma comment(lib, "gdiplus.lib") +#include +#include "PdnGuid.h" +#include "PdnShell.h" +#include +#include "MemoryStream.h" + +using namespace Gdiplus; + +CPdnShellExtension::CPdnShellExtension() + : m_lRefCount(1), + m_bstrFileName(NULL) +{ + m_size.cx = -1; + m_size.cy = -1; + InterlockedIncrement(&g_lRefCount); +} + +CPdnShellExtension::~CPdnShellExtension() +{ + InterlockedDecrement(&g_lRefCount); +} + +DWORD CPdnShellExtension::AddRef() +{ + TraceEnter(); + TraceLeave(); + return (DWORD)InterlockedIncrement(&m_lRefCount); +} + +DWORD CPdnShellExtension::Release() +{ + TraceEnter(); + DWORD dwRefCount = (DWORD)InterlockedDecrement(&m_lRefCount); + + if (0 == dwRefCount) + { + delete this; + } + + TraceLeave(); + return dwRefCount; +} + +STDMETHODIMP CPdnShellExtension::QueryInterface(REFIID iid, void **ppvObject) +{ + HRESULT hr = S_OK; + TraceEnter(); + TraceOut("riid=%S", GuidToString(iid)); + + if (NULL == ppvObject) + { + return E_INVALIDARG; + } + + if (SUCCEEDED(hr)) + { + *ppvObject = NULL; + + if (IsEqualCLSID(iid, IID_IUnknown)) + { + *ppvObject = this; + } + else if (IsEqualCLSID(iid, IID_IPersistFile)) + { + *ppvObject = (IPersistFile *)this; + } + else if (IsEqualCLSID(iid, IID_IExtractImage)) + { + *ppvObject = (IExtractImage *)this; + } + else if (IsEqualCLSID(iid, CLSID_PdnShellExtension)) + { + *ppvObject = this; + } + else + { + hr = E_NOINTERFACE; + } + } + + if (SUCCEEDED(hr)) + { + (*(IUnknown **)ppvObject)->AddRef(); + hr = S_OK; + } + + TraceLeaveHr(hr); + return hr; +} + + +STDMETHODIMP CPdnShellExtension::GetClassID(CLSID *pClassID) +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CPdnShellExtension::GetCurFile(LPOLESTR *ppszFileName) +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CPdnShellExtension::IsDirty() +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CPdnShellExtension::Load(LPCOLESTR pszFileName, + DWORD dwMode) +{ + HRESULT hr = S_OK; + TraceEnter(); + + TraceOut("filename=%S", pszFileName); + TraceOut("mode=%u", dwMode); + + if (SUCCEEDED(hr)) + { + if (NULL == pszFileName) + { + hr = E_INVALIDARG; + } + } + + if (SUCCEEDED(hr)) + { + SysFreeString(m_bstrFileName); + m_bstrFileName = SysAllocString(pszFileName); + + if (NULL == m_bstrFileName) + { + hr = E_OUTOFMEMORY; + } + else + { + hr = S_OK; + } + } + + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CPdnShellExtension::Save(LPCOLESTR pszFileName, + BOOL fRemember) +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + + TraceOut("fileName=%S", pszFileName); + + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CPdnShellExtension::SaveCompleted(LPCOLESTR pszFileName) +{ + HRESULT hr = E_NOTIMPL; + TraceEnter(); + TraceLeaveHr(hr); + return hr; +} + +HRESULT DoGdiplusStartup(Status *pStatusResult, ULONG_PTR *pToken, GdiplusStartupInput *gdiplusStartupInput) +{ + // An exception may be thrown because we delay-load gdiplus.dll and + // this is not installed on Win2K systems. Even if .NET is installed, + // gdiplus.dll is not located in the system directory and thus is not + // locatable by the loader. + // This was moved into a separate function because of compiler error + // C2712: "cannot use __try in functions that require object unwinding" + + Status status = Ok; + HRESULT hr = S_OK; + + if (NULL == pStatusResult) + { + hr = E_INVALIDARG; + } + + if (SUCCEEDED(hr)) + { + __try + { + status = GdiplusStartup(pToken, gdiplusStartupInput, NULL); + } + + __except (EXCEPTION_EXECUTE_HANDLER) + { + hr = E_FAIL; + status = Win32Error; + } + } + + if (SUCCEEDED(hr)) + { + *pStatusResult = status; + } + + return hr; +} + + +// ReadFile does not guarantee that it will read all the bytes that you ask for. +// It may decide to read fewer bytes for whatever reason. This function is a +// wrapper around ReadFile that loops until all the bytes you have asked for +// are read, or there was an error, or the end of file was reached. EOF is +// considered an error condition; when you ask for N bytes with this function +// you either get all N bytes, or an error. +static HRESULT ReadFileComplete(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead) +{ + HRESULT hr = S_OK; + + while (SUCCEEDED(hr) && nNumberOfBytesToRead > 0) + { + DWORD dwBytesRead = 0; + + BOOL bResult = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, &dwBytesRead, NULL); + + if (!bResult) + { + DWORD dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + } + else if (bResult && 0 == dwBytesRead) + { + hr = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF); + } + else + { + lpBuffer = (void *)((BYTE *)lpBuffer + dwBytesRead); + nNumberOfBytesToRead -= dwBytesRead; + } + } + + return hr; +} + + +static SIZE ComputeThumbnailSize(int originalWidth, int originalHeight, int maxEdgeLength) +{ + SIZE thumbSize; + ZeroMemory(&thumbSize, sizeof(thumbSize)); + + if (originalWidth <= 0 || originalHeight <= 0) + { + thumbSize.cx = 1; + thumbSize.cy = 1; + } + else if (originalWidth > originalHeight) + { + int longSide = min(originalWidth, maxEdgeLength); + thumbSize.cx = longSide; + thumbSize.cy = max(1, (originalHeight * longSide) / originalWidth); + } + else if (originalHeight > originalWidth) + { + int longSide = min(originalHeight, maxEdgeLength); + thumbSize.cx = max(1, (originalWidth * longSide) / originalHeight); + thumbSize.cy = longSide; + } + else // if (docSize.Width == docSize.Height) + { + int longSide = min(originalWidth, maxEdgeLength); + thumbSize.cx = longSide; + thumbSize.cy = longSide; + } + + return thumbSize; +} + +static HRESULT VerifyWindowsVersion(DWORD dwMajor, DWORD dwMinor, BOOL *pbResult) +{ + if (NULL == pbResult) + { + return E_INVALIDARG; + } + + HRESULT hr = S_OK; + BOOL bResult = TRUE; + DWORD dwError = ERROR_SUCCESS; + + OSVERSIONINFOEX osviex; + ZeroMemory(&osviex, sizeof(osviex)); + + osviex.dwOSVersionInfoSize = sizeof(osviex); + osviex.dwMajorVersion = dwMajor; + osviex.dwMinorVersion = dwMinor; + + DWORDLONG dwlConditionMask = 0; + + int vop = VER_GREATER_EQUAL; + + VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, vop); + VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, vop); + + if (SUCCEEDED(hr)) + { + bResult = VerifyVersionInfo(&osviex, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask); + + if (bResult) + { + *pbResult = TRUE; + } + else + { + dwError = GetLastError(); + + if (ERROR_OLD_WIN_VERSION == dwError) + { + *pbResult = FALSE; + } + else + { + hr = HRESULT_FROM_WIN32(dwError); + } + } + } + + return hr; +} + +STDMETHODIMP CPdnShellExtension::Extract(HBITMAP *phBmpImage) +{ + HRESULT hr = S_OK; + DWORD dwError = ERROR_SUCCESS; + BOOL bResult = TRUE; + TraceEnter(); + + // Open file + HANDLE hFile = INVALID_HANDLE_VALUE; + if (SUCCEEDED(hr)) + { + LPCWSTR lpFileName = (LPCWSTR)m_bstrFileName; + DWORD dwDesiredAccess = GENERIC_READ; + DWORD dwShareMode = FILE_SHARE_READ; + LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL; + DWORD dwCreationDisposition = OPEN_EXISTING; + DWORD dwFlagsAndAttributes = FILE_FLAG_SEQUENTIAL_SCAN; + HANDLE hTemplateFile = NULL; + + hFile = CreateFileW( + lpFileName, + dwDesiredAccess, + dwShareMode, + lpSecurityAttributes, + dwCreationDisposition, + dwFlagsAndAttributes, + hTemplateFile); + + if (INVALID_HANDLE_VALUE == hFile) + { + dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + TraceOut("CreateFile failed, hr=0x%x", hr); + } + } + + // Read magic numbers + BOOL bPdn3File = FALSE; + BYTE bMagic[4]; + ZeroMemory(bMagic, sizeof(bMagic)); + + if (SUCCEEDED(hr)) + { + hr = ReadFileComplete(hFile, (LPVOID)bMagic, sizeof(bMagic)); + } + + if (SUCCEEDED(hr)) + { + if ('P' == bMagic[0] && + 'D' == bMagic[1] && + 'N' == bMagic[2] && + '3' == bMagic[3]) + { + bPdn3File = TRUE; + } + } + else + { + TraceOut("ReadFile(1) failed, hr=0x%x", hr); + } + + if (SUCCEEDED(hr) && bPdn3File) + { + TraceOut("we have a pdn3 file"); + + TraceOut("Read + decode length"); + + int iLength = -1; + BYTE bLength[3]; + ZeroMemory(bLength, sizeof(bLength)); + + if (SUCCEEDED(hr)) + { + hr = ReadFileComplete(hFile, (LPVOID)bLength, sizeof(bLength)); + } + + if (SUCCEEDED(hr)) + { + iLength = bLength[0] + (bLength[1] << 8) + (bLength[2] << 16); + } + else + { + TraceOut("ReadFile(2) failed, hr=0x%x", hr); + } + + TraceOut("Allocate buffer"); + BYTE *pbHeaderBytes = NULL; + + if (SUCCEEDED(hr)) + { + pbHeaderBytes = new BYTE[1 + iLength]; + + if (NULL == pbHeaderBytes) + { + hr = E_OUTOFMEMORY; + TraceOut("pbHeaderBytes alloc failed"); + } + else + { + ZeroMemory(pbHeaderBytes, 1 + iLength); + } + } + + TraceOut("Read N bytes"); + if (SUCCEEDED(hr)) + { + hr = ReadFileComplete(hFile, (LPVOID)pbHeaderBytes, iLength); + } + + if (FAILED(hr)) + { + TraceOut("ReadFile(3) failed, hr=0x%x", hr); + } + + TraceOut("Convert to UTF8 string"); + CHAR *szHeader = (CHAR *)pbHeaderBytes; + + TraceOut("Search for \"byte[] conversion"); + int nImgBytes = -1; + if (SUCCEEDED(hr)) + { + nImgBytes = Base64DecodeGetRequiredLength(iImgBase64Len); + TraceOut("nImgBytes=%d", nImgBytes); + } + + TraceOut("Allocate %d byte buffer for base64->byte[] conversion", nImgBytes); + BYTE *pbImgBytes = NULL; + if (SUCCEEDED(hr)) + { + pbImgBytes = new BYTE[nImgBytes]; + + if (NULL == pbImgBytes) + { + hr = E_OUTOFMEMORY; + TraceOut("pbImgBytes alloc failed"); + } + else + { + ZeroMemory(pbImgBytes, nImgBytes); + } + } + + TraceOut("Convert from base64 to byte[]"); + int iImgLen = -1; + if (SUCCEEDED(hr)) + { + int nDestLen = iImgBase64Len; + + bResult = Base64Decode(szImgBase64, iImgBase64Len, pbImgBytes, &nDestLen); + + if (!bResult) + { + TraceOut("Base64Decode failed"); + hr = E_FAIL; + } + else + { + iImgLen = nDestLen; + } + } + + TraceOut("iImgLen = %d", iImgLen); + TraceOut("Wrap a memory stream around it"); + CMemoryStream *pMemoryStream = NULL; + if (SUCCEEDED(hr)) + { + pMemoryStream = new CMemoryStream(pbImgBytes, iImgLen); + + if (NULL == pMemoryStream) + { + TraceOut("pMemoryStream alloc failed"); + hr = E_OUTOFMEMORY; + } + } + + TraceOut("Startup GDI+"); + ULONG_PTR pGdiToken = NULL; + + if (SUCCEEDED(hr)) + { + GdiplusStartupInput gdiplusStartupInput; + Status status; + + hr = DoGdiplusStartup(&status, &pGdiToken, &gdiplusStartupInput); + + if (status != Ok) + { + hr = E_FAIL; + pGdiToken = NULL; + TraceOut("GdiplusStartup failed"); + } + } + + TraceOut("Load image"); + Bitmap *pBitmap = NULL; + if (SUCCEEDED(hr)) + { + pBitmap = Bitmap::FromStream(pMemoryStream, FALSE); + + if (NULL == pBitmap) + { + hr = E_FAIL; + TraceOut("Bitmap::FromStream returned NULL"); + } + } + + Bitmap *pResizedBitmap = NULL; + if (SUCCEEDED(hr)) + { + if (m_size.cx > 0 && m_size.cy > 0) + { + UINT nMaxEdge = min(m_size.cx, m_size.cy); + SIZE newSize = ComputeThumbnailSize(pBitmap->GetWidth(), pBitmap->GetHeight(), nMaxEdge); + UINT nNewWidth = newSize.cx; + UINT nNewHeight = newSize.cy; + + if (SUCCEEDED(hr)) + { + pResizedBitmap = new Bitmap(nNewWidth, nNewHeight, PixelFormat32bppARGB); + + if (NULL == pResizedBitmap) + { + hr = E_OUTOFMEMORY; + } + } + + Graphics *pG = NULL; + if (SUCCEEDED(hr)) + { + pG = Graphics::FromImage(pResizedBitmap); + + if (NULL == pG) + { + hr = E_OUTOFMEMORY; + } + } + + if (SUCCEEDED(hr)) + { + // In Windows Vista, we can have a transparent background and it works great. + // In XP, our alpha channel gets stomped to black. + BOOL bIsVista = FALSE; + HRESULT hrx = VerifyWindowsVersion(6, 0, &bIsVista); + + pG->SetCompositingMode(CompositingModeSourceCopy); + + if (SUCCEEDED(hrx) && bIsVista) + { + pG->Clear(Color::Transparent); + } + else + { + pG->Clear(Color::White); + } + + pG->SetCompositingMode(CompositingModeSourceOver); + + // Fit the thumbnail to the output bitmap + pG->SetInterpolationMode(InterpolationModeBicubic); + + pG->SetPixelOffsetMode(PixelOffsetModeHalf); + + pG->DrawImage( + pBitmap, + RectF((REAL)0, (REAL)0, (REAL)pResizedBitmap->GetWidth(), (REAL)pResizedBitmap->GetHeight()), + (REAL)0, + (REAL)0, + (REAL)pBitmap->GetWidth(), + (REAL)pBitmap->GetHeight(), + UnitPixel); + } + + if (NULL != pG) + { + delete pG; + pG = NULL; + } + } + else + { + pResizedBitmap = pBitmap->Clone(Rect(0, 0, pBitmap->GetWidth(), pBitmap->GetHeight()), pBitmap->GetPixelFormat()); + + if (NULL == pResizedBitmap) + { + hr = E_OUTOFMEMORY; + } + } + } + + if (NULL != pBitmap) + { + delete pBitmap; + pBitmap = NULL; + } + + TraceOut("Get HBITMAP from it"); + HBITMAP hBitmap = NULL; + if (SUCCEEDED(hr)) + { + Status status = pResizedBitmap->GetHBITMAP(Color(0), &hBitmap); + TraceOut("status=%d", status); + + if (Ok != status) + { + if (Win32Error == status) + { + dwError = GetLastError(); + hr = HRESULT_FROM_WIN32(dwError); + TraceOut("pResizedBitmap->GetHBITMAP failed, hr=0x%x", hr); + } + else + { + TraceOut("pResizedBitmap->GetHBITMAP failed, not Win32Error, hr is now = E_FAIL"); + hr = E_FAIL; + } + } + } + + TraceOut("Give bitmap to the caller!"); + if (SUCCEEDED(hr)) + { + *phBmpImage = hBitmap; + } + + TraceOut("Cleanup"); + if (NULL != pResizedBitmap) + { + delete pResizedBitmap; + pResizedBitmap = NULL; + } + + if (NULL != pGdiToken) + { + GdiplusShutdown(pGdiToken); + pGdiToken = NULL; + } + + if (NULL != pMemoryStream) + { + pMemoryStream->Release(); + pMemoryStream = NULL; + } + + if (NULL != pbHeaderBytes) + { + delete [] pbHeaderBytes; + pbHeaderBytes = NULL; + } + + if (NULL != pbImgBytes) + { + delete [] pbImgBytes; + pbImgBytes = NULL; + } + } + + if (!bPdn3File || FAILED(hr)) + { + // Give generic PDN icon of some sort + hr = E_FAIL; + } + + // Cleanup + if (INVALID_HANDLE_VALUE != hFile) + { + CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + } + + TraceLeaveHr(hr); + return hr; +} + +STDMETHODIMP CPdnShellExtension::GetLocation(LPWSTR pszPathBuffer, + DWORD cchMax, + DWORD *pdwPriority, + const SIZE *prgSize, + DWORD dwRecClrDepth, + DWORD *pdwFlags) +{ + HRESULT hr = S_OK; + TraceEnter(); + + TraceOut("pszPathBuffer=%S", pszPathBuffer); + TraceOut("cchMax=%u", cchMax); + + if (SUCCEEDED(hr)) + { + if (NULL == pszPathBuffer) + { + TraceOut("pszPathBuffer is NULL"); + hr = E_INVALIDARG; + } + } + + if (SUCCEEDED(hr)) + { + if (NULL == pdwPriority) + { + TraceOut("pdwPriority is NULL"); + hr = E_INVALIDARG; + } + } + + if (SUCCEEDED(hr)) + { + if (NULL == pdwFlags) + { + TraceOut("pdwFlags is NULL"); + hr = E_INVALIDARG; + } + } + + if (SUCCEEDED(hr)) + { + if (NULL == prgSize) + { + TraceOut("prgSize is NULL"); + hr = E_INVALIDARG; + } + } + + TraceOut("*pdwFlags = %u", *pdwFlags); + TraceOut("prgSize=%d x %d", prgSize->cx, prgSize->cy); + + if (SUCCEEDED(hr)) + { + wcscpy_s(pszPathBuffer, cchMax, m_bstrFileName); + + *pdwPriority = IEIT_PRIORITY_NORMAL; + + if ((*pdwFlags & IEIFLAG_ASPECT) || + (*pdwFlags & IEIFLAG_ORIGSIZE)) + { + m_size = *prgSize; + TraceOut("m_size = %d x %d", m_size.cx, m_size.cy); + } + else + { + m_size.cx = -1; + m_size.cy = -1; + } + + *pdwFlags |= IEIFLAG_CACHE; + + if (*pdwFlags & IEIFLAG_ASYNC) + { + hr = E_PENDING; + } + else + { + hr = NOERROR; + } + } + + TraceLeaveHr(hr); + return hr; +} + diff --git a/src/ShellExtension/PdnShellExtension.h b/src/ShellExtension/PdnShellExtension.h new file mode 100644 index 0000000..51c991e --- /dev/null +++ b/src/ShellExtension/PdnShellExtension.h @@ -0,0 +1,58 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include + +class CPdnShellExtension + : IPersistFile, + IExtractImage +{ +public: + CPdnShellExtension(); + ~CPdnShellExtension(); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject); + STDMETHODIMP_(DWORD) AddRef(); + STDMETHODIMP_(DWORD) Release(); + + // IPersist methods (via IPersistFile) + STDMETHODIMP GetClassID(CLSID *pClassID); + + // IPersistFile methods + STDMETHODIMP GetCurFile(LPOLESTR *ppszFileName); + STDMETHODIMP IsDirty(); + + STDMETHODIMP Load(LPCOLESTR pszFileName, + DWORD dwMode); + + STDMETHODIMP Save(LPCOLESTR pszFileName, + BOOL fRemember); + + STDMETHODIMP SaveCompleted(LPCOLESTR pszFileName); + + // IExtractImage methods + STDMETHODIMP Extract(HBITMAP *phBmpImage); + + STDMETHODIMP GetLocation(LPWSTR pszPathBuffer, + DWORD cchMax, + DWORD *pdwPriority, + const SIZE *prgSize, + DWORD dwRecClrDepth, + DWORD *pdwFlags); + +protected: + volatile LONG m_lRefCount; + +private: + BSTR m_bstrFileName; + SIZE m_size; +}; diff --git a/src/ShellExtension/ShellExtension.rc b/src/ShellExtension/ShellExtension.rc new file mode 100644 index 0000000..8eeb63e --- /dev/null +++ b/src/ShellExtension/ShellExtension.rc @@ -0,0 +1,101 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 3,36,0,0 + PRODUCTVERSION 3,36,0,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "Paint.NET Shell Extension DLL" + VALUE "FileVersion", "3, 36, 0, 0" + VALUE "InternalName", "PdnShellExtension" + VALUE "LegalCopyright", "Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved." + VALUE "OriginalFilename", "ShellExtension.dll " + VALUE "ProductName", "Paint.NET" + VALUE "ProductVersion", "3, 36, 0, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/ShellExtension/ShellExtension.vcproj b/src/ShellExtension/ShellExtension.vcproj new file mode 100644 index 0000000..5391d73 --- /dev/null +++ b/src/ShellExtension/ShellExtension.vcproj @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ShellExtension/ShellExtension_x64/PdnShell_x64.def b/src/ShellExtension/ShellExtension_x64/PdnShell_x64.def new file mode 100644 index 0000000..00336f2 --- /dev/null +++ b/src/ShellExtension/ShellExtension_x64/PdnShell_x64.def @@ -0,0 +1,4 @@ +LIBRARY ShellExtension_x64 +EXPORTS +DllCanUnloadNow PRIVATE +DllGetClassObject PRIVATE diff --git a/src/ShellExtension/ShellExtension_x64/ShellExtension_x64.vcproj b/src/ShellExtension/ShellExtension_x64/ShellExtension_x64.vcproj new file mode 100644 index 0000000..b5e9384 --- /dev/null +++ b/src/ShellExtension/ShellExtension_x64/ShellExtension_x64.vcproj @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ShellExtension/ShellExtension_x86/PdnShell_x86.def b/src/ShellExtension/ShellExtension_x86/PdnShell_x86.def new file mode 100644 index 0000000..085d67f --- /dev/null +++ b/src/ShellExtension/ShellExtension_x86/PdnShell_x86.def @@ -0,0 +1,4 @@ +LIBRARY ShellExtension_x86 +EXPORTS +DllCanUnloadNow PRIVATE +DllGetClassObject PRIVATE diff --git a/src/ShellExtension/ShellExtension_x86/ShellExtension_x86.vcproj b/src/ShellExtension/ShellExtension_x86/ShellExtension_x86.vcproj new file mode 100644 index 0000000..5cd01c2 --- /dev/null +++ b/src/ShellExtension/ShellExtension_x86/ShellExtension_x86.vcproj @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ShellExtension/gpc.c b/src/ShellExtension/gpc.c new file mode 100644 index 0000000..a209058 --- /dev/null +++ b/src/ShellExtension/gpc.c @@ -0,0 +1,2500 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// For Paint.NET 3.xx, it is very convenient for us to place the GPC native +// code into the ShellExtension*.dll. This way we don't need to add another +// binary to installation, nor do we need to set up another project within +// Visual Studio. +// In Paint.NET 4.xx, there will be a PaintDotNet.Native.[x86|x64].dll, and +// that's where this will live. + +// NOTE: +// Although this Paint.NET distribution includes the GPC source code, use of +// the GPC code in any other commercial application is not permitted without +// a GPC Commercial Use Licence from The University of Manchester -- contact +// gpc@cs.man.ac.uk for details. +// Website for GPC: http://www.cs.man.ac.uk/~toby/alan/software/ + +/* +=========================================================================== + +Project: Generic Polygon Clipper + + A new algorithm for calculating the difference, intersection, + exclusive-or or union of arbitrary polygon sets. + +File: gpc.c +Author: Alan Murta (email: gpc@cs.man.ac.uk) +Version: 2.32 +Date: 17th December 2004 + +Copyright: (C) Advanced Interfaces Group, + University of Manchester. + + This software is free for non-commercial use. It may be copied, + modified, and redistributed provided that this copyright notice + is preserved on all copies. The intellectual property rights of + the algorithms used reside with the University of Manchester + Advanced Interfaces Group. + + You may not use this software, in whole or in part, in support + of any commercial product without the express consent of the + author. + + There is no warranty or other guarantee of fitness of this + software for any purpose. It is provided solely "as is". + +=========================================================================== +*/ + + +/* +=========================================================================== + Includes +=========================================================================== +*/ + +#include "gpc.h" +#include +#include +#include + + +/* +=========================================================================== + Constants +=========================================================================== +*/ + +#ifndef TRUE +#define FALSE 0 +#define TRUE 1 +#endif + +#define LEFT 0 +#define RIGHT 1 + +#define ABOVE 0 +#define BELOW 1 + +#define CLIP 0 +#define SUBJ 1 + +#define INVERT_TRISTRIPS FALSE + + +/* +=========================================================================== + Macros +=========================================================================== +*/ + +#define EQ(a, b) (fabs((a) - (b)) <= GPC_EPSILON) + +#define PREV_INDEX(i, n) ((i - 1 + n) % n) +#define NEXT_INDEX(i, n) ((i + 1 ) % n) + +#define OPTIMAL(v, i, n) ((v[PREV_INDEX(i, n)].y != v[i].y) || \ + (v[NEXT_INDEX(i, n)].y != v[i].y)) + +#define FWD_MIN(v, i, n) ((v[PREV_INDEX(i, n)].vertex.y >= v[i].vertex.y) \ + && (v[NEXT_INDEX(i, n)].vertex.y > v[i].vertex.y)) + +#define NOT_FMAX(v, i, n) (v[NEXT_INDEX(i, n)].vertex.y > v[i].vertex.y) + +#define REV_MIN(v, i, n) ((v[PREV_INDEX(i, n)].vertex.y > v[i].vertex.y) \ + && (v[NEXT_INDEX(i, n)].vertex.y >= v[i].vertex.y)) + +#define NOT_RMAX(v, i, n) (v[PREV_INDEX(i, n)].vertex.y > v[i].vertex.y) + +#define VERTEX(e,p,s,x,y) {add_vertex(&((e)->outp[(p)]->v[(s)]), x, y); \ + (e)->outp[(p)]->active++;} + +#define P_EDGE(d,e,p,i,j) {(d)= (e); \ + do {(d)= (d)->prev;} while (!(d)->outp[(p)]); \ + (i)= (d)->bot.x + (d)->dx * ((j)-(d)->bot.y);} + +#define N_EDGE(d,e,p,i,j) {(d)= (e); \ + do {(d)= (d)->next;} while (!(d)->outp[(p)]); \ + (i)= (d)->bot.x + (d)->dx * ((j)-(d)->bot.y);} + +#define MALLOC(p, b, s, t) {if ((b) > 0) { \ + p= (t*)malloc(b); if (!(p)) { \ + fprintf(stderr, "gpc malloc failure: %s\n", s); \ + exit(0);}} else p= NULL;} + +#define FREE(p) {if (p) {free(p); (p)= NULL;}} + + +/* +=========================================================================== + Private Data Types +=========================================================================== +*/ + +typedef enum /* Edge intersection classes */ +{ + NUL, /* Empty non-intersection */ + EMX, /* External maximum */ + ELI, /* External left intermediate */ + TED, /* Top edge */ + ERI, /* External right intermediate */ + RED, /* Right edge */ + IMM, /* Internal maximum and minimum */ + IMN, /* Internal minimum */ + EMN, /* External minimum */ + EMM, /* External maximum and minimum */ + LED, /* Left edge */ + ILI, /* Internal left intermediate */ + BED, /* Bottom edge */ + IRI, /* Internal right intermediate */ + IMX, /* Internal maximum */ + FUL /* Full non-intersection */ +} vertex_type; + +typedef enum /* Horizontal edge states */ +{ + NH, /* No horizontal edge */ + BH, /* Bottom horizontal edge */ + TH /* Top horizontal edge */ +} h_state; + +typedef enum /* Edge bundle state */ +{ + UNBUNDLED, /* Isolated edge not within a bundle */ + BUNDLE_HEAD, /* Bundle head node */ + BUNDLE_TAIL /* Passive bundle tail node */ +} bundle_state; + +typedef struct v_shape /* Internal vertex list datatype */ +{ + double x; /* X coordinate component */ + double y; /* Y coordinate component */ + struct v_shape *next; /* Pointer to next vertex in list */ +} vertex_node; + +typedef struct p_shape /* Internal contour / tristrip type */ +{ + int active; /* Active flag / vertex count */ + int hole; /* Hole / external contour flag */ + vertex_node *v[2]; /* Left and right vertex list ptrs */ + struct p_shape *next; /* Pointer to next polygon contour */ + struct p_shape *proxy; /* Pointer to actual structure used */ +} polygon_node; + +typedef struct edge_shape +{ + gpc_vertex vertex; /* Piggy-backed contour vertex data */ + gpc_vertex bot; /* Edge lower (x, y) coordinate */ + gpc_vertex top; /* Edge upper (x, y) coordinate */ + double xb; /* Scanbeam bottom x coordinate */ + double xt; /* Scanbeam top x coordinate */ + double dx; /* Change in x for a unit y increase */ + int type; /* Clip / subject edge flag */ + int bundle[2][2]; /* Bundle edge flags */ + int bside[2]; /* Bundle left / right indicators */ + bundle_state bstate[2]; /* Edge bundle state */ + polygon_node *outp[2]; /* Output polygon / tristrip pointer */ + struct edge_shape *prev; /* Previous edge in the AET */ + struct edge_shape *next; /* Next edge in the AET */ + struct edge_shape *pred; /* Edge connected at the lower end */ + struct edge_shape *succ; /* Edge connected at the upper end */ + struct edge_shape *next_bound; /* Pointer to next bound in LMT */ +} edge_node; + +typedef struct lmt_shape /* Local minima table */ +{ + double y; /* Y coordinate at local minimum */ + edge_node *first_bound; /* Pointer to bound list */ + struct lmt_shape *next; /* Pointer to next local minimum */ +} lmt_node; + +typedef struct sbt_t_shape /* Scanbeam tree */ +{ + double y; /* Scanbeam node y value */ + struct sbt_t_shape *less; /* Pointer to nodes with lower y */ + struct sbt_t_shape *more; /* Pointer to nodes with higher y */ +} sb_tree; + +typedef struct it_shape /* Intersection table */ +{ + edge_node *ie[2]; /* Intersecting edge (bundle) pair */ + gpc_vertex point; /* Point of intersection */ + struct it_shape *next; /* The next intersection table node */ +} it_node; + +typedef struct st_shape /* Sorted edge table */ +{ + edge_node *edge; /* Pointer to AET edge */ + double xb; /* Scanbeam bottom x coordinate */ + double xt; /* Scanbeam top x coordinate */ + double dx; /* Change in x for a unit y increase */ + struct st_shape *prev; /* Previous edge in sorted list */ +} st_node; + +typedef struct bbox_shape /* Contour axis-aligned bounding box */ +{ + double xmin; /* Minimum x coordinate */ + double ymin; /* Minimum y coordinate */ + double xmax; /* Maximum x coordinate */ + double ymax; /* Maximum y coordinate */ +} bbox; + + +/* +=========================================================================== + Global Data +=========================================================================== +*/ + +/* Horizontal edge state transitions within scanbeam boundary */ +const h_state next_h_state[3][6]= +{ + /* ABOVE BELOW CROSS */ + /* L R L R L R */ + /* NH */ {BH, TH, TH, BH, NH, NH}, + /* BH */ {NH, NH, NH, NH, TH, TH}, + /* TH */ {NH, NH, NH, NH, BH, BH} +}; + + +/* +=========================================================================== + Private Functions +=========================================================================== +*/ + +static void reset_it(it_node **it) +{ + it_node *itn; + + while (*it) + { + itn= (*it)->next; + FREE(*it); + *it= itn; + } +} + + +static void reset_lmt(lmt_node **lmt) +{ + lmt_node *lmtn; + + while (*lmt) + { + lmtn= (*lmt)->next; + FREE(*lmt); + *lmt= lmtn; + } +} + + +static void insert_bound(edge_node **b, edge_node *e) +{ + edge_node *existing_bound; + + if (!*b) + { + /* Link node e to the tail of the list */ + *b= e; + } + else + { + /* Do primary sort on the x field */ + if (e[0].bot.x < (*b)[0].bot.x) + { + /* Insert a new node mid-list */ + existing_bound= *b; + *b= e; + (*b)->next_bound= existing_bound; + } + else + { + if (e[0].bot.x == (*b)[0].bot.x) + { + /* Do secondary sort on the dx field */ + if (e[0].dx < (*b)[0].dx) + { + /* Insert a new node mid-list */ + existing_bound= *b; + *b= e; + (*b)->next_bound= existing_bound; + } + else + { + /* Head further down the list */ + insert_bound(&((*b)->next_bound), e); + } + } + else + { + /* Head further down the list */ + insert_bound(&((*b)->next_bound), e); + } + } + } +} + + +static edge_node **bound_list(lmt_node **lmt, double y) +{ + lmt_node *existing_node; + + if (!*lmt) + { + /* Add node onto the tail end of the LMT */ + MALLOC(*lmt, sizeof(lmt_node), "LMT insertion", lmt_node); + (*lmt)->y= y; + (*lmt)->first_bound= NULL; + (*lmt)->next= NULL; + return &((*lmt)->first_bound); + } + else + if (y < (*lmt)->y) + { + /* Insert a new LMT node before the current node */ + existing_node= *lmt; + MALLOC(*lmt, sizeof(lmt_node), "LMT insertion", lmt_node); + (*lmt)->y= y; + (*lmt)->first_bound= NULL; + (*lmt)->next= existing_node; + return &((*lmt)->first_bound); + } + else + if (y > (*lmt)->y) + /* Head further up the LMT */ + return bound_list(&((*lmt)->next), y); + else + /* Use this existing LMT node */ + return &((*lmt)->first_bound); +} + + +static void add_to_sbtree(int *entries, sb_tree **sbtree, double y) +{ + if (!*sbtree) + { + /* Add a new tree node here */ + MALLOC(*sbtree, sizeof(sb_tree), "scanbeam tree insertion", sb_tree); + (*sbtree)->y= y; + (*sbtree)->less= NULL; + (*sbtree)->more= NULL; + (*entries)++; + } + else + { + if ((*sbtree)->y > y) + { + /* Head into the 'less' sub-tree */ + add_to_sbtree(entries, &((*sbtree)->less), y); + } + else + { + if ((*sbtree)->y < y) + { + /* Head into the 'more' sub-tree */ + add_to_sbtree(entries, &((*sbtree)->more), y); + } + } + } +} + + +static void build_sbt(int *entries, double *sbt, sb_tree *sbtree) +{ + if (sbtree->less) + build_sbt(entries, sbt, sbtree->less); + sbt[*entries]= sbtree->y; + (*entries)++; + if (sbtree->more) + build_sbt(entries, sbt, sbtree->more); +} + + +static void free_sbtree(sb_tree **sbtree) +{ + if (*sbtree) + { + free_sbtree(&((*sbtree)->less)); + free_sbtree(&((*sbtree)->more)); + FREE(*sbtree); + } +} + + +static int count_optimal_vertices(gpc_vertex_list c) +{ + int result= 0, i; + + /* Ignore non-contributing contours */ + if (c.num_vertices > 0) + { + for (i= 0; i < c.num_vertices; i++) + /* Ignore superfluous vertices embedded in horizontal edges */ + if (OPTIMAL(c.vertex, i, c.num_vertices)) + result++; + } + return result; +} + + +static edge_node *build_lmt(lmt_node **lmt, sb_tree **sbtree, + int *sbt_entries, gpc_polygon *p, int type, + gpc_op op) +{ + int c, i, min, max, num_edges, v, num_vertices; + int total_vertices= 0, e_index=0; + edge_node *e, *edge_table; + + for (c= 0; c < p->num_contours; c++) + total_vertices+= count_optimal_vertices(p->contour[c]); + + /* Create the entire input polygon edge table in one go */ + MALLOC(edge_table, total_vertices * sizeof(edge_node), + "edge table creation", edge_node); + + for (c= 0; c < p->num_contours; c++) + { + if (p->contour[c].num_vertices < 0) + { + /* Ignore the non-contributing contour and repair the vertex count */ + p->contour[c].num_vertices= -p->contour[c].num_vertices; + } + else + { + /* Perform contour optimisation */ + num_vertices= 0; + for (i= 0; i < p->contour[c].num_vertices; i++) + if (OPTIMAL(p->contour[c].vertex, i, p->contour[c].num_vertices)) + { + edge_table[num_vertices].vertex.x= p->contour[c].vertex[i].x; + edge_table[num_vertices].vertex.y= p->contour[c].vertex[i].y; + + /* Record vertex in the scanbeam table */ + add_to_sbtree(sbt_entries, sbtree, + edge_table[num_vertices].vertex.y); + + num_vertices++; + } + + /* Do the contour forward pass */ + for (min= 0; min < num_vertices; min++) + { + /* If a forward local minimum... */ + if (FWD_MIN(edge_table, min, num_vertices)) + { + /* Search for the next local maximum... */ + num_edges= 1; + max= NEXT_INDEX(min, num_vertices); + while (NOT_FMAX(edge_table, max, num_vertices)) + { + num_edges++; + max= NEXT_INDEX(max, num_vertices); + } + + /* Build the next edge list */ + e= &edge_table[e_index]; + e_index+= num_edges; + v= min; + e[0].bstate[BELOW]= UNBUNDLED; + e[0].bundle[BELOW][CLIP]= FALSE; + e[0].bundle[BELOW][SUBJ]= FALSE; + for (i= 0; i < num_edges; i++) + { + e[i].xb= edge_table[v].vertex.x; + e[i].bot.x= edge_table[v].vertex.x; + e[i].bot.y= edge_table[v].vertex.y; + + v= NEXT_INDEX(v, num_vertices); + + e[i].top.x= edge_table[v].vertex.x; + e[i].top.y= edge_table[v].vertex.y; + e[i].dx= (edge_table[v].vertex.x - e[i].bot.x) / + (e[i].top.y - e[i].bot.y); + e[i].type= type; + e[i].outp[ABOVE]= NULL; + e[i].outp[BELOW]= NULL; + e[i].next= NULL; + e[i].prev= NULL; + e[i].succ= ((num_edges > 1) && (i < (num_edges - 1))) ? + &(e[i + 1]) : NULL; + e[i].pred= ((num_edges > 1) && (i > 0)) ? &(e[i - 1]) : NULL; + e[i].next_bound= NULL; + e[i].bside[CLIP]= (op == GPC_DIFF) ? RIGHT : LEFT; + e[i].bside[SUBJ]= LEFT; + } + insert_bound(bound_list(lmt, edge_table[min].vertex.y), e); + } + } + + /* Do the contour reverse pass */ + for (min= 0; min < num_vertices; min++) + { + /* If a reverse local minimum... */ + if (REV_MIN(edge_table, min, num_vertices)) + { + /* Search for the previous local maximum... */ + num_edges= 1; + max= PREV_INDEX(min, num_vertices); + while (NOT_RMAX(edge_table, max, num_vertices)) + { + num_edges++; + max= PREV_INDEX(max, num_vertices); + } + + /* Build the previous edge list */ + e= &edge_table[e_index]; + e_index+= num_edges; + v= min; + e[0].bstate[BELOW]= UNBUNDLED; + e[0].bundle[BELOW][CLIP]= FALSE; + e[0].bundle[BELOW][SUBJ]= FALSE; + for (i= 0; i < num_edges; i++) + { + e[i].xb= edge_table[v].vertex.x; + e[i].bot.x= edge_table[v].vertex.x; + e[i].bot.y= edge_table[v].vertex.y; + + v= PREV_INDEX(v, num_vertices); + + e[i].top.x= edge_table[v].vertex.x; + e[i].top.y= edge_table[v].vertex.y; + e[i].dx= (edge_table[v].vertex.x - e[i].bot.x) / + (e[i].top.y - e[i].bot.y); + e[i].type= type; + e[i].outp[ABOVE]= NULL; + e[i].outp[BELOW]= NULL; + e[i].next= NULL; + e[i].prev= NULL; + e[i].succ= ((num_edges > 1) && (i < (num_edges - 1))) ? + &(e[i + 1]) : NULL; + e[i].pred= ((num_edges > 1) && (i > 0)) ? &(e[i - 1]) : NULL; + e[i].next_bound= NULL; + e[i].bside[CLIP]= (op == GPC_DIFF) ? RIGHT : LEFT; + e[i].bside[SUBJ]= LEFT; + } + insert_bound(bound_list(lmt, edge_table[min].vertex.y), e); + } + } + } + } + return edge_table; +} + + +static void add_edge_to_aet(edge_node **aet, edge_node *edge, edge_node *prev) +{ + if (!*aet) + { + /* Append edge onto the tail end of the AET */ + *aet= edge; + edge->prev= prev; + edge->next= NULL; + } + else + { + /* Do primary sort on the xb field */ + if (edge->xb < (*aet)->xb) + { + /* Insert edge here (before the AET edge) */ + edge->prev= prev; + edge->next= *aet; + (*aet)->prev= edge; + *aet= edge; + } + else + { + if (edge->xb == (*aet)->xb) + { + /* Do secondary sort on the dx field */ + if (edge->dx < (*aet)->dx) + { + /* Insert edge here (before the AET edge) */ + edge->prev= prev; + edge->next= *aet; + (*aet)->prev= edge; + *aet= edge; + } + else + { + /* Head further into the AET */ + add_edge_to_aet(&((*aet)->next), edge, *aet); + } + } + else + { + /* Head further into the AET */ + add_edge_to_aet(&((*aet)->next), edge, *aet); + } + } + } +} + + +static void add_intersection(it_node **it, edge_node *edge0, edge_node *edge1, + double x, double y) +{ + it_node *existing_node; + + if (!*it) + { + /* Append a new node to the tail of the list */ + MALLOC(*it, sizeof(it_node), "IT insertion", it_node); + (*it)->ie[0]= edge0; + (*it)->ie[1]= edge1; + (*it)->point.x= x; + (*it)->point.y= y; + (*it)->next= NULL; + } + else + { + if ((*it)->point.y > y) + { + /* Insert a new node mid-list */ + existing_node= *it; + MALLOC(*it, sizeof(it_node), "IT insertion", it_node); + (*it)->ie[0]= edge0; + (*it)->ie[1]= edge1; + (*it)->point.x= x; + (*it)->point.y= y; + (*it)->next= existing_node; + } + else + /* Head further down the list */ + add_intersection(&((*it)->next), edge0, edge1, x, y); + } +} + + +static void add_st_edge(st_node **st, it_node **it, edge_node *edge, + double dy) +{ + st_node *existing_node; + double den, r, x, y; + + if (!*st) + { + /* Append edge onto the tail end of the ST */ + MALLOC(*st, sizeof(st_node), "ST insertion", st_node); + (*st)->edge= edge; + (*st)->xb= edge->xb; + (*st)->xt= edge->xt; + (*st)->dx= edge->dx; + (*st)->prev= NULL; + } + else + { + den= ((*st)->xt - (*st)->xb) - (edge->xt - edge->xb); + + /* If new edge and ST edge don't cross */ + if ((edge->xt >= (*st)->xt) || (edge->dx == (*st)->dx) || + (fabs(den) <= DBL_EPSILON)) + { + /* No intersection - insert edge here (before the ST edge) */ + existing_node= *st; + MALLOC(*st, sizeof(st_node), "ST insertion", st_node); + (*st)->edge= edge; + (*st)->xb= edge->xb; + (*st)->xt= edge->xt; + (*st)->dx= edge->dx; + (*st)->prev= existing_node; + } + else + { + /* Compute intersection between new edge and ST edge */ + r= (edge->xb - (*st)->xb) / den; + x= (*st)->xb + r * ((*st)->xt - (*st)->xb); + y= r * dy; + + /* Insert the edge pointers and the intersection point in the IT */ + add_intersection(it, (*st)->edge, edge, x, y); + + /* Head further into the ST */ + add_st_edge(&((*st)->prev), it, edge, dy); + } + } +} + + +static void build_intersection_table(it_node **it, edge_node *aet, double dy) +{ + st_node *st, *stp; + edge_node *edge; + + /* Build intersection table for the current scanbeam */ + reset_it(it); + st= NULL; + + /* Process each AET edge */ + for (edge= aet; edge; edge= edge->next) + { + if ((edge->bstate[ABOVE] == BUNDLE_HEAD) || + edge->bundle[ABOVE][CLIP] || edge->bundle[ABOVE][SUBJ]) + add_st_edge(&st, it, edge, dy); + } + + /* Free the sorted edge table */ + while (st) + { + stp= st->prev; + FREE(st); + st= stp; + } +} + +static int count_contours(polygon_node *polygon) +{ + int nc, nv; + vertex_node *v, *nextv; + + for (nc= 0; polygon; polygon= polygon->next) + if (polygon->active) + { + /* Count the vertices in the current contour */ + nv= 0; + for (v= polygon->proxy->v[LEFT]; v; v= v->next) + nv++; + + /* Record valid vertex counts in the active field */ + if (nv > 2) + { + polygon->active= nv; + nc++; + } + else + { + /* Invalid contour: just free the heap */ + for (v= polygon->proxy->v[LEFT]; v; v= nextv) + { + nextv= v->next; + FREE(v); + } + polygon->active= 0; + } + } + return nc; +} + + +static void add_left(polygon_node *p, double x, double y) +{ + vertex_node *nv; + + /* Create a new vertex node and set its fields */ + MALLOC(nv, sizeof(vertex_node), "vertex node creation", vertex_node); + nv->x= x; + nv->y= y; + + /* Add vertex nv to the left end of the polygon's vertex list */ + nv->next= p->proxy->v[LEFT]; + + /* Update proxy->[LEFT] to point to nv */ + p->proxy->v[LEFT]= nv; +} + + +static void merge_left(polygon_node *p, polygon_node *q, polygon_node *list) +{ + polygon_node *target; + + /* Label contour as a hole */ + q->proxy->hole= TRUE; + + if (p->proxy != q->proxy) + { + /* Assign p's vertex list to the left end of q's list */ + p->proxy->v[RIGHT]->next= q->proxy->v[LEFT]; + q->proxy->v[LEFT]= p->proxy->v[LEFT]; + + /* Redirect any p->proxy references to q->proxy */ + + for (target= p->proxy; list; list= list->next) + { + if (list->proxy == target) + { + list->active= FALSE; + list->proxy= q->proxy; + } + } + } +} + + +static void add_right(polygon_node *p, double x, double y) +{ + vertex_node *nv; + + /* Create a new vertex node and set its fields */ + MALLOC(nv, sizeof(vertex_node), "vertex node creation", vertex_node); + nv->x= x; + nv->y= y; + nv->next= NULL; + + /* Add vertex nv to the right end of the polygon's vertex list */ + p->proxy->v[RIGHT]->next= nv; + + /* Update proxy->v[RIGHT] to point to nv */ + p->proxy->v[RIGHT]= nv; +} + + +static void merge_right(polygon_node *p, polygon_node *q, polygon_node *list) +{ + polygon_node *target; + + /* Label contour as external */ + q->proxy->hole= FALSE; + + if (p->proxy != q->proxy) + { + /* Assign p's vertex list to the right end of q's list */ + q->proxy->v[RIGHT]->next= p->proxy->v[LEFT]; + q->proxy->v[RIGHT]= p->proxy->v[RIGHT]; + + /* Redirect any p->proxy references to q->proxy */ + for (target= p->proxy; list; list= list->next) + { + if (list->proxy == target) + { + list->active= FALSE; + list->proxy= q->proxy; + } + } + } +} + + +static void add_local_min(polygon_node **p, edge_node *edge, + double x, double y) +{ + polygon_node *existing_min; + vertex_node *nv; + + existing_min= *p; + + MALLOC(*p, sizeof(polygon_node), "polygon node creation", polygon_node); + + /* Create a new vertex node and set its fields */ + MALLOC(nv, sizeof(vertex_node), "vertex node creation", vertex_node); + nv->x= x; + nv->y= y; + nv->next= NULL; + + /* Initialise proxy to point to p itself */ + (*p)->proxy= (*p); + (*p)->active= TRUE; + (*p)->next= existing_min; + + /* Make v[LEFT] and v[RIGHT] point to new vertex nv */ + (*p)->v[LEFT]= nv; + (*p)->v[RIGHT]= nv; + + /* Assign polygon p to the edge */ + edge->outp[ABOVE]= *p; +} + + +static int count_tristrips(polygon_node *tn) +{ + int total; + + for (total= 0; tn; tn= tn->next) + if (tn->active > 2) + total++; + return total; +} + + +static void add_vertex(vertex_node **t, double x, double y) +{ + if (!(*t)) + { + MALLOC(*t, sizeof(vertex_node), "tristrip vertex creation", vertex_node); + (*t)->x= x; + (*t)->y= y; + (*t)->next= NULL; + } + else + /* Head further down the list */ + add_vertex(&((*t)->next), x, y); +} + + +static void new_tristrip(polygon_node **tn, edge_node *edge, + double x, double y) +{ + if (!(*tn)) + { + MALLOC(*tn, sizeof(polygon_node), "tristrip node creation", polygon_node); + (*tn)->next= NULL; + (*tn)->v[LEFT]= NULL; + (*tn)->v[RIGHT]= NULL; + (*tn)->active= 1; + add_vertex(&((*tn)->v[LEFT]), x, y); + edge->outp[ABOVE]= *tn; + } + else + /* Head further down the list */ + new_tristrip(&((*tn)->next), edge, x, y); +} + + +static bbox *create_contour_bboxes(gpc_polygon *p) +{ + bbox *box; + int c, v; + + MALLOC(box, p->num_contours * sizeof(bbox), "Bounding box creation", bbox); + + /* Construct contour bounding boxes */ + for (c= 0; c < p->num_contours; c++) + { + /* Initialise bounding box extent */ + box[c].xmin= DBL_MAX; + box[c].ymin= DBL_MAX; + box[c].xmax= -DBL_MAX; + box[c].ymax= -DBL_MAX; + + for (v= 0; v < p->contour[c].num_vertices; v++) + { + /* Adjust bounding box */ + if (p->contour[c].vertex[v].x < box[c].xmin) + box[c].xmin= p->contour[c].vertex[v].x; + if (p->contour[c].vertex[v].y < box[c].ymin) + box[c].ymin= p->contour[c].vertex[v].y; + if (p->contour[c].vertex[v].x > box[c].xmax) + box[c].xmax= p->contour[c].vertex[v].x; + if (p->contour[c].vertex[v].y > box[c].ymax) + box[c].ymax= p->contour[c].vertex[v].y; + } + } + return box; +} + + +static void minimax_test(gpc_polygon *subj, gpc_polygon *clip, gpc_op op) +{ + bbox *s_bbox, *c_bbox; + int s, c, *o_table, overlap; + + s_bbox= create_contour_bboxes(subj); + c_bbox= create_contour_bboxes(clip); + + MALLOC(o_table, subj->num_contours * clip->num_contours * sizeof(int), + "overlap table creation", int); + + /* Check all subject contour bounding boxes against clip boxes */ + for (s= 0; s < subj->num_contours; s++) + for (c= 0; c < clip->num_contours; c++) + o_table[c * subj->num_contours + s]= + (!((s_bbox[s].xmax < c_bbox[c].xmin) || + (s_bbox[s].xmin > c_bbox[c].xmax))) && + (!((s_bbox[s].ymax < c_bbox[c].ymin) || + (s_bbox[s].ymin > c_bbox[c].ymax))); + + /* For each clip contour, search for any subject contour overlaps */ + for (c= 0; c < clip->num_contours; c++) + { + overlap= 0; + for (s= 0; (!overlap) && (s < subj->num_contours); s++) + overlap= o_table[c * subj->num_contours + s]; + + if (!overlap) + /* Flag non contributing status by negating vertex count */ + clip->contour[c].num_vertices = -clip->contour[c].num_vertices; + } + + if (op == GPC_INT) + { + /* For each subject contour, search for any clip contour overlaps */ + for (s= 0; s < subj->num_contours; s++) + { + overlap= 0; + for (c= 0; (!overlap) && (c < clip->num_contours); c++) + overlap= o_table[c * subj->num_contours + s]; + + if (!overlap) + /* Flag non contributing status by negating vertex count */ + subj->contour[s].num_vertices = -subj->contour[s].num_vertices; + } + } + + FREE(s_bbox); + FREE(c_bbox); + FREE(o_table); +} + + +/* +=========================================================================== + Public Functions +=========================================================================== +*/ + +void gpc_free_polygon(gpc_polygon *p) +{ + int c; + + for (c= 0; c < p->num_contours; c++) + FREE(p->contour[c].vertex); + FREE(p->hole); + FREE(p->contour); + p->num_contours= 0; +} + + +#if 0 +void gpc_read_polygon(FILE *fp, int read_hole_flags, gpc_polygon *p) +{ + int c, v; + + fscanf(fp, "%d", &(p->num_contours)); + MALLOC(p->hole, p->num_contours * sizeof(int), + "hole flag array creation", int); + MALLOC(p->contour, p->num_contours + * sizeof(gpc_vertex_list), "contour creation", gpc_vertex_list); + for (c= 0; c < p->num_contours; c++) + { + fscanf(fp, "%d", &(p->contour[c].num_vertices)); + + if (read_hole_flags) + fscanf(fp, "%d", &(p->hole[c])); + else + p->hole[c]= FALSE; /* Assume all contours to be external */ + + MALLOC(p->contour[c].vertex, p->contour[c].num_vertices + * sizeof(gpc_vertex), "vertex creation", gpc_vertex); + for (v= 0; v < p->contour[c].num_vertices; v++) + fscanf(fp, "%lf %lf", &(p->contour[c].vertex[v].x), + &(p->contour[c].vertex[v].y)); + } +} +#endif + +#if 0 +void gpc_write_polygon(FILE *fp, int write_hole_flags, gpc_polygon *p) +{ + int c, v; + + fprintf(fp, "%d\n", p->num_contours); + for (c= 0; c < p->num_contours; c++) + { + fprintf(fp, "%d\n", p->contour[c].num_vertices); + + if (write_hole_flags) + fprintf(fp, "%d\n", p->hole[c]); + + for (v= 0; v < p->contour[c].num_vertices; v++) + fprintf(fp, "% .*lf % .*lf\n", + DBL_DIG, p->contour[c].vertex[v].x, + DBL_DIG, p->contour[c].vertex[v].y); + } +} +#endif + +void gpc_add_contour(gpc_polygon *p, gpc_vertex_list *new_contour, int hole) +{ + int *extended_hole, c, v; + gpc_vertex_list *extended_contour; + + /* Create an extended hole array */ + MALLOC(extended_hole, (p->num_contours + 1) + * sizeof(int), "contour hole addition", int); + + /* Create an extended contour array */ + MALLOC(extended_contour, (p->num_contours + 1) + * sizeof(gpc_vertex_list), "contour addition", gpc_vertex_list); + + /* Copy the old contour and hole data into the extended arrays */ + for (c= 0; c < p->num_contours; c++) + { + extended_hole[c]= p->hole[c]; + extended_contour[c]= p->contour[c]; + } + + /* Copy the new contour and hole onto the end of the extended arrays */ + c= p->num_contours; + extended_hole[c]= hole; + extended_contour[c].num_vertices= new_contour->num_vertices; + MALLOC(extended_contour[c].vertex, new_contour->num_vertices + * sizeof(gpc_vertex), "contour addition", gpc_vertex); + for (v= 0; v < new_contour->num_vertices; v++) + extended_contour[c].vertex[v]= new_contour->vertex[v]; + + /* Dispose of the old contour */ + FREE(p->contour); + FREE(p->hole); + + /* Update the polygon information */ + p->num_contours++; + p->hole= extended_hole; + p->contour= extended_contour; +} + + +void gpc_polygon_clip(gpc_op op, gpc_polygon *subj, gpc_polygon *clip, + gpc_polygon *result) +{ + sb_tree *sbtree= NULL; + it_node *it= NULL, *intersect; + edge_node *edge, *prev_edge, *next_edge, *succ_edge, *e0, *e1; + edge_node *aet= NULL, *c_heap= NULL, *s_heap= NULL; + lmt_node *lmt= NULL, *local_min; + polygon_node *out_poly= NULL, *p, *q, *poly, *npoly, *cf= NULL; + vertex_node *vtx, *nv; + h_state horiz[2]; + int in[2], exists[2], parity[2]= {LEFT, LEFT}; + int c, v, contributing, search, scanbeam= 0, sbt_entries= 0; + int vclass, bl, br, tl, tr; + double *sbt= NULL, xb, px, yb, yt, dy, ix, iy; + + /* Test for trivial NULL result cases */ + if (((subj->num_contours == 0) && (clip->num_contours == 0)) + || ((subj->num_contours == 0) && ((op == GPC_INT) || (op == GPC_DIFF))) + || ((clip->num_contours == 0) && (op == GPC_INT))) + { + result->num_contours= 0; + result->hole= NULL; + result->contour= NULL; + return; + } + + /* Identify potentialy contributing contours */ + if (((op == GPC_INT) || (op == GPC_DIFF)) + && (subj->num_contours > 0) && (clip->num_contours > 0)) + minimax_test(subj, clip, op); + + /* Build LMT */ + if (subj->num_contours > 0) + s_heap= build_lmt(&lmt, &sbtree, &sbt_entries, subj, SUBJ, op); + if (clip->num_contours > 0) + c_heap= build_lmt(&lmt, &sbtree, &sbt_entries, clip, CLIP, op); + + /* Return a NULL result if no contours contribute */ + if (lmt == NULL) + { + result->num_contours= 0; + result->hole= NULL; + result->contour= NULL; + reset_lmt(&lmt); + FREE(s_heap); + FREE(c_heap); + return; + } + + /* Build scanbeam table from scanbeam tree */ + MALLOC(sbt, sbt_entries * sizeof(double), "sbt creation", double); + build_sbt(&scanbeam, sbt, sbtree); + scanbeam= 0; + free_sbtree(&sbtree); + + /* Allow pointer re-use without causing memory leak */ + if (subj == result) + gpc_free_polygon(subj); + if (clip == result) + gpc_free_polygon(clip); + + /* Invert clip polygon for difference operation */ + if (op == GPC_DIFF) + parity[CLIP]= RIGHT; + + local_min= lmt; + + /* Process each scanbeam */ + while (scanbeam < sbt_entries) + { + /* Set yb and yt to the bottom and top of the scanbeam */ + yb= sbt[scanbeam++]; + if (scanbeam < sbt_entries) + { + yt= sbt[scanbeam]; + dy= yt - yb; + } + + /* === SCANBEAM BOUNDARY PROCESSING ================================ */ + + /* If LMT node corresponding to yb exists */ + if (local_min) + { + if (local_min->y == yb) + { + /* Add edges starting at this local minimum to the AET */ + for (edge= local_min->first_bound; edge; edge= edge->next_bound) + add_edge_to_aet(&aet, edge, NULL); + + local_min= local_min->next; + } + } + + /* Set dummy previous x value */ + px= -DBL_MAX; + + /* Create bundles within AET */ + e0= aet; + e1= aet; + + /* Set up bundle fields of first edge */ + aet->bundle[ABOVE][ aet->type]= (aet->top.y != yb); + aet->bundle[ABOVE][!aet->type]= FALSE; + aet->bstate[ABOVE]= UNBUNDLED; + + for (next_edge= aet->next; next_edge; next_edge= next_edge->next) + { + /* Set up bundle fields of next edge */ + next_edge->bundle[ABOVE][ next_edge->type]= (next_edge->top.y != yb); + next_edge->bundle[ABOVE][!next_edge->type]= FALSE; + next_edge->bstate[ABOVE]= UNBUNDLED; + + /* Bundle edges above the scanbeam boundary if they coincide */ + if (next_edge->bundle[ABOVE][next_edge->type]) + { + if (EQ(e0->xb, next_edge->xb) && EQ(e0->dx, next_edge->dx) + && (e0->top.y != yb)) + { + next_edge->bundle[ABOVE][ next_edge->type]^= + e0->bundle[ABOVE][ next_edge->type]; + next_edge->bundle[ABOVE][!next_edge->type]= + e0->bundle[ABOVE][!next_edge->type]; + next_edge->bstate[ABOVE]= BUNDLE_HEAD; + e0->bundle[ABOVE][CLIP]= FALSE; + e0->bundle[ABOVE][SUBJ]= FALSE; + e0->bstate[ABOVE]= BUNDLE_TAIL; + } + e0= next_edge; + } + } + + horiz[CLIP]= NH; + horiz[SUBJ]= NH; + + /* Process each edge at this scanbeam boundary */ + for (edge= aet; edge; edge= edge->next) + { + exists[CLIP]= edge->bundle[ABOVE][CLIP] + + (edge->bundle[BELOW][CLIP] << 1); + exists[SUBJ]= edge->bundle[ABOVE][SUBJ] + + (edge->bundle[BELOW][SUBJ] << 1); + + if (exists[CLIP] || exists[SUBJ]) + { + /* Set bundle side */ + edge->bside[CLIP]= parity[CLIP]; + edge->bside[SUBJ]= parity[SUBJ]; + + /* Determine contributing status and quadrant occupancies */ + switch (op) + { + case GPC_DIFF: + case GPC_INT: + contributing= (exists[CLIP] && (parity[SUBJ] || horiz[SUBJ])) + || (exists[SUBJ] && (parity[CLIP] || horiz[CLIP])) + || (exists[CLIP] && exists[SUBJ] + && (parity[CLIP] == parity[SUBJ])); + br= (parity[CLIP]) + && (parity[SUBJ]); + bl= (parity[CLIP] ^ edge->bundle[ABOVE][CLIP]) + && (parity[SUBJ] ^ edge->bundle[ABOVE][SUBJ]); + tr= (parity[CLIP] ^ (horiz[CLIP]!=NH)) + && (parity[SUBJ] ^ (horiz[SUBJ]!=NH)); + tl= (parity[CLIP] ^ (horiz[CLIP]!=NH) ^ edge->bundle[BELOW][CLIP]) + && (parity[SUBJ] ^ (horiz[SUBJ]!=NH) ^ edge->bundle[BELOW][SUBJ]); + break; + case GPC_XOR: + contributing= exists[CLIP] || exists[SUBJ]; + br= (parity[CLIP]) + ^ (parity[SUBJ]); + bl= (parity[CLIP] ^ edge->bundle[ABOVE][CLIP]) + ^ (parity[SUBJ] ^ edge->bundle[ABOVE][SUBJ]); + tr= (parity[CLIP] ^ (horiz[CLIP]!=NH)) + ^ (parity[SUBJ] ^ (horiz[SUBJ]!=NH)); + tl= (parity[CLIP] ^ (horiz[CLIP]!=NH) ^ edge->bundle[BELOW][CLIP]) + ^ (parity[SUBJ] ^ (horiz[SUBJ]!=NH) ^ edge->bundle[BELOW][SUBJ]); + break; + case GPC_UNION: + contributing= (exists[CLIP] && (!parity[SUBJ] || horiz[SUBJ])) + || (exists[SUBJ] && (!parity[CLIP] || horiz[CLIP])) + || (exists[CLIP] && exists[SUBJ] + && (parity[CLIP] == parity[SUBJ])); + br= (parity[CLIP]) + || (parity[SUBJ]); + bl= (parity[CLIP] ^ edge->bundle[ABOVE][CLIP]) + || (parity[SUBJ] ^ edge->bundle[ABOVE][SUBJ]); + tr= (parity[CLIP] ^ (horiz[CLIP]!=NH)) + || (parity[SUBJ] ^ (horiz[SUBJ]!=NH)); + tl= (parity[CLIP] ^ (horiz[CLIP]!=NH) ^ edge->bundle[BELOW][CLIP]) + || (parity[SUBJ] ^ (horiz[SUBJ]!=NH) ^ edge->bundle[BELOW][SUBJ]); + break; + } + + /* Update parity */ + parity[CLIP]^= edge->bundle[ABOVE][CLIP]; + parity[SUBJ]^= edge->bundle[ABOVE][SUBJ]; + + /* Update horizontal state */ + if (exists[CLIP]) + horiz[CLIP]= + next_h_state[horiz[CLIP]] + [((exists[CLIP] - 1) << 1) + parity[CLIP]]; + if (exists[SUBJ]) + horiz[SUBJ]= + next_h_state[horiz[SUBJ]] + [((exists[SUBJ] - 1) << 1) + parity[SUBJ]]; + + vclass= tr + (tl << 1) + (br << 2) + (bl << 3); + + if (contributing) + { + xb= edge->xb; + + switch (vclass) + { + case EMN: + case IMN: + add_local_min(&out_poly, edge, xb, yb); + px= xb; + cf= edge->outp[ABOVE]; + break; + case ERI: + if (xb != px) + { + add_right(cf, xb, yb); + px= xb; + } + edge->outp[ABOVE]= cf; + cf= NULL; + break; + case ELI: + add_left(edge->outp[BELOW], xb, yb); + px= xb; + cf= edge->outp[BELOW]; + break; + case EMX: + if (xb != px) + { + add_left(cf, xb, yb); + px= xb; + } + merge_right(cf, edge->outp[BELOW], out_poly); + cf= NULL; + break; + case ILI: + if (xb != px) + { + add_left(cf, xb, yb); + px= xb; + } + edge->outp[ABOVE]= cf; + cf= NULL; + break; + case IRI: + add_right(edge->outp[BELOW], xb, yb); + px= xb; + cf= edge->outp[BELOW]; + edge->outp[BELOW]= NULL; + break; + case IMX: + if (xb != px) + { + add_right(cf, xb, yb); + px= xb; + } + merge_left(cf, edge->outp[BELOW], out_poly); + cf= NULL; + edge->outp[BELOW]= NULL; + break; + case IMM: + if (xb != px) + { + add_right(cf, xb, yb); + px= xb; + } + merge_left(cf, edge->outp[BELOW], out_poly); + edge->outp[BELOW]= NULL; + add_local_min(&out_poly, edge, xb, yb); + cf= edge->outp[ABOVE]; + break; + case EMM: + if (xb != px) + { + add_left(cf, xb, yb); + px= xb; + } + merge_right(cf, edge->outp[BELOW], out_poly); + edge->outp[BELOW]= NULL; + add_local_min(&out_poly, edge, xb, yb); + cf= edge->outp[ABOVE]; + break; + case LED: + if (edge->bot.y == yb) + add_left(edge->outp[BELOW], xb, yb); + edge->outp[ABOVE]= edge->outp[BELOW]; + px= xb; + break; + case RED: + if (edge->bot.y == yb) + add_right(edge->outp[BELOW], xb, yb); + edge->outp[ABOVE]= edge->outp[BELOW]; + px= xb; + break; + default: + break; + } /* End of switch */ + } /* End of contributing conditional */ + } /* End of edge exists conditional */ + } /* End of AET loop */ + + /* Delete terminating edges from the AET, otherwise compute xt */ + for (edge= aet; edge; edge= edge->next) + { + if (edge->top.y == yb) + { + prev_edge= edge->prev; + next_edge= edge->next; + if (prev_edge) + prev_edge->next= next_edge; + else + aet= next_edge; + if (next_edge) + next_edge->prev= prev_edge; + + /* Copy bundle head state to the adjacent tail edge if required */ + if ((edge->bstate[BELOW] == BUNDLE_HEAD) && prev_edge) + { + if (prev_edge->bstate[BELOW] == BUNDLE_TAIL) + { + prev_edge->outp[BELOW]= edge->outp[BELOW]; + prev_edge->bstate[BELOW]= UNBUNDLED; + if (prev_edge->prev) + if (prev_edge->prev->bstate[BELOW] == BUNDLE_TAIL) + prev_edge->bstate[BELOW]= BUNDLE_HEAD; + } + } + } + else + { + if (edge->top.y == yt) + edge->xt= edge->top.x; + else + edge->xt= edge->bot.x + edge->dx * (yt - edge->bot.y); + } + } + + if (scanbeam < sbt_entries) + { + /* === SCANBEAM INTERIOR PROCESSING ============================== */ + + build_intersection_table(&it, aet, dy); + + /* Process each node in the intersection table */ + for (intersect= it; intersect; intersect= intersect->next) + { + e0= intersect->ie[0]; + e1= intersect->ie[1]; + + /* Only generate output for contributing intersections */ + if ((e0->bundle[ABOVE][CLIP] || e0->bundle[ABOVE][SUBJ]) + && (e1->bundle[ABOVE][CLIP] || e1->bundle[ABOVE][SUBJ])) + { + p= e0->outp[ABOVE]; + q= e1->outp[ABOVE]; + ix= intersect->point.x; + iy= intersect->point.y + yb; + + in[CLIP]= ( e0->bundle[ABOVE][CLIP] && !e0->bside[CLIP]) + || ( e1->bundle[ABOVE][CLIP] && e1->bside[CLIP]) + || (!e0->bundle[ABOVE][CLIP] && !e1->bundle[ABOVE][CLIP] + && e0->bside[CLIP] && e1->bside[CLIP]); + in[SUBJ]= ( e0->bundle[ABOVE][SUBJ] && !e0->bside[SUBJ]) + || ( e1->bundle[ABOVE][SUBJ] && e1->bside[SUBJ]) + || (!e0->bundle[ABOVE][SUBJ] && !e1->bundle[ABOVE][SUBJ] + && e0->bside[SUBJ] && e1->bside[SUBJ]); + + /* Determine quadrant occupancies */ + switch (op) + { + case GPC_DIFF: + case GPC_INT: + tr= (in[CLIP]) + && (in[SUBJ]); + tl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP]) + && (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ]); + br= (in[CLIP] ^ e0->bundle[ABOVE][CLIP]) + && (in[SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + bl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP] ^ e0->bundle[ABOVE][CLIP]) + && (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + break; + case GPC_XOR: + tr= (in[CLIP]) + ^ (in[SUBJ]); + tl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP]) + ^ (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ]); + br= (in[CLIP] ^ e0->bundle[ABOVE][CLIP]) + ^ (in[SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + bl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP] ^ e0->bundle[ABOVE][CLIP]) + ^ (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + break; + case GPC_UNION: + tr= (in[CLIP]) + || (in[SUBJ]); + tl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP]) + || (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ]); + br= (in[CLIP] ^ e0->bundle[ABOVE][CLIP]) + || (in[SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + bl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP] ^ e0->bundle[ABOVE][CLIP]) + || (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + break; + } + + vclass= tr + (tl << 1) + (br << 2) + (bl << 3); + + switch (vclass) + { + case EMN: + add_local_min(&out_poly, e0, ix, iy); + e1->outp[ABOVE]= e0->outp[ABOVE]; + break; + case ERI: + if (p) + { + add_right(p, ix, iy); + e1->outp[ABOVE]= p; + e0->outp[ABOVE]= NULL; + } + break; + case ELI: + if (q) + { + add_left(q, ix, iy); + e0->outp[ABOVE]= q; + e1->outp[ABOVE]= NULL; + } + break; + case EMX: + if (p && q) + { + add_left(p, ix, iy); + merge_right(p, q, out_poly); + e0->outp[ABOVE]= NULL; + e1->outp[ABOVE]= NULL; + } + break; + case IMN: + add_local_min(&out_poly, e0, ix, iy); + e1->outp[ABOVE]= e0->outp[ABOVE]; + break; + case ILI: + if (p) + { + add_left(p, ix, iy); + e1->outp[ABOVE]= p; + e0->outp[ABOVE]= NULL; + } + break; + case IRI: + if (q) + { + add_right(q, ix, iy); + e0->outp[ABOVE]= q; + e1->outp[ABOVE]= NULL; + } + break; + case IMX: + if (p && q) + { + add_right(p, ix, iy); + merge_left(p, q, out_poly); + e0->outp[ABOVE]= NULL; + e1->outp[ABOVE]= NULL; + } + break; + case IMM: + if (p && q) + { + add_right(p, ix, iy); + merge_left(p, q, out_poly); + add_local_min(&out_poly, e0, ix, iy); + e1->outp[ABOVE]= e0->outp[ABOVE]; + } + break; + case EMM: + if (p && q) + { + add_left(p, ix, iy); + merge_right(p, q, out_poly); + add_local_min(&out_poly, e0, ix, iy); + e1->outp[ABOVE]= e0->outp[ABOVE]; + } + break; + default: + break; + } /* End of switch */ + } /* End of contributing intersection conditional */ + + /* Swap bundle sides in response to edge crossing */ + if (e0->bundle[ABOVE][CLIP]) + e1->bside[CLIP]= !e1->bside[CLIP]; + if (e1->bundle[ABOVE][CLIP]) + e0->bside[CLIP]= !e0->bside[CLIP]; + if (e0->bundle[ABOVE][SUBJ]) + e1->bside[SUBJ]= !e1->bside[SUBJ]; + if (e1->bundle[ABOVE][SUBJ]) + e0->bside[SUBJ]= !e0->bside[SUBJ]; + + /* Swap e0 and e1 bundles in the AET */ + prev_edge= e0->prev; + next_edge= e1->next; + if (next_edge) + next_edge->prev= e0; + + if (e0->bstate[ABOVE] == BUNDLE_HEAD) + { + search= TRUE; + while (search) + { + prev_edge= prev_edge->prev; + if (prev_edge) + { + if (prev_edge->bstate[ABOVE] != BUNDLE_TAIL) + search= FALSE; + } + else + search= FALSE; + } + } + if (!prev_edge) + { + aet->prev= e1; + e1->next= aet; + aet= e0->next; + } + else + { + prev_edge->next->prev= e1; + e1->next= prev_edge->next; + prev_edge->next= e0->next; + } + e0->next->prev= prev_edge; + e1->next->prev= e1; + e0->next= next_edge; + } /* End of IT loop*/ + + /* Prepare for next scanbeam */ + for (edge= aet; edge; edge= next_edge) + { + next_edge= edge->next; + succ_edge= edge->succ; + + if ((edge->top.y == yt) && succ_edge) + { + /* Replace AET edge by its successor */ + succ_edge->outp[BELOW]= edge->outp[ABOVE]; + succ_edge->bstate[BELOW]= edge->bstate[ABOVE]; + succ_edge->bundle[BELOW][CLIP]= edge->bundle[ABOVE][CLIP]; + succ_edge->bundle[BELOW][SUBJ]= edge->bundle[ABOVE][SUBJ]; + prev_edge= edge->prev; + if (prev_edge) + prev_edge->next= succ_edge; + else + aet= succ_edge; + if (next_edge) + next_edge->prev= succ_edge; + succ_edge->prev= prev_edge; + succ_edge->next= next_edge; + } + else + { + /* Update this edge */ + edge->outp[BELOW]= edge->outp[ABOVE]; + edge->bstate[BELOW]= edge->bstate[ABOVE]; + edge->bundle[BELOW][CLIP]= edge->bundle[ABOVE][CLIP]; + edge->bundle[BELOW][SUBJ]= edge->bundle[ABOVE][SUBJ]; + edge->xb= edge->xt; + } + edge->outp[ABOVE]= NULL; + } + } + } /* === END OF SCANBEAM PROCESSING ================================== */ + + /* Generate result polygon from out_poly */ + result->contour= NULL; + result->hole= NULL; + result->num_contours= count_contours(out_poly); + if (result->num_contours > 0) + { + MALLOC(result->hole, result->num_contours + * sizeof(int), "hole flag table creation", int); + MALLOC(result->contour, result->num_contours + * sizeof(gpc_vertex_list), "contour creation", gpc_vertex_list); + + c= 0; + for (poly= out_poly; poly; poly= npoly) + { + npoly= poly->next; + if (poly->active) + { + result->hole[c]= poly->proxy->hole; + result->contour[c].num_vertices= poly->active; + MALLOC(result->contour[c].vertex, + result->contour[c].num_vertices * sizeof(gpc_vertex), + "vertex creation", gpc_vertex); + + v= result->contour[c].num_vertices - 1; + for (vtx= poly->proxy->v[LEFT]; vtx; vtx= nv) + { + nv= vtx->next; + result->contour[c].vertex[v].x= vtx->x; + result->contour[c].vertex[v].y= vtx->y; + FREE(vtx); + v--; + } + c++; + } + FREE(poly); + } + } + else + { + for (poly= out_poly; poly; poly= npoly) + { + npoly= poly->next; + FREE(poly); + } + } + + /* Tidy up */ + reset_it(&it); + reset_lmt(&lmt); + FREE(c_heap); + FREE(s_heap); + FREE(sbt); +} + +#if 0 +void gpc_free_tristrip(gpc_tristrip *t) +{ + int s; + + for (s= 0; s < t->num_strips; s++) + FREE(t->strip[s].vertex); + FREE(t->strip); + t->num_strips= 0; +} +#endif + +#if 0 +void gpc_polygon_to_tristrip(gpc_polygon *s, gpc_tristrip *t) +{ + gpc_polygon c; + + c.num_contours= 0; + c.hole= NULL; + c.contour= NULL; + gpc_tristrip_clip(GPC_DIFF, s, &c, t); +} +#endif + +#if 0 +void gpc_tristrip_clip(gpc_op op, gpc_polygon *subj, gpc_polygon *clip, + gpc_tristrip *result) +{ + sb_tree *sbtree= NULL; + it_node *it= NULL, *intersect; + edge_node *edge, *prev_edge, *next_edge, *succ_edge, *e0, *e1; + edge_node *aet= NULL, *c_heap= NULL, *s_heap= NULL, *cf; + lmt_node *lmt= NULL, *local_min; + polygon_node *tlist= NULL, *tn, *tnn, *p, *q; + vertex_node *lt, *ltn, *rt, *rtn; + h_state horiz[2]; + vertex_type cft; + int in[2], exists[2], parity[2]= {LEFT, LEFT}; + int s, v, contributing, search, scanbeam= 0, sbt_entries= 0; + int vclass, bl, br, tl, tr; + double *sbt= NULL, xb, px, nx, yb, yt, dy, ix, iy; + + /* Test for trivial NULL result cases */ + if (((subj->num_contours == 0) && (clip->num_contours == 0)) + || ((subj->num_contours == 0) && ((op == GPC_INT) || (op == GPC_DIFF))) + || ((clip->num_contours == 0) && (op == GPC_INT))) + { + result->num_strips= 0; + result->strip= NULL; + return; + } + + /* Identify potentialy contributing contours */ + if (((op == GPC_INT) || (op == GPC_DIFF)) + && (subj->num_contours > 0) && (clip->num_contours > 0)) + minimax_test(subj, clip, op); + + /* Build LMT */ + if (subj->num_contours > 0) + s_heap= build_lmt(&lmt, &sbtree, &sbt_entries, subj, SUBJ, op); + if (clip->num_contours > 0) + c_heap= build_lmt(&lmt, &sbtree, &sbt_entries, clip, CLIP, op); + + /* Return a NULL result if no contours contribute */ + if (lmt == NULL) + { + result->num_strips= 0; + result->strip= NULL; + reset_lmt(&lmt); + FREE(s_heap); + FREE(c_heap); + return; + } + + /* Build scanbeam table from scanbeam tree */ + MALLOC(sbt, sbt_entries * sizeof(double), "sbt creation", double); + build_sbt(&scanbeam, sbt, sbtree); + scanbeam= 0; + free_sbtree(&sbtree); + + /* Invert clip polygon for difference operation */ + if (op == GPC_DIFF) + parity[CLIP]= RIGHT; + + local_min= lmt; + + /* Process each scanbeam */ + while (scanbeam < sbt_entries) + { + /* Set yb and yt to the bottom and top of the scanbeam */ + yb= sbt[scanbeam++]; + if (scanbeam < sbt_entries) + { + yt= sbt[scanbeam]; + dy= yt - yb; + } + + /* === SCANBEAM BOUNDARY PROCESSING ================================ */ + + /* If LMT node corresponding to yb exists */ + if (local_min) + { + if (local_min->y == yb) + { + /* Add edges starting at this local minimum to the AET */ + for (edge= local_min->first_bound; edge; edge= edge->next_bound) + add_edge_to_aet(&aet, edge, NULL); + + local_min= local_min->next; + } + } + + /* Set dummy previous x value */ + px= -DBL_MAX; + + /* Create bundles within AET */ + e0= aet; + e1= aet; + + /* Set up bundle fields of first edge */ + aet->bundle[ABOVE][ aet->type]= (aet->top.y != yb); + aet->bundle[ABOVE][!aet->type]= FALSE; + aet->bstate[ABOVE]= UNBUNDLED; + + for (next_edge= aet->next; next_edge; next_edge= next_edge->next) + { + /* Set up bundle fields of next edge */ + next_edge->bundle[ABOVE][ next_edge->type]= (next_edge->top.y != yb); + next_edge->bundle[ABOVE][!next_edge->type]= FALSE; + next_edge->bstate[ABOVE]= UNBUNDLED; + + /* Bundle edges above the scanbeam boundary if they coincide */ + if (next_edge->bundle[ABOVE][next_edge->type]) + { + if (EQ(e0->xb, next_edge->xb) && EQ(e0->dx, next_edge->dx) + && (e0->top.y != yb)) + { + next_edge->bundle[ABOVE][ next_edge->type]^= + e0->bundle[ABOVE][ next_edge->type]; + next_edge->bundle[ABOVE][!next_edge->type]= + e0->bundle[ABOVE][!next_edge->type]; + next_edge->bstate[ABOVE]= BUNDLE_HEAD; + e0->bundle[ABOVE][CLIP]= FALSE; + e0->bundle[ABOVE][SUBJ]= FALSE; + e0->bstate[ABOVE]= BUNDLE_TAIL; + } + e0= next_edge; + } + } + + horiz[CLIP]= NH; + horiz[SUBJ]= NH; + + /* Process each edge at this scanbeam boundary */ + for (edge= aet; edge; edge= edge->next) + { + exists[CLIP]= edge->bundle[ABOVE][CLIP] + + (edge->bundle[BELOW][CLIP] << 1); + exists[SUBJ]= edge->bundle[ABOVE][SUBJ] + + (edge->bundle[BELOW][SUBJ] << 1); + + if (exists[CLIP] || exists[SUBJ]) + { + /* Set bundle side */ + edge->bside[CLIP]= parity[CLIP]; + edge->bside[SUBJ]= parity[SUBJ]; + + /* Determine contributing status and quadrant occupancies */ + switch (op) + { + case GPC_DIFF: + case GPC_INT: + contributing= (exists[CLIP] && (parity[SUBJ] || horiz[SUBJ])) + || (exists[SUBJ] && (parity[CLIP] || horiz[CLIP])) + || (exists[CLIP] && exists[SUBJ] + && (parity[CLIP] == parity[SUBJ])); + br= (parity[CLIP]) + && (parity[SUBJ]); + bl= (parity[CLIP] ^ edge->bundle[ABOVE][CLIP]) + && (parity[SUBJ] ^ edge->bundle[ABOVE][SUBJ]); + tr= (parity[CLIP] ^ (horiz[CLIP]!=NH)) + && (parity[SUBJ] ^ (horiz[SUBJ]!=NH)); + tl= (parity[CLIP] ^ (horiz[CLIP]!=NH) ^ edge->bundle[BELOW][CLIP]) + && (parity[SUBJ] ^ (horiz[SUBJ]!=NH) ^ edge->bundle[BELOW][SUBJ]); + break; + case GPC_XOR: + contributing= exists[CLIP] || exists[SUBJ]; + br= (parity[CLIP]) + ^ (parity[SUBJ]); + bl= (parity[CLIP] ^ edge->bundle[ABOVE][CLIP]) + ^ (parity[SUBJ] ^ edge->bundle[ABOVE][SUBJ]); + tr= (parity[CLIP] ^ (horiz[CLIP]!=NH)) + ^ (parity[SUBJ] ^ (horiz[SUBJ]!=NH)); + tl= (parity[CLIP] ^ (horiz[CLIP]!=NH) ^ edge->bundle[BELOW][CLIP]) + ^ (parity[SUBJ] ^ (horiz[SUBJ]!=NH) ^ edge->bundle[BELOW][SUBJ]); + break; + case GPC_UNION: + contributing= (exists[CLIP] && (!parity[SUBJ] || horiz[SUBJ])) + || (exists[SUBJ] && (!parity[CLIP] || horiz[CLIP])) + || (exists[CLIP] && exists[SUBJ] + && (parity[CLIP] == parity[SUBJ])); + br= (parity[CLIP]) + || (parity[SUBJ]); + bl= (parity[CLIP] ^ edge->bundle[ABOVE][CLIP]) + || (parity[SUBJ] ^ edge->bundle[ABOVE][SUBJ]); + tr= (parity[CLIP] ^ (horiz[CLIP]!=NH)) + || (parity[SUBJ] ^ (horiz[SUBJ]!=NH)); + tl= (parity[CLIP] ^ (horiz[CLIP]!=NH) ^ edge->bundle[BELOW][CLIP]) + || (parity[SUBJ] ^ (horiz[SUBJ]!=NH) ^ edge->bundle[BELOW][SUBJ]); + break; + } + + /* Update parity */ + parity[CLIP]^= edge->bundle[ABOVE][CLIP]; + parity[SUBJ]^= edge->bundle[ABOVE][SUBJ]; + + /* Update horizontal state */ + if (exists[CLIP]) + horiz[CLIP]= + next_h_state[horiz[CLIP]] + [((exists[CLIP] - 1) << 1) + parity[CLIP]]; + if (exists[SUBJ]) + horiz[SUBJ]= + next_h_state[horiz[SUBJ]] + [((exists[SUBJ] - 1) << 1) + parity[SUBJ]]; + + vclass= tr + (tl << 1) + (br << 2) + (bl << 3); + + if (contributing) + { + xb= edge->xb; + + switch (vclass) + { + case EMN: + new_tristrip(&tlist, edge, xb, yb); + cf= edge; + break; + case ERI: + edge->outp[ABOVE]= cf->outp[ABOVE]; + if (xb != cf->xb) + VERTEX(edge, ABOVE, RIGHT, xb, yb); + cf= NULL; + break; + case ELI: + VERTEX(edge, BELOW, LEFT, xb, yb); + edge->outp[ABOVE]= NULL; + cf= edge; + break; + case EMX: + if (xb != cf->xb) + VERTEX(edge, BELOW, RIGHT, xb, yb); + edge->outp[ABOVE]= NULL; + cf= NULL; + break; + case IMN: + if (cft == LED) + { + if (cf->bot.y != yb) + VERTEX(cf, BELOW, LEFT, cf->xb, yb); + new_tristrip(&tlist, cf, cf->xb, yb); + } + edge->outp[ABOVE]= cf->outp[ABOVE]; + VERTEX(edge, ABOVE, RIGHT, xb, yb); + break; + case ILI: + new_tristrip(&tlist, edge, xb, yb); + cf= edge; + cft= ILI; + break; + case IRI: + if (cft == LED) + { + if (cf->bot.y != yb) + VERTEX(cf, BELOW, LEFT, cf->xb, yb); + new_tristrip(&tlist, cf, cf->xb, yb); + } + VERTEX(edge, BELOW, RIGHT, xb, yb); + edge->outp[ABOVE]= NULL; + break; + case IMX: + VERTEX(edge, BELOW, LEFT, xb, yb); + edge->outp[ABOVE]= NULL; + cft= IMX; + break; + case IMM: + VERTEX(edge, BELOW, LEFT, xb, yb); + edge->outp[ABOVE]= cf->outp[ABOVE]; + if (xb != cf->xb) + VERTEX(cf, ABOVE, RIGHT, xb, yb); + cf= edge; + break; + case EMM: + VERTEX(edge, BELOW, RIGHT, xb, yb); + edge->outp[ABOVE]= NULL; + new_tristrip(&tlist, edge, xb, yb); + cf= edge; + break; + case LED: + if (edge->bot.y == yb) + VERTEX(edge, BELOW, LEFT, xb, yb); + edge->outp[ABOVE]= edge->outp[BELOW]; + cf= edge; + cft= LED; + break; + case RED: + edge->outp[ABOVE]= cf->outp[ABOVE]; + if (cft == LED) + { + if (cf->bot.y == yb) + { + VERTEX(edge, BELOW, RIGHT, xb, yb); + } + else + { + if (edge->bot.y == yb) + { + VERTEX(cf, BELOW, LEFT, cf->xb, yb); + VERTEX(edge, BELOW, RIGHT, xb, yb); + } + } + } + else + { + VERTEX(edge, BELOW, RIGHT, xb, yb); + VERTEX(edge, ABOVE, RIGHT, xb, yb); + } + cf= NULL; + break; + default: + break; + } /* End of switch */ + } /* End of contributing conditional */ + } /* End of edge exists conditional */ + } /* End of AET loop */ + + /* Delete terminating edges from the AET, otherwise compute xt */ + for (edge= aet; edge; edge= edge->next) + { + if (edge->top.y == yb) + { + prev_edge= edge->prev; + next_edge= edge->next; + if (prev_edge) + prev_edge->next= next_edge; + else + aet= next_edge; + if (next_edge) + next_edge->prev= prev_edge; + + /* Copy bundle head state to the adjacent tail edge if required */ + if ((edge->bstate[BELOW] == BUNDLE_HEAD) && prev_edge) + { + if (prev_edge->bstate[BELOW] == BUNDLE_TAIL) + { + prev_edge->outp[BELOW]= edge->outp[BELOW]; + prev_edge->bstate[BELOW]= UNBUNDLED; + if (prev_edge->prev) + if (prev_edge->prev->bstate[BELOW] == BUNDLE_TAIL) + prev_edge->bstate[BELOW]= BUNDLE_HEAD; + } + } + } + else + { + if (edge->top.y == yt) + edge->xt= edge->top.x; + else + edge->xt= edge->bot.x + edge->dx * (yt - edge->bot.y); + } + } + + if (scanbeam < sbt_entries) + { + /* === SCANBEAM INTERIOR PROCESSING ============================== */ + + build_intersection_table(&it, aet, dy); + + /* Process each node in the intersection table */ + for (intersect= it; intersect; intersect= intersect->next) + { + e0= intersect->ie[0]; + e1= intersect->ie[1]; + + /* Only generate output for contributing intersections */ + if ((e0->bundle[ABOVE][CLIP] || e0->bundle[ABOVE][SUBJ]) + && (e1->bundle[ABOVE][CLIP] || e1->bundle[ABOVE][SUBJ])) + { + p= e0->outp[ABOVE]; + q= e1->outp[ABOVE]; + ix= intersect->point.x; + iy= intersect->point.y + yb; + + in[CLIP]= ( e0->bundle[ABOVE][CLIP] && !e0->bside[CLIP]) + || ( e1->bundle[ABOVE][CLIP] && e1->bside[CLIP]) + || (!e0->bundle[ABOVE][CLIP] && !e1->bundle[ABOVE][CLIP] + && e0->bside[CLIP] && e1->bside[CLIP]); + in[SUBJ]= ( e0->bundle[ABOVE][SUBJ] && !e0->bside[SUBJ]) + || ( e1->bundle[ABOVE][SUBJ] && e1->bside[SUBJ]) + || (!e0->bundle[ABOVE][SUBJ] && !e1->bundle[ABOVE][SUBJ] + && e0->bside[SUBJ] && e1->bside[SUBJ]); + + /* Determine quadrant occupancies */ + switch (op) + { + case GPC_DIFF: + case GPC_INT: + tr= (in[CLIP]) + && (in[SUBJ]); + tl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP]) + && (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ]); + br= (in[CLIP] ^ e0->bundle[ABOVE][CLIP]) + && (in[SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + bl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP] ^ e0->bundle[ABOVE][CLIP]) + && (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + break; + case GPC_XOR: + tr= (in[CLIP]) + ^ (in[SUBJ]); + tl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP]) + ^ (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ]); + br= (in[CLIP] ^ e0->bundle[ABOVE][CLIP]) + ^ (in[SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + bl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP] ^ e0->bundle[ABOVE][CLIP]) + ^ (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + break; + case GPC_UNION: + tr= (in[CLIP]) + || (in[SUBJ]); + tl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP]) + || (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ]); + br= (in[CLIP] ^ e0->bundle[ABOVE][CLIP]) + || (in[SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + bl= (in[CLIP] ^ e1->bundle[ABOVE][CLIP] ^ e0->bundle[ABOVE][CLIP]) + || (in[SUBJ] ^ e1->bundle[ABOVE][SUBJ] ^ e0->bundle[ABOVE][SUBJ]); + break; + } + + vclass= tr + (tl << 1) + (br << 2) + (bl << 3); + + switch (vclass) + { + case EMN: + new_tristrip(&tlist, e1, ix, iy); + e0->outp[ABOVE]= e1->outp[ABOVE]; + break; + case ERI: + if (p) + { + P_EDGE(prev_edge, e0, ABOVE, px, iy); + VERTEX(prev_edge, ABOVE, LEFT, px, iy); + VERTEX(e0, ABOVE, RIGHT, ix, iy); + e1->outp[ABOVE]= e0->outp[ABOVE]; + e0->outp[ABOVE]= NULL; + } + break; + case ELI: + if (q) + { + N_EDGE(next_edge, e1, ABOVE, nx, iy); + VERTEX(e1, ABOVE, LEFT, ix, iy); + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + e0->outp[ABOVE]= e1->outp[ABOVE]; + e1->outp[ABOVE]= NULL; + } + break; + case EMX: + if (p && q) + { + VERTEX(e0, ABOVE, LEFT, ix, iy); + e0->outp[ABOVE]= NULL; + e1->outp[ABOVE]= NULL; + } + break; + case IMN: + P_EDGE(prev_edge, e0, ABOVE, px, iy); + VERTEX(prev_edge, ABOVE, LEFT, px, iy); + N_EDGE(next_edge, e1, ABOVE, nx, iy); + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + new_tristrip(&tlist, prev_edge, px, iy); + e1->outp[ABOVE]= prev_edge->outp[ABOVE]; + VERTEX(e1, ABOVE, RIGHT, ix, iy); + new_tristrip(&tlist, e0, ix, iy); + next_edge->outp[ABOVE]= e0->outp[ABOVE]; + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + break; + case ILI: + if (p) + { + VERTEX(e0, ABOVE, LEFT, ix, iy); + N_EDGE(next_edge, e1, ABOVE, nx, iy); + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + e1->outp[ABOVE]= e0->outp[ABOVE]; + e0->outp[ABOVE]= NULL; + } + break; + case IRI: + if (q) + { + VERTEX(e1, ABOVE, RIGHT, ix, iy); + P_EDGE(prev_edge, e0, ABOVE, px, iy); + VERTEX(prev_edge, ABOVE, LEFT, px, iy); + e0->outp[ABOVE]= e1->outp[ABOVE]; + e1->outp[ABOVE]= NULL; + } + break; + case IMX: + if (p && q) + { + VERTEX(e0, ABOVE, RIGHT, ix, iy); + VERTEX(e1, ABOVE, LEFT, ix, iy); + e0->outp[ABOVE]= NULL; + e1->outp[ABOVE]= NULL; + P_EDGE(prev_edge, e0, ABOVE, px, iy); + VERTEX(prev_edge, ABOVE, LEFT, px, iy); + new_tristrip(&tlist, prev_edge, px, iy); + N_EDGE(next_edge, e1, ABOVE, nx, iy); + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + next_edge->outp[ABOVE]= prev_edge->outp[ABOVE]; + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + } + break; + case IMM: + if (p && q) + { + VERTEX(e0, ABOVE, RIGHT, ix, iy); + VERTEX(e1, ABOVE, LEFT, ix, iy); + P_EDGE(prev_edge, e0, ABOVE, px, iy); + VERTEX(prev_edge, ABOVE, LEFT, px, iy); + new_tristrip(&tlist, prev_edge, px, iy); + N_EDGE(next_edge, e1, ABOVE, nx, iy); + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + e1->outp[ABOVE]= prev_edge->outp[ABOVE]; + VERTEX(e1, ABOVE, RIGHT, ix, iy); + new_tristrip(&tlist, e0, ix, iy); + next_edge->outp[ABOVE]= e0->outp[ABOVE]; + VERTEX(next_edge, ABOVE, RIGHT, nx, iy); + } + break; + case EMM: + if (p && q) + { + VERTEX(e0, ABOVE, LEFT, ix, iy); + new_tristrip(&tlist, e1, ix, iy); + e0->outp[ABOVE]= e1->outp[ABOVE]; + } + break; + default: + break; + } /* End of switch */ + } /* End of contributing intersection conditional */ + + /* Swap bundle sides in response to edge crossing */ + if (e0->bundle[ABOVE][CLIP]) + e1->bside[CLIP]= !e1->bside[CLIP]; + if (e1->bundle[ABOVE][CLIP]) + e0->bside[CLIP]= !e0->bside[CLIP]; + if (e0->bundle[ABOVE][SUBJ]) + e1->bside[SUBJ]= !e1->bside[SUBJ]; + if (e1->bundle[ABOVE][SUBJ]) + e0->bside[SUBJ]= !e0->bside[SUBJ]; + + /* Swap e0 and e1 bundles in the AET */ + prev_edge= e0->prev; + next_edge= e1->next; + if (e1->next) + e1->next->prev= e0; + + if (e0->bstate[ABOVE] == BUNDLE_HEAD) + { + search= TRUE; + while (search) + { + prev_edge= prev_edge->prev; + if (prev_edge) + { + if (prev_edge->bundle[ABOVE][CLIP] + || prev_edge->bundle[ABOVE][SUBJ] + || (prev_edge->bstate[ABOVE] == BUNDLE_HEAD)) + search= FALSE; + } + else + search= FALSE; + } + } + if (!prev_edge) + { + e1->next= aet; + aet= e0->next; + } + else + { + e1->next= prev_edge->next; + prev_edge->next= e0->next; + } + e0->next->prev= prev_edge; + e1->next->prev= e1; + e0->next= next_edge; + } /* End of IT loop*/ + + /* Prepare for next scanbeam */ + for (edge= aet; edge; edge= next_edge) + { + next_edge= edge->next; + succ_edge= edge->succ; + + if ((edge->top.y == yt) && succ_edge) + { + /* Replace AET edge by its successor */ + succ_edge->outp[BELOW]= edge->outp[ABOVE]; + succ_edge->bstate[BELOW]= edge->bstate[ABOVE]; + succ_edge->bundle[BELOW][CLIP]= edge->bundle[ABOVE][CLIP]; + succ_edge->bundle[BELOW][SUBJ]= edge->bundle[ABOVE][SUBJ]; + prev_edge= edge->prev; + if (prev_edge) + prev_edge->next= succ_edge; + else + aet= succ_edge; + if (next_edge) + next_edge->prev= succ_edge; + succ_edge->prev= prev_edge; + succ_edge->next= next_edge; + } + else + { + /* Update this edge */ + edge->outp[BELOW]= edge->outp[ABOVE]; + edge->bstate[BELOW]= edge->bstate[ABOVE]; + edge->bundle[BELOW][CLIP]= edge->bundle[ABOVE][CLIP]; + edge->bundle[BELOW][SUBJ]= edge->bundle[ABOVE][SUBJ]; + edge->xb= edge->xt; + } + edge->outp[ABOVE]= NULL; + } + } + } /* === END OF SCANBEAM PROCESSING ================================== */ + + /* Generate result tristrip from tlist */ + result->strip= NULL; + result->num_strips= count_tristrips(tlist); + if (result->num_strips > 0) + { + MALLOC(result->strip, result->num_strips * sizeof(gpc_vertex_list), + "tristrip list creation", gpc_vertex_list); + + s= 0; + for (tn= tlist; tn; tn= tnn) + { + tnn= tn->next; + + if (tn->active > 2) + { + /* Valid tristrip: copy the vertices and free the heap */ + result->strip[s].num_vertices= tn->active; + MALLOC(result->strip[s].vertex, tn->active * sizeof(gpc_vertex), + "tristrip creation", gpc_vertex); + v= 0; + if (INVERT_TRISTRIPS) + { + lt= tn->v[RIGHT]; + rt= tn->v[LEFT]; + } + else + { + lt= tn->v[LEFT]; + rt= tn->v[RIGHT]; + } + while (lt || rt) + { + if (lt) + { + ltn= lt->next; + result->strip[s].vertex[v].x= lt->x; + result->strip[s].vertex[v].y= lt->y; + v++; + FREE(lt); + lt= ltn; + } + if (rt) + { + rtn= rt->next; + result->strip[s].vertex[v].x= rt->x; + result->strip[s].vertex[v].y= rt->y; + v++; + FREE(rt); + rt= rtn; + } + } + s++; + } + else + { + /* Invalid tristrip: just free the heap */ + for (lt= tn->v[LEFT]; lt; lt= ltn) + { + ltn= lt->next; + FREE(lt); + } + for (rt= tn->v[RIGHT]; rt; rt=rtn) + { + rtn= rt->next; + FREE(rt); + } + } + FREE(tn); + } + } + + /* Tidy up */ + reset_it(&it); + reset_lmt(&lmt); + FREE(c_heap); + FREE(s_heap); + FREE(sbt); +} +#endif + +/* +=========================================================================== + End of file: gpc.c +=========================================================================== +*/ diff --git a/src/ShellExtension/gpc.h b/src/ShellExtension/gpc.h new file mode 100644 index 0000000..5802ee7 --- /dev/null +++ b/src/ShellExtension/gpc.h @@ -0,0 +1,177 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// For Paint.NET 3.xx, it is very convenient for us to place the GPC native +// code into the ShellExtension*.dll. This way we don't need to add another +// binary to installation, nor do we need to set up another project within +// Visual Studio. +// In Paint.NET 4.xx, there will be a PaintDotNet.Native.[x86|x64].dll, and +// that's where this will live. + +// NOTE: +// Although this Paint.NET distribution includes the GPC source code, use of +// the GPC code in any other commercial application is not permitted without +// a GPC Commercial Use Licence from The University of Manchester -- contact +// gpc@cs.man.ac.uk for details. +// Website for GPC: http://www.cs.man.ac.uk/~toby/alan/software/ + +/* +=========================================================================== + +Project: Generic Polygon Clipper + + A new algorithm for calculating the difference, intersection, + exclusive-or or union of arbitrary polygon sets. + +File: gpc.h +Author: Alan Murta (email: gpc@cs.man.ac.uk) +Version: 2.32 +Date: 17th December 2004 + +Copyright: (C) Advanced Interfaces Group, + University of Manchester. + + This software is free for non-commercial use. It may be copied, + modified, and redistributed provided that this copyright notice + is preserved on all copies. The intellectual property rights of + the algorithms used reside with the University of Manchester + Advanced Interfaces Group. + + You may not use this software, in whole or in part, in support + of any commercial product without the express consent of the + author. + + There is no warranty or other guarantee of fitness of this + software for any purpose. It is provided solely "as is". + +=========================================================================== +*/ + +#ifndef __gpc_h +#define __gpc_h + +#include + + +/* +=========================================================================== + Constants +=========================================================================== +*/ + +/* Increase GPC_EPSILON to encourage merging of near coincident edges */ + +#define GPC_EPSILON (DBL_EPSILON) + +#define GPC_VERSION "2.32" + + +/* +=========================================================================== + Public Data Types +=========================================================================== +*/ + +typedef enum /* Set operation type */ +{ + GPC_DIFF, /* Difference */ + GPC_INT, /* Intersection */ + GPC_XOR, /* Exclusive or */ + GPC_UNION /* Union */ +} gpc_op; + +typedef struct /* Polygon vertex structure */ +{ + double x; /* Vertex x component */ + double y; /* vertex y component */ +} gpc_vertex; + +typedef struct /* Vertex list structure */ +{ + int num_vertices; /* Number of vertices in list */ + gpc_vertex *vertex; /* Vertex array pointer */ +} gpc_vertex_list; + +typedef struct /* Polygon set structure */ +{ + int num_contours; /* Number of contours in polygon */ + int *hole; /* Hole / external contour flags */ + gpc_vertex_list *contour; /* Contour array pointer */ +} gpc_polygon; + +typedef struct /* Tristrip set structure */ +{ + int num_strips; /* Number of tristrips */ + gpc_vertex_list *strip; /* Tristrip array pointer */ +} gpc_tristrip; + + +/* +=========================================================================== + Public Function Prototypes +=========================================================================== +*/ + +// For Paint.NET, we do not need file read/write, nor any tristrip functionality. +// So, we remove them. + +/* +__declspec(dllexport) +void gpc_read_polygon (FILE *infile_ptr, + int read_hole_flags, + gpc_polygon *polygon); +*/ + +/* +__declspec(dllexport) +void gpc_write_polygon (FILE *outfile_ptr, + int write_hole_flags, + gpc_polygon *polygon); +*/ + +__declspec(dllexport) +void gpc_add_contour (gpc_polygon *polygon, + gpc_vertex_list *contour, + int hole); + +__declspec(dllexport) +void gpc_polygon_clip (gpc_op set_operation, + gpc_polygon *subject_polygon, + gpc_polygon *clip_polygon, + gpc_polygon *result_polygon); + +/* +__declspec(dllexport) +void gpc_tristrip_clip (gpc_op set_operation, + gpc_polygon *subject_polygon, + gpc_polygon *clip_polygon, + gpc_tristrip *result_tristrip); +*/ + +/* +__declspec(dllexport) +void gpc_polygon_to_tristrip (gpc_polygon *polygon, + gpc_tristrip *tristrip); +*/ + +__declspec(dllexport) +void gpc_free_polygon (gpc_polygon *polygon); + +/* +__declspec(dllexport) +void gpc_free_tristrip (gpc_tristrip *tristrip); +*/ + +#endif + +/* +=========================================================================== + End of file: gpc.h +=========================================================================== +*/ diff --git a/src/ShellExtension/resource.h b/src/ShellExtension/resource.h new file mode 100644 index 0000000..5828cb2 --- /dev/null +++ b/src/ShellExtension/resource.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ShellExtension.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/SplashForm.cs b/src/SplashForm.cs new file mode 100644 index 0000000..be78ce1 --- /dev/null +++ b/src/SplashForm.cs @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class SplashForm + : PdnBaseForm + { + private System.Windows.Forms.Label copyrightLabel; + private PdnBanner banner; + private ProgressBar progressBar; + + public SplashForm() + { + //SuspendLayout(); + + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + // Fill in the status label + banner.BannerText = PdnResources.GetString("SplashForm.StatusLabel.Text"); + + // Fill in the copyright label + copyrightLabel.Text = PdnInfo.GetCopyrightString(); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.banner = new PdnBanner(); + this.copyrightLabel = new System.Windows.Forms.Label(); + this.progressBar = new ProgressBar(); + this.SuspendLayout(); + // + // banner + // + this.banner.Name = "banner"; + this.banner.Location = new Point(0, 0); + this.banner.Dock = DockStyle.Top; + // + // copyrightLabel + // + this.copyrightLabel.BackColor = System.Drawing.Color.White; + this.copyrightLabel.Dock = DockStyle.Top; + this.copyrightLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); + this.copyrightLabel.Name = "copyrightLabel"; + this.copyrightLabel.Size = new System.Drawing.Size(this.banner.ClientSize.Width, 28); + this.copyrightLabel.TabIndex = 3; + this.copyrightLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // progressBar + // + this.progressBar.Minimum = 0; + this.progressBar.Maximum = 0; + this.progressBar.Value = 0; + this.progressBar.Style = ProgressBarStyle.Marquee; + this.progressBar.MarqueeAnimationSpeed = 30; + this.progressBar.Dock = DockStyle.Top; + this.progressBar.Size = new Size(this.banner.ClientSize.Width, 0); + // + // SplashForm + // + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size( + this.banner.ClientSize.Width, + this.banner.ClientSize.Height + this.copyrightLabel.ClientSize.Height + this.progressBar.ClientSize.Height); + this.ControlBox = false; + this.Controls.Add(this.copyrightLabel); + this.Controls.Add(this.progressBar); + this.Controls.Add(this.banner); + this.FormBorderStyle = FormBorderStyle.None; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SplashForm"; + this.ShowInTaskbar = false; + this.SizeGripStyle = SizeGripStyle.Hide; + this.StartPosition = FormStartPosition.CenterScreen; + this.ResumeLayout(false); + } + #endregion + + } +} diff --git a/src/SplashForm.resx b/src/SplashForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Startup.cs b/src/Startup.cs new file mode 100644 index 0000000..93d19f8 --- /dev/null +++ b/src/Startup.cs @@ -0,0 +1,897 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Security.Policy; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal sealed class Startup + { + private static Startup instance; + private static DateTime startupTime; + private string[] args; + private MainForm mainForm; + + private Startup(string[] args) + { + this.args = args; + } + + /// + /// Starts a new instance of Paint.NET with the give arguments. + /// + /// The name of the filename to open, or null to start with a blank canvas. + public static void StartNewInstance(IWin32Window parent, bool requireAdmin, string[] args) + { + StringBuilder allArgsSB = new StringBuilder(); + + foreach (string arg in args) + { + allArgsSB.Append(' '); + + if (arg.IndexOf(' ') != -1) + { + allArgsSB.Append('"'); + } + + allArgsSB.Append(arg); + + if (arg.IndexOf(' ') != -1) + { + allArgsSB.Append('"'); + } + } + + string allArgs; + + if (allArgsSB.Length > 0) + { + allArgs = allArgsSB.ToString(1, allArgsSB.Length - 1); + } + else + { + allArgs = null; + } + + Shell.Execute( + parent, + Application.ExecutablePath, + allArgs, + requireAdmin ? ExecutePrivilege.RequireAdmin : ExecutePrivilege.AsInvokerOrAsManifest, + ExecuteWaitType.ReturnImmediately); + } + + public static void StartNewInstance(IWin32Window parent, string fileName) + { + string arg; + + if (fileName != null && fileName.Length != 0) + { + arg = "\"" + fileName + "\""; + } + else + { + arg = ""; + } + + StartNewInstance(parent, false, new string[1] { arg }); + } + + private static bool CloseForm(Form form) + { + ArrayList openForms = new ArrayList(Application.OpenForms); + + if (openForms.IndexOf(form) == -1) + { + return false; + } + + form.Close(); + + ArrayList openForms2 = new ArrayList(Application.OpenForms); + + if (openForms2.IndexOf(form) == -1) + { + return true; + } + + return false; + } + + public static bool CloseApplication() + { + bool returnVal = true; + + List allFormsButMainForm = new List(); + + foreach (Form form in Application.OpenForms) + { + if (form.Modal && !object.ReferenceEquals(form, instance.mainForm)) + { + allFormsButMainForm.Add(form); + } + } + + if (allFormsButMainForm.Count > 0) + { + // Cannot close application if there are modal dialogs + return false; + } + + returnVal = CloseForm(instance.mainForm); + return returnVal; + } + + /// + /// Checks to make sure certain files are present, and tries to repair the problem. + /// + /// + /// true if any repairs had to be made, at which point PDN must be restarted. + /// false otherwise, if everything's okay. + /// + private bool CheckForImportantFiles() + { + string[] requiredFiles = + new string[] + { + "FileTypes\\DdsFileType.dll", + "ICSharpCode.SharpZipLib.dll", + "Interop.WIA.dll", + "PaintDotNet.Base.dll", + "PaintDotNet.Core.dll", + "PaintDotNet.Data.dll", + "PaintDotNet.Effects.dll", + "PaintDotNet.Resources.dll", + "PaintDotNet.Strings.3.DE.resources", + "PaintDotNet.Strings.3.ES.resources", + "PaintDotNet.Strings.3.FR.resources", + "PaintDotNet.Strings.3.IT.resources", + "PaintDotNet.Strings.3.JA.resources", + "PaintDotNet.Strings.3.KO.resources", + "PaintDotNet.Strings.3.PT-BR.resources", + "PaintDotNet.Strings.3.resources", + "PaintDotNet.Strings.3.ZH-CN.resources", + "PaintDotNet.StylusReader.dll", + "PaintDotNet.SystemLayer.dll", + "SetupNgen.exe", + "ShellExtension_x64.dll", + "ShellExtension_x86.dll", + "Squish_x64.dll", + "Squish_x86.dll", + "Squish_x86_SSE2.dll", + "UpdateMonitor.exe", + "WiaProxy32.exe" + }; + + string dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + List missingFiles = null; + + foreach (string requiredFile in requiredFiles) + { + bool missing; + + try + { + string pathName = Path.Combine(dirName, requiredFile); + FileInfo fileInfo = new FileInfo(pathName); + missing = !fileInfo.Exists; + } + + catch (Exception) + { + missing = true; + } + + if (missing) + { + if (missingFiles == null) + { + missingFiles = new List(); + } + + missingFiles.Add(requiredFile); + } + } + + if (missingFiles == null) + { + return false; + } + else + { + if (Shell.ReplaceMissingFiles(missingFiles.ToArray())) + { + // Everything is repaired and happy. + return true; + } + else + { + // Things didn't get fixed. Bail. + Process.GetCurrentProcess().Kill(); + return false; + } + } + } + + public void Start() + { + // Set up unhandled exception handlers +#if DEBUG + // In debug builds we'd prefer to have it dump us into the debugger +#else + AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); + Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); +#endif + + // Initialize some misc. Windows Forms settings + Application.SetCompatibleTextRenderingDefault(false); + Application.EnableVisualStyles(); + + // If any files are missing, try to repair. + // However, support /skipRepairAttempt for when developing in the IDE + // so that we don't needlessly try to repair in that case. + if (this.args.Length > 0 && + string.Compare(this.args[0], "/skipRepairAttempt", StringComparison.InvariantCultureIgnoreCase) == 0) + { + // do nothing: we need this so that we can run from IDE/debugger + // without it trying to repair itself all the time + } + else + { + if (CheckForImportantFiles()) + { + Startup.StartNewInstance(null, false, args); + return; + } + } + + // The rest of the code is put in a separate method so that certain DLL's + // won't get delay loaded until after we try to do repairs. + StartPart2(); + } + + private void StartPart2() + { + // Set up locale / resource details + string locale = Settings.CurrentUser.GetString(SettingNames.LanguageName, null); + + if (locale == null) + { + locale = Settings.SystemWide.GetString(SettingNames.LanguageName, null); + } + + if (locale != null) + { + try + { + CultureInfo ci = new CultureInfo(locale, true); + Thread.CurrentThread.CurrentUICulture = ci; + } + + catch (Exception) + { + // Don't want bad culture name to crash us + } + } + + // Check system requirements + if (!OS.CheckOSRequirement()) + { + string message = PdnResources.GetString("Error.OSRequirement"); + Utility.ErrorBox(null, message); + return; + } + + // Parse command-line arguments + if (this.args.Length == 1 && + this.args[0] == Updates.UpdatesOptionsDialog.CommandLineParameter) + { + Updates.UpdatesOptionsDialog.ShowUpdateOptionsDialog(null, false); + } + else + { + SingleInstanceManager singleInstanceManager = new SingleInstanceManager(InvariantStrings.SingleInstanceMonikerName); + + // If this is not the first instance of PDN.exe, then forward the command-line + // parameters over to the first instance. + if (!singleInstanceManager.IsFirstInstance) + { + singleInstanceManager.FocusFirstInstance(); + + foreach (string arg in this.args) + { + singleInstanceManager.SendInstanceMessage(arg, 30); + } + + singleInstanceManager.Dispose(); + singleInstanceManager = null; + + return; + } + + // Create main window + this.mainForm = new MainForm(this.args); + + this.mainForm.SingleInstanceManager = singleInstanceManager; + singleInstanceManager = null; // mainForm owns it now + + // 3 2 1 go + Application.Run(this.mainForm); + + try + { + this.mainForm.Dispose(); + } + + catch (Exception) + { + } + + this.mainForm = null; + } + } + + /// + /// The main entry point for the application. + /// + [STAThread] + public static int Main(string[] args) + { + startupTime = DateTime.Now; + +#if !DEBUG + try + { +#endif + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + + instance = new Startup(args); + instance.Start(); +#if !DEBUG + } + + catch (Exception ex) + { + try + { + UnhandledException(ex); + Process.GetCurrentProcess().Kill(); + } + + catch (Exception) + { + MessageBox.Show(ex.ToString()); + Process.GetCurrentProcess().Kill(); + } + } +#endif + + return 0; + } + + private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + // For v3.05, we renamed PdnLib.dll to PaintDotNet.Core.dll. So we should really make + // sure we stay compatible with old plugin DLL's. + const string oldCoreName = "PdnLib"; + + int index = args.Name.IndexOf(oldCoreName, StringComparison.InvariantCultureIgnoreCase); + Assembly newAssembly = null; + + if (index == 0) + { + newAssembly = typeof(ColorBgra).Assembly; + } + + return newAssembly; + } + + private static void UnhandledException(Exception ex) + { + string dir = Shell.GetVirtualPath(VirtualFolderName.UserDesktop, true); + const string fileName = "pdncrash.log"; + string fullName = Path.Combine(dir, fileName); + + using (StreamWriter stream = new System.IO.StreamWriter(fullName, true)) + { + stream.AutoFlush = true; + WriteCrashLog(ex, stream); + } + + string errorFormat; + string errorText; + + try + { + errorFormat = PdnResources.GetString("Startup.UnhandledError.Format"); + } + + catch (Exception) + { + errorFormat = InvariantStrings.StartupUnhandledErrorFormatFallback; + } + + errorText = string.Format(errorFormat, fileName); + Utility.ErrorBox(null, errorText); + } + + public static string GetCrashLogHeader() + { + StringBuilder headerSB = new StringBuilder(); + StringWriter headerSW = new StringWriter(headerSB); + WriteCrashLog(null, headerSW); + return headerSB.ToString(); + } + + private static void WriteCrashLog(Exception ex, TextWriter stream) + { + string headerFormat; + + try + { + headerFormat = PdnResources.GetString("CrashLog.HeaderText.Format"); + } + + catch (Exception ex13) + { + headerFormat = + InvariantStrings.CrashLogHeaderTextFormatFallback + + ", --- Exception while calling PdnResources.GetString(\"CrashLog.HeaderText.Format\"): " + + ex13.ToString() + + Environment.NewLine; + } + + string header; + + try + { + header = string.Format(headerFormat, InvariantStrings.CrashlogEmail); + } + + catch + { + header = string.Empty; + } + + stream.WriteLine(header); + + const string noInfoString = "err"; + + string fullAppName = noInfoString; + string timeOfCrash = noInfoString; + string appUptime = noInfoString; + string osVersion = noInfoString; + string osRevision = noInfoString; + string osType = noInfoString; + string processorNativeArchitecture = noInfoString; + string clrVersion = noInfoString; + string fxInventory = noInfoString; + string processorArchitecture = noInfoString; + string cpuName = noInfoString; + string cpuCount = noInfoString; + string cpuSpeed = noInfoString; + string cpuFeatures = noInfoString; + string totalPhysicalBytes = noInfoString; + string dpiInfo = noInfoString; + string localeName = noInfoString; + string inkInfo = noInfoString; + string updaterInfo = noInfoString; + string featuresInfo = noInfoString; + string assembliesInfo = noInfoString; + + try + { + try + { + fullAppName = PdnInfo.GetFullAppName(); + } + + catch (Exception ex1) + { + fullAppName = Application.ProductVersion + ", --- Exception while calling PdnInfo.GetFullAppName(): " + ex1.ToString() + Environment.NewLine; + } + + try + { + timeOfCrash = DateTime.Now.ToString(); + } + + catch (Exception ex2) + { + timeOfCrash = "--- Exception while populating timeOfCrash: " + ex2.ToString() + Environment.NewLine; + } + + try + { + appUptime = (DateTime.Now - startupTime).ToString(); + } + + catch (Exception ex13) + { + appUptime = "--- Exception while populating appUptime: " + ex13.ToString() + Environment.NewLine; + } + + try + { + osVersion = System.Environment.OSVersion.Version.ToString(); + } + + catch (Exception ex3) + { + osVersion = "--- Exception while populating osVersion: " + ex3.ToString() + Environment.NewLine; + } + + try + { + osRevision = OS.Revision; + } + + catch (Exception ex4) + { + osRevision = "--- Exception while populating osRevision: " + ex4.ToString() + Environment.NewLine; + } + + try + { + osType = OS.Type.ToString(); + } + + catch (Exception ex5) + { + osType = "--- Exception while populating osType: " + ex5.ToString() + Environment.NewLine; + } + + try + { + processorNativeArchitecture = Processor.NativeArchitecture.ToString().ToLower(); + } + + catch (Exception ex6) + { + processorNativeArchitecture = "--- Exception while populating processorNativeArchitecture: " + ex6.ToString() + Environment.NewLine; + } + + try + { + clrVersion = System.Environment.Version.ToString(); + } + + catch (Exception ex7) + { + clrVersion = "--- Exception while populating clrVersion: " + ex7.ToString() + Environment.NewLine; + } + + try + { + fxInventory = + (SystemLayer.OS.IsDotNetVersionInstalled(2, 0, 0, false) ? "2.0 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(2, 0, 1, false) ? "2.0SP1 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(2, 0, 2, false) ? "2.0SP2 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 0, 0, false) ? "3.0 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 0, 1, false) ? "3.0SP1 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 0, 2, false) ? "3.0SP2 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 5, 0, false) ? "3.5 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 5, 1, false) ? "3.5SP1 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 5, 1, true) ? "3.5SP1_Client " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(3, 5, 2, false) ? "3.5SP2 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(4, 0, 0, false) ? "4.0 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(4, 0, 1, false) ? "4.0SP1 " : "") + + (SystemLayer.OS.IsDotNetVersionInstalled(4, 0, 2, false) ? "4.0SP2 " : "") + .Trim(); + } + + catch (Exception ex30) + { + fxInventory = "--- Exception while populating fxInventory: " + ex30.ToString() + Environment.NewLine; + } + + try + { + processorArchitecture = Processor.Architecture.ToString().ToLower(); + } + + catch (Exception ex8) + { + processorArchitecture = "--- Exception while populating processorArchitecture: " + ex8.ToString() + Environment.NewLine; + } + + try + { + cpuName = SystemLayer.Processor.CpuName; + } + + catch (Exception ex9) + { + cpuName = "--- Exception while populating cpuName: " + ex9.ToString() + Environment.NewLine; + } + + try + { + cpuCount = SystemLayer.Processor.LogicalCpuCount.ToString() + "x"; + } + + catch (Exception ex10) + { + cpuCount = "--- Exception while populating cpuCount: " + ex10.ToString() + Environment.NewLine; + } + + try + { + cpuSpeed = "@ ~" + SystemLayer.Processor.ApproximateSpeedMhz.ToString() + "MHz"; + } + + catch (Exception ex16) + { + cpuSpeed = "--- Exception while populating cpuSpeed: " + ex16.ToString() + Environment.NewLine; + } + + try + { + cpuFeatures = string.Empty; + string[] featureNames = Enum.GetNames(typeof(ProcessorFeature)); + bool firstFeature = true; + + for (int i = 0; i < featureNames.Length; ++i) + { + string featureName = featureNames[i]; + ProcessorFeature feature = (ProcessorFeature)Enum.Parse(typeof(ProcessorFeature), featureName); + + if (Processor.IsFeaturePresent(feature)) + { + if (firstFeature) + { + cpuFeatures = "("; + firstFeature = false; + } + else + { + cpuFeatures += ", "; + } + + cpuFeatures += featureName; + } + } + + if (cpuFeatures.Length > 0) + { + cpuFeatures += ")"; + } + } + + catch (Exception ex17) + { + cpuFeatures = "--- Exception while populating cpuFeatures: " + ex17.ToString() + Environment.NewLine; + } + + try + { + totalPhysicalBytes = ((SystemLayer.Memory.TotalPhysicalBytes / 1024) / 1024) + " MB"; + } + + catch (Exception ex11) + { + totalPhysicalBytes = "--- Exception while populating totalPhysicalBytes: " + ex11.ToString() + Environment.NewLine; + } + + try + { + float xScale; + + try + { + xScale = UI.GetXScaleFactor(); + } + + catch (Exception) + { + using (Control c = new Control()) + { + UI.InitScaling(c); + xScale = UI.GetXScaleFactor(); + } + } + + dpiInfo = string.Format("{0} dpi ({1}x scale)", (96.0f * xScale).ToString("F2"), xScale.ToString("F2")); + } + + catch (Exception ex19) + { + dpiInfo = "--- Exception while populating dpiInfo: " + ex19.ToString() + Environment.NewLine; + } + + try + { + localeName = + "pdnr.c: " + PdnResources.Culture.Name + + ", hklm: " + Settings.SystemWide.GetString(SettingNames.LanguageName, "n/a") + + ", hkcu: " + Settings.CurrentUser.GetString(SettingNames.LanguageName, "n/a") + + ", cc: " + CultureInfo.CurrentCulture.Name + + ", cuic: " + CultureInfo.CurrentUICulture.Name; + } + + catch (Exception ex14) + { + localeName = "--- Exception while populating localeName: " + ex14.ToString() + Environment.NewLine; + } + + try + { + inkInfo = Ink.IsAvailable() ? "yes" : "no"; + } + + catch (Exception ex15) + { + inkInfo = "--- Exception while populating inkInfo: " + ex15.ToString() + Environment.NewLine; + } + + try + { + string autoCheckForUpdates = Settings.SystemWide.GetString(SettingNames.AutoCheckForUpdates, noInfoString); + + string lastUpdateCheckTimeInfo; + + try + { + string lastUpdateCheckTimeString = Settings.CurrentUser.Get(SettingNames.LastUpdateCheckTimeTicks); + long lastUpdateCheckTimeTicks = long.Parse(lastUpdateCheckTimeString); + DateTime lastUpdateCheckTime = new DateTime(lastUpdateCheckTimeTicks); + lastUpdateCheckTimeInfo = lastUpdateCheckTime.ToShortDateString(); + } + + catch (Exception) + { + lastUpdateCheckTimeInfo = noInfoString; + } + + updaterInfo = string.Format( + "{0}, {1}", + (autoCheckForUpdates == "1") ? "true" : (autoCheckForUpdates == "0" ? "false" : (autoCheckForUpdates ?? "null")), + lastUpdateCheckTimeInfo); + } + + catch (Exception ex17) + { + updaterInfo = "--- Exception while populating updaterInfo: " + ex17.ToString() + Environment.NewLine; + } + + try + { + StringBuilder featureSB = new StringBuilder(); + + IEnumerable featureList = SystemLayer.Tracing.GetLoggedFeatures(); + + bool first = true; + foreach (string feature in featureList) + { + if (!first) + { + featureSB.Append(", "); + } + + featureSB.Append(feature); + + first = false; + } + + featuresInfo = featureSB.ToString(); + } + + catch (Exception ex18) + { + featuresInfo = "--- Exception while populating featuresInfo: " + ex18.ToString() + Environment.NewLine; + } + + try + { + StringBuilder assembliesInfoSB = new StringBuilder(); + + Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (Assembly assembly in loadedAssemblies) + { + assembliesInfoSB.AppendFormat("{0} {1} @ {2}", Environment.NewLine, assembly.FullName, assembly.Location); + } + + assembliesInfo = assembliesInfoSB.ToString(); + } + + catch (Exception ex16) + { + assembliesInfo = "--- Exception while populating assembliesInfo: " + ex16.ToString() + Environment.NewLine; + } + } + + catch (Exception ex12) + { + stream.WriteLine("Exception while gathering app and system info: " + ex12.ToString()); + } + + stream.WriteLine("Application version: " + fullAppName); + stream.WriteLine("Time of crash: " + timeOfCrash); + stream.WriteLine("Application uptime: " + appUptime); + + stream.WriteLine("OS Version: " + osVersion + (string.IsNullOrEmpty(osRevision) ? "" : (" " + osRevision)) + " " + osType + " " + processorNativeArchitecture); + stream.WriteLine(".NET version: CLR " + clrVersion + " " + processorArchitecture + ", FX " + fxInventory); + stream.WriteLine("Processor: " + cpuCount + " \"" + cpuName + "\" " + cpuSpeed + " " + cpuFeatures); + stream.WriteLine("Physical memory: " + totalPhysicalBytes); + stream.WriteLine("UI DPI: " + dpiInfo); + stream.WriteLine("Tablet PC: " + inkInfo); + stream.WriteLine("Updates: " + updaterInfo); + stream.WriteLine("Locale: " + localeName); + stream.WriteLine("Features log: " + featuresInfo); + stream.WriteLine("Loaded assemblies: " + assembliesInfo); + stream.WriteLine(); + + stream.WriteLine("Exception details:"); + + if (ex == null) + { + stream.WriteLine("(null)"); + } + else + { + stream.WriteLine(ex.ToString()); + + // Determine if there is any 'secondary' exception to report + Exception[] otherEx = null; + + if (ex is System.Reflection.ReflectionTypeLoadException) + { + otherEx = ((System.Reflection.ReflectionTypeLoadException)ex).LoaderExceptions; + } + + if (otherEx != null) + { + for (int i = 0; i < otherEx.Length; ++i) + { + stream.WriteLine(); + stream.WriteLine("Secondary exception details:"); + + if (otherEx[i] == null) + { + stream.WriteLine("(null)"); + } + else + { + stream.WriteLine(otherEx[i].ToString()); + } + } + } + } + + stream.WriteLine("------------------------------------------------------------------------------"); + stream.Flush(); + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + UnhandledException((Exception)e.ExceptionObject); + Process.GetCurrentProcess().Kill(); + } + + private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) + { + UnhandledException(e.Exception); + Process.GetCurrentProcess().Kill(); + } + } +} diff --git a/src/Strings/Strings.resx b/src/Strings/Strings.resx new file mode 100644 index 0000000..44c1e59 --- /dev/null +++ b/src/Strings/Strings.resx @@ -0,0 +1,3316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Paint.NET + + + {0} v{1}{2} + {0} is Application.ProductName.Bare. {1} is the version number (e.g., "2.6"). {2} is either an empty string, or a version tag constructed with Application.ProductNage.Format (e.g., "Beta 5") + + + {0} + {0} is the version tag, e.g. Beta 5 + + + All Rights Reserved. + + + Layers + + + Add New Layer + + + Delete Layer + + + Merge Layer Down + + + Duplicate Layer + + + Move Layer Up + + + Move Layer Down + + + Properties + + + History + + + Undo All (Rewind) + + + Undo (Step Backward) + + + Redo All (Fast Forward) + + + Redo (Step Forward) + + + Type a name for the palette: + + + Palettes that have already been saved: + + + Save Current Palette + + + Colors + + + Add color to palette + + + Delete color from palette + + + ; Paint.NET Palette File +; Lines that start with a semicolon are comments +; Colors are written as 8-digit hexadecimal numbers: aarrggbb +; For example, this would specify green: FF00FF00 +; The alpha ('aa') value specifies how transparent a color is. FF is fully opaque, 00 is fully transparent. +; A palette must consist of ninety six (96) colors. If there are less than this, the remaining color +; slots will be set to white (FFFFFFFF). If there are more, then the remaining colors will be ignored. + Yes, this should be translated. This is written to the beginning of palette files, which are stored in text files (*.txt). They are intended to be understandable by the user. + + + Paint.NET User Files + This is a directory that will be created in the My Documents folder. + + + Palettes + This is a directory that will be created in the directory that SystemLayer.UserDataDirName refers to. + + + Manage color palettes + + + Save Current Palette &As... + + + &Open Palettes Folder... + + + &Reset to Default Palette + + + R: + + + B: + + + G: + + + Hex: + + + H: + + + S: + + + V: + + + RGB + + + HSV + + + Transparency - Alpha + + + Less + + + More + + + Primary + + + Secondary + + + Tools + + + About {0} + + + OK + + + Cancel + + + &Save + + + &Reset + + + Close + + + Credits: + + + Solid Color + + + Horizontal + + + Min + + + Vertical + + + Forward Diagonal + + + Backward Diagonal + + + Cross + + + Large Grid + + + Max + + + Diagonal Cross + + + Percent 05 + + + Percent 10 + + + Percent 20 + + + Percent 25 + + + Percent 30 + + + Percent 40 + + + Percent 50 + + + Percent 60 + + + Percent 70 + + + Percent 75 + + + Percent 80 + + + Percent 90 + + + Light Downward Diagonal + + + Light Upward Diagonal + + + Dark Downward Diagonal + + + Dark Upward Diagonal + + + Wide Downward Diagonal + + + Wide Upward Diagonal + + + Light Vertical + + + Light Horizontal + + + Narrow Vertical + + + Narrow Horizontal + + + Dark Vertical + + + Dark Horizontal + + + Dashed Downward Diagonal + + + Dashed Upward Diagonal + + + Dashed Horizontal + + + Dashed Vertical + + + Small Confetti + + + Large Confetti + + + Zig Zag + + + Wave + + + Diagonal Brick + + + Horizontal Brick + + + Weave + + + Plaid + + + Divot + + + Dotted Grid + + + Dotted Diamond + + + Shingle + + + Trellis + + + Sphere + + + Small Grid + + + Small Checker Board + + + Large Checker Board + + + Outlined Diamond + + + Solid Diamond + + + Fill: + + + Canvas Size + + + Not enough memory to resize the canvas. + + + Top Left + + + Top + + + Top Right + + + Left + + + Middle + + + Right + + + Bottom Left + + + Bottom + + + Bottom Right + + + Canvas Size + + + Anchor + + + The new space will be filled with the secondary color. + + + Clone Stamp + + + Hold Ctrl and left click to select an origin. Afterwards, left click and draw to copy + + + Swap colors +Shortcut key: X + + + Black and White + + + Primary + + + Secondary + + + Color Picker + + + Left click to set primary color, right click to set secondary color + + + New + + + Open + + + Save + + + Print + + + Cut + + + Copy + + + Paste + + + Crop to Selection + + + Deselect + + + Undo + + + Redo + + + Zoom In + + + Zoom Out + + + Crop to Selection + + + Deselect + + + Please wait... + + + Layer {0} + {0} represents the layer index + + + There was an error loading {0}. It may be corrupt, or it may need to be recompiled. + {0} is a file name + + + There was not enough memory to complete the operation. + + + New Layer + + + Layer {0} + {0} is the number of the new layer + + + There must be at least one layer in an image. + + + Delete layer? + + + Delete Layer + + + Duplicate Layer + + + Merge Layer Down + + + Move Layer Up + + + Move Layer Down + + + Clear history? + + + Clear History + + + Ellipse Select + + + Click and drag to draw an elliptical selection. Hold shift to constrain to a circle. + + + Ellipse + + + Click and drag to draw an ellipse (right click for secondary color). Hold shift to constrain to a circle. + + + Size: {0}{1} x {2}{3}, Area: {4} {5} + + + Eraser + + + Click and drag to erase a portion of the image + + + Erase Selection + + + Fill Selection + + + Flatten + + + Flip Horizontal (all) + + + Flip Vertical (all) + + + Flip Horizontal + + + Flip Vertical + + + Freeform Shape + + + Left click to draw a freeform shape with the primary color, right click to use the secondary color + + + Import From File + + + Ran out of memory while trying to resize the image. + + + Ran out of memory while trying to resize the layer. + + + {0}: {1} + {0} is the file being imported, {1} is the name of a layer within that file + + + Invert Selection + + + Lasso Select + + + Click and drag to draw the outline for a selection area. + + + Gradient + + + Click and drag to start drawing. Holding shift constrains the angle. Right mouse button reverses colors. + + + Offset: {0}{1} x {2}{3}, Length: {4} {5}, Angle: {6}°. Holding other mouse button will move both nubs. + + + Drag a nub to adjust the gradient. Right click nub to swap colors. Press Enter to finish, or Esc to undo. + + + Line / Curve + + + Left click to draw with primary color, right click to use secondary color + + + Offset: {0}{1} x {2}{3}, Length: {4} {5}, Angle: {6}° + + + Drag nubs to curve line (right button for Bezier), or press Enter to finish line, or draw elsewhere for a new line. + + + Press Enter to finish the curve, or draw elsewhere for a new line. Tap Ctrl to toggle nubs. + + + Magic Wand + + + Click to select an area of similar color. + + + Move Selected Pixels + + + Drag the selection to move. Drag the nubs to scale. Drag with right mouse button to rotate. + + + Move Pixels + + + Scale Pixels + + + Rotate Pixels + + + Finish Pixels + + + Move Selection + + + Drag the selection to move. Drag the nubs to scale. Drag with right mouse button to rotate. + + + Move Selection + + + Scale Selection + + + Rotate Selection + + + Finish Selection + + + New + + + Paintbrush + + + Left click to draw with primary color, right click to draw with secondary color + + + Paint Bucket + + + Left click to fill a region with the primary color, right click to fill with the secondary color + + + Pan + + + When zoomed in close, click and drag to navigate the image + + + pdnabout.html + + + {0} ({1}) + {0} is the product name ("Paint.NET"), {1} is the version string ("2.2.2000.3000") + + + {0} {1} build {2} + {0} is the friendly version name (configuration name), such as "Beta 2" or "Final". {1} is Debug or Release. {2} is the product version. + + + x86 + + + x64 + + + {0} + + + version {0}{1} + {0} is the version, such as "2.5". {1} is the configuration ("Beta 5") formatted via PdnInfo.FriendlyVersionString.ConfigWithSpace.Format, or it is an empty string if the configuration is "Final" + + + Pencil + + + Left click to draw freeform, one-pixel wide lines with the primary color, right click to use the secondary color + + + Brush width: + + + Invalid number + + + Size is smaller than 1 + + + Size is smaller than {0} + + + Size is larger than 500 + + + Size is larger than {0} + + + {0}% + {0} is a number from 0 to 100 + + + Recolor + + + Left click to replace the secondary color with the primary color. + + + Move Selection + + + Rotate Selection + + + Scale Selection + + + Rectangle Select + + + Click and drag to draw a rectangular selection. Hold shift to constrain to a square. + + + Rectangle + + + Click and drag to draw a rectangle (right click for secondary color). Hold shift to constrain to a square. + + + Size: {0}{1} x {2}{3}, area: {4} {5} + + + Resize + + + There was an unspecified error while trying to resize the image. + + + Not enough memory to resize the image. + + + New size: {0} + {0} is a byte size, such as 3.8mb + + + Nearest Neighbor + + + Bicubic + + + Best Quality + + + Bilinear + + + Resize + + + * + + + % + + + pixels + + + pixels + + + &By percentage: + + + &By absolute size: + + + Pixel size + + + Print size + + + Current image size: + + + Resampling: + + + Width: + + + Height: + + + Width: + + + Height: + + + &Maintain aspect ratio + + + * Super Sampling will be used + + + * Bicubic will be used + + + 180° + + + 90° CW + + + 90° CCW + + + {0} {1} + {0} is the action name ("Rotate"), {1} is the angle (90 CW) + + + Rotate + + + Rounded Rectangle + + + Click and drag to draw a rounded rectangle (right click for secondary color). Hold shift to constrain. + + + Bounding rectangle size: {0}{1} x {2}{3}, area: {4} {5} + + + Save Configuration + + + Settings + + + De&faults + + + Preview, file size: {0} + {0} is a file size, or some text in parenthesis (such as SaveConfigDialog.FileSizeText.Text.Error) + + + (computing) + + + (error) + + + (computing: {0}%) + {0} is a number between 0 and 100 + + + Saving + + + Saving: + + + Select All + + + Draw Shape Outline + + + Draw Filled Shape + + + Draw Filled Shape With Outline + + + Starting... + + + There was an unhandled error, and Paint.NET must be closed. Refer to the file '{0}', which has been placed on your desktop, for more information. + {0} is a filename + + + Quality: + + + Smooth + + + Pixelated + + + Bicubic + + + Super Sampling + + + After click: + + + Do not switch tool + + + Switch to previous tool + + + Switch to Pencil tool + + + Invalid number + + + Size is smaller than {0} + {0} is an integer + + + Size is larger than {0} + {0} is an integer + + + Font: + + + Bold + + + Italics + + + Underline + + + Align Left + + + Center Align + + + Align Right + + + Text + + + Left click to place the text cursor, and then type to enter text. Text is drawn with the primary color. + + + Drag the nub to move the text. Hold Ctrl to hide nub and cursor. Tap Ctrl to toggle nub. + + + Anchor point: {0}{1}, {2}{3} + + + Tolerance + + + {0}% + {0} is an integer + + + Display grid when zoomed in + + + Rulers + + + Zoom + + + Left click to zoom in, right click to zoom out, middle click to slide + + + Zoom in + + + Zoom out + + + Window + + + Selection + + + Factor + + + Invalid number + + + Zoom has to be at least 1% + + + Zoom cannot exceed 3200% + + + {0}% + {0} is an integer + + + {0}: {1} + {0} is the tool's name, {1} is its help text + + + Acquire Image + + + There is no usable image in the clipboard. + + + There was an error transferring the image from the clipboard. + + + There is not enough memory to copy the image from the clipboard. + + + {0}{1} x {2}{3} + {0} is width, {1} is units (e.g. cm or px), {2} is height, {3} is units + + + Untitled + + + {0} ({1}) - {2} + {0} is friendly document name, {1} is zoom level, {2} is application name + + + {0} - {1} + {0} is friendly document name, {1} is application name + + + Not enough memory to create new image. + + + New Image + + + The image type is not recognized, and cannot be opened. + + + The file name is invalid. + +"{0}" + {0} is a filename. + + + The filename is blank. + + + There was an error opening the file. + + + Access was denied to the requested file (unauthorized access). + + + Access was denied to the requested file (security exception). + + + The file could not be found. + + + The directory could not be found. + + + The file name is too long. + + + There was an error reading the file from the media. + + + The file is corrupt or was saved with a newer version of Paint.NET. + + + Not enough memory to load the image. + + + There was an unspecified error while opening the file. + + + Open Image + + + This image was saved with an older version of Paint.NET. If you save it, it will not be readable by that older version. + +Saved with: Paint.NET v{0} +Current version: Paint.NET v{1} + {0} and {1} are version numbers + + + The file name is too long. Please try a shorter name. + + + All image types + + + Save As + + + Access is denied. Use File->Save As... to save under a different location or name. + + + Access is denied. Use File->Save As... to save under a different location or name. + + + The directory could not be found. Use File->Save As... to save to another location. + + + An I/O error occurred when writing to the file. + + + Not enough memory to save the image. + + + There was an unspecified error while saving the file. + + + Save + + + Saving in this file format requires that the image is first "flattened", which reduces it to a single layer. + + + &Flatten + + + The image will be flattened, and then saved. You will be able to undo the flatten operation after saving is finished. + + + Cancel + + + Cancels the save action. + + + There was an error copying the image to the clipboard. + + + There was not enough memory to complete the clipboard operation. + + + There was an error copying to the clipboard. + + + Cut + + + Rendering... + + + Repeat {0} + {0} is the effect name + + + Plugin Load Errors + + + The following plugins failed to load. They may have been written for an older or newer version of Paint.NET. If you already have the most up-to-date version of Paint.NET, then you should check to see if there is an updated version of any plugin causing an error. + + + (Not Supplied) + + + {0} of {1} +-------------- + {0} and {1} are numbers, and {1} is always greater than or equal to {0}. For example: "3 of 5" to indicate the 3rd out of 5 errors. + + + File: {0} + Effect Name: {1} + Full error message: {2} + + + + File: {0} + Name: {1} + Version: {2} + Author: {3} + Copyright: {4} + Website: {5} + Full error message: {6} + + {0} is a full path name, such as "C:\Program Files\Paint.NET\Effects\EdHarvey.dll" (localization not necessary). {1} is a display name such as "Surface Blur" (localization is handled by plugin). {2} is the version of the plugin or DLL, such as "1.2.345.6789" (localization handled by the .NET Framework's Version.ToString() method). {3} is a name or company name (localization handled by plugin), {4} is copyright information (localization handled by plugin), {5} is a valid website URL starting with http:// or https:// (localization handled by plugin). {6} is an exception string and is provided and localized by the .NET Framework (it is meant for the plugin author, and not necessarily important for the user to understand). + + + {0}... + {0} is the effect name + + + There was an error getting the image from the clipboard. + + + Not enough memory to paste from the clipboard. + + + The image in the clipboard couldn't be recognized. Try re-copying it with the original application that was used to acquire it. + + + The clipboard doesn't contain an image. + + + The image being pasted is larger than the image canvas. +Expand canvas to fit pasted image? + + + Not enough memory to create a new layer. + + + Discard hidden layers? + + + {0}% + {0} is an integer + + + Selection top left: {0}{1}, {2}{3}. Bounding rectangle size: {4}{5} x {6}{7}. Area: {8} {9} square + {0} is an X coordinate, {1} is units abbreviation, {2} is Y coordinate, {3} is units abbreviation, {4} is width, {5} is units abbreviation, {6} is height, {7} is units abbreviation, {8} is the area, {9} is plural units (pixels, inches) + + + Selection top left: {0}{1}, {2}{3}. Bounding rectangle size: {4}{5} x {6}{7}. Area: {8} {9} square. Angle: {10}° + {0} is an X coordinate, {1} is units abbreviation, {2} is Y coordinate, {3} is units abbreviation, {4} is width, {5} is units abbreviation, {6} is height, {7} is units abbreviation, {8} is the area, {9} is plural units (pixels, inches), {10} is angle of rotation + + + To use this feature you must enable the Windows Image Acquisition system service. + + + The Windows Image Acquisition (WIA) library, "wiaaut.dll" is unavailable. Please read the Help file (Help menu -> Help Topics), in the Frequently Asked Questions topic, for troubleshooting information. + + + Drag and Drop + + + What would you like to do with the file(s)? + + + &Open + + + Open the file(s) into tab(s). + + + &Add into layer(s) + + + Load and then add into new layer(s) in the current image. + + + Import into new layer(s) in a new image. + + + (none) + + + &Clear this list + + + &View Plugin Load Errors... + + + &File + + + &New... + + + &Open... + + + Open &Recent + + + &Close + + + Ac&quire + + + From &Scanner or Camera... + + + &Save + + + Save &As... + + + &Print... + + + E&xit + + + &Edit + + + &Undo + + + &Redo + + + Cu&t + + + &Copy + + + &Paste + + + Paste in to New &Layer + + + Paste in to New I&mage + + + &Erase Selection + + + &Fill Selection + + + Backspace + + + &Invert Selection + + + Select &All + + + &Deselect + + + &View + + + Zoom &In + + + Ctrl++ + + + Zoom &Out + + + Ctrl+- + + + Zoom to &Window + + + Zoom to &Selection + + + &Actual Size + + + &Rulers + + + &Grid + + + &Image + + + Cro&p to Selection + + + &Resize... + + + Canvas &Size... + + + Flip &Horizontal + + + Flip &Vertical + + + Ro&tate + + + Rotate &90° Clockwise + + + Rotate 90° Counter-Clockwise + + + Rotate &180° + + + &Flatten + + + &Layers + + + &Add New Layer + + + De&lete Layer + + + &Duplicate Layer + + + &Merge Layer Down + + + &Import From File... + + + &Adjustments + + + Flip &Horizontal + + + Flip &Vertical + + + Layer &Properties... + + + Effe&cts + + + &Tools + + + &Antialiasing + + + Alpha &blending + + + &Window + + + &Reset Window Locations + + + Translucent + + + &Tools + + + &History + + + &Layers + + + &Colors + + + &Show Image List + + + &Next Tab + + + &Previous Tab + + + Lan&guage + + + &Help + + + &Help Topics + + + &Paint.NET Website + + + Paint.NET &Search... + + + &Donate... + + + &Forum + + + &Tutorials + + + &Plugins + + + &About + + + &Updates + + + Check f&or Updates... + + + Send feedback or bug report... + + + << Enter your feedback or bug report here (English only). >> + + + Feedback for: {0} + + + {0}% + {0} is an integer + + + {0} GB + + + {0} MB + + + {0} KB + + + {0} bytes + + + Blend Mode + + + Bitmap (BMP) + + + All image types + + + Quality + + + Background Status + + + Name + + + Visibility + + + Opacity + + + Background + + + Layer Properties + + + Visible + + + Name: + + + General + + + Blending + + + Mode: + + + Opacity: + + + Normal + + + Multiply + + + Additive + + + Color Burn + + + Color Dodge + + + Reflect + + + Glow + + + Overlay + + + Negation + + + Difference + + + Screen + + + Lighten + + + Darken + + + Xor + + + Angle + + + Auto-Level + + + Blurs + + + Pencil Sketch + + + Pencil tip size + + + Range + + + Outline + + + Thickness + + + Intensity + + + Median + + + Radius + + + Percentile + + + Unfocus + + + Radius + + + pixels + + + Fragment + + + Fragment Count + + + Rotation + + + Distance + + + Surface Blur + + + Radius + + + Threshold + + + Dents + Note to translators: The term 'dents' is a bit ambiguous here, so I'll defer to Ed Harvey's description: "Think of reflections off a shiny metal surface with a series of shallow indentations on it; similar hammer-worked copper like a polished version of this surface: http://www.getpaint.net/misc/HammeredCopper.jpg. Synonyms: (plural of) Dint, Indentation, Dimple, Pit." + + + Scale + + + Refraction + + + Roughness + + + Tension + + + Quality + + + Random Noise + + + Reseed + + + Vignette + + + Center + + + Radius + + + Density + + + Zoom Blur + + + Zoom Amount + + + Center + + + Gaussian Blur + + + Glow + + + Radius + + + Brightness + + + Contrast + + + Bulge + + + Distort + + + Tile Reflection + + + Twist + + + Bulge + + + Center + + + Curvature + + + Quality + + + Angle + + + Tile Size + + + Quality + + + Center + + + Size + + + Amount / Direction + + + Polar Inversion + + + Amount + + + Offset + + + Edge Behavior + + + Clamp + + + Reflect + + + Wrap + + + Quality + + + Posterize + + + Red + + + Green + + + Blue + + + Linked + + + Brightness / Contrast + + + Brightness + + + Contrast + + + Black and White + + + Edge Detect + + + Emboss + + + Frosted Glass + + + Minimum Scatter Radius + + + Maximum Scatter Radius + + + Smoothness + + + Hue / Saturation + + + Hue + + + Saturation + + + Lightness + + + Invert Colors + + + Levels + + + Output + + + Output Gamma + + + Output White Point (double click to choose) + + + Output Black Point (double click to choose) + + + Output Histogram + + + This histogram shows a preview of the distribution of color in the resultant image + + + Input Histogram + + + This histogram shows the distribution of color in the original image. + + + Input + + + Input White Point (double click to choose) + + + Input Black Point (double click to choose) + + + R + + + Toggle manipulation of the red channel + + + G + + + Toggle manipulation of the green channel + + + B + + + Toggle manipulation of the blue channel + + + Auto + + + Automatically adjusts the input white point, black point, and output gamma to equalize the image + + + Reset + + + Levels Adjustment + + + Motion Blur + + + Motion Blur + + + ° + + + pixels + + + Centered + + + Angle + + + Distance + + + Oil Painting + + + Brush size + + + Coarseness + + + Pixelate + + + Cell size + + + pixels + + + Red Eye Removal + + + Tolerance + + + Saturation percentage + + + Hint: For best results, first use the selection tools to select each eye + + + Relief + + + Sharpen + + + Amount + + + Reset + + + Radius + + + pixels + + + ° + + + Units: + + + Pixel + + + Inch + + + Centimeter + + + Pixels + + + Inches + + + Centimeters + + + px + + + cm + + + in + + + {0}{1}, {2}{3} + {0} is x offset, {1} is units, {2} is y offset, {3} is units + + + Resizing... + + + Resolution: + + + pixels/inch + + + pixels/cm + + + pixels + + + Paint.NET Updates + + + &Options... + + + More Info... + + + New version: + + + {0}% + + + The Paint.NET Updates System is initializing. + + + Initializing + + + Paint.NET is ready to check for updates. + + + Check Now + + + Paint.NET is currently checking to see if there is a newer version available. + + + Checking + + + A newer version of Paint.NET is available. You may click the Install button below to download and install the update. + + + &Install + + + The update for Paint.NET is being downloaded. + + + Downloading + + + The update has been downloaded, and is currently being extracted and verified. + + + Extracting + + + Paint.NET must close to install the update. After you click OK, you will be asked to save any changes if necessary. + + + OK + + + Installing the update! + + + Installing + + + No updates are currently available. + + + Done + + + The updater was aborted. + + + + + + There was an error while checking for updates. + +{0} + + + + + + A newer version of Paint.NET is available. You may click the Install button to download and install it, or you may click Close to be reminded again in several days. + + + NOTE: This is a pre-release version (beta). It may not be stable. + + + Close + + + Download: + + + Currently Installed Version + + + New Version + + + Install + + + Paint.NET Update Options + + + &Save + + + Automatically check for newer versions of Paint.NET + + + Also check for pre-release (beta) versions + + + Note: These settings affect all users of this computer. + + + This pre-release version of Paint.NET has expired (30 days). + +Click OK to go to the Paint.NET website where you may download a newer version. + + + This pre-release version of Paint.NET has expired. To continue using Paint.NET, you must update to the latest version. + + + &Check for an Update + + + Check to see if an update is available, and then install it. + + + &Go to the Paint.NET website + + + If checking for updates does not work, you may try going to the website to manually find an update. + + + E&xit + + + Exit Paint.NET without checking for updates. + + + Paint.NET Updates + + + Downloading... + + + Extracting... + + + There was an error downloading and installing the new version. Please try again later with the 'Help -> Check for Updates ...' menu command. + + + Language Choice Confirmation + + + Changing the language requires that Paint.NET be restarted. + + + &Restart Paint.NET + + + Closes Paint.NET and then restarts it in {0}. You will be asked to save your changes if necessary. + {0} is the name of the new language that has been chosen (ex, "English"). + + + Cancel + + + Does not close Paint.NET. Your language choice will be cancelled. + + + Reduce Noise + + + Radius + + + Strength + + + Intensity + + + Color Saturation + + + Coverage + + + Add Noise + + + Are you sure you want to clear the Open Recent list? + + + Sepia + + + You must be a system administrator to perform this action. + + + Bit Depth + + + Auto-detect + + + 24-bit + + + 8-bit + + + Dithering level + + + Bit Depth + + + Auto-detect + + + 32-bit + + + 24-bit + + + 8-bit + + + Dithering level + + + Transparency threshold + + + Pixels with an alpha value less than the threshold will be fully transparent. + + + + Transparency threshold + + + Dithering level + + + Multiply by alpha channel + + + Pixels with an alpha value less than the threshold will be fully transparent. + + + + + + Please ensure that Paint.NET is closed, and then click Retry. Or, press Cancel to exit the setup wizard. + + + Paint.NET Installer Help + +Command-line options: + +/skipConfig - Skips configuration of options and install directory. Uses MSI properties in the following order of precedence: 1. supplied on command-line, 2. read from HKLM\Software\Paint.NET, 3. default values. NOTE: Use of this option infers acceptance of the License Agreement. +/auto - Automatic install. Infers /skipConfig. Does not show the final wizard page that details the result of installation. +/createMsi - Creates MSI packages that may be used for deployment (AD/GPO) purposes. All properties specified on the command-line are merged into the MSI packages, so no further transforms are necessary. +/verbose - Enables verbose Windows Installer MSI logging +PROPERTY=VALUE - Sets an MSI property. See help file after installing for more information. You do not need to specify /saveMsi to specify properties. + +Return code is 0 on success, or non-zero on failure. + + + 32-bit MSI may not be installed on 64-bit OS. + + + Paint.NET requires Windows XP SP2, Windows Vista, Windows Server 2003, or later. + + + Administrator privilege is required to install Paint.NET. + + + Cancel + + + < &Back + + + &Next > + + + Finish + + + The installation is not yet complete. Are you sure you want to exit? + + + Setup Wizard + + + This installer will help you install {0}. + +Choose the installation method you prefer, and then click Next. + {0} is the product name. + + + Quick + + + This is the recommended choice for most users. This installs using the default options, or the same options that were specified during a previous installation. + + + Custom + + + Allows you to change the installation directory, and options related to file type associations and update checking. + + + NOTE: This is a pre-release version of Paint.NET that will expire on {0}. + {0} is a date. + + + License Agreement + + + Please take a moment to read the license agreement now. If you accept the terms below, click "I Agree", then "Next". Otherwise click "Cancel". + + + I &Do Not Agree + + + I &Agree + + + Configure Options + + + Use these options to configure how Paint.NET can be launched. + + + Use it as the default image editor for JPEG, PNG, and BMP images + + + Use it as the default image editor for TGA images + + + Create a shortcut on the Desktop + + + Paint.NET can periodically check to see if there is a new version available and then notify you. No information is transmitted from your computer during this process. Note: You will only be notified of new versions while Paint.NET is running. + + + Automatically check for updates + + + Also check for pre-release (beta) versions + + + Select Installation Folder + + + The installer will install {0} to the following folder. + +To install in this folder, click "Next". To install to a different folder, enter it below or click "Browse". + + + Folder: + + + &Browse... + + + Paint.NET + + + Choose an installation folder: + + + The specified folder could not be created. You will need to choose another folder to install to. + + + Confirm Installation + + + The installer is ready to install {0} on your computer. + +Click "Next" to start the installation. + + + Installing + + + Installation Succeeded + + + Error + + + Creating system restore point... + + + Closing system restore point... + + + Removing previous version... + + + {0} is being installed... + + + Optimizing performance for your system. This may take a minute. + + + {0} was installed successfully. Click "Finish" to close this installation wizard. + + + {0} was installed successfully. A reboot may be required to use it properly. Click "Finish" to close this installation wizard. + + + There was an error while installing {0}. + + + Thanks for using Paint.NET. You can show your appreciation and support future development by donating! + + + Deployment Wizard + + + Creating MSI packages... + + + The MSI packages have been placed into the following directory: '{0}'. + + + Paint.NET is provided FREE OF CHARGE. If you paid money for it (other than to <a>donate at the Paint.NET website</a>, of course) then we recommend that you get a refund! + The area delimited with <a> and </a> is treated as a link. The destination URL is the donation page at the Paint.NET website. + + + Paint.NET + + + Create, edit, scan, and print images and photographs. + + + Rotate / Zoom + + + Fine Tuning + + + Preserve background + + + Tiling + + + Roll / Rotate + + + Angle: + + + Pan + + + X Pan: + + + Y Pan: + + + Reset + + + Reset + + + Twist Angle: + + + Twist Radius: + + + Reset + + + &Reset All + + + Zoom + + + {0}x + + + Bit Depth + + + Compress (RLE) + + + 24-bit + + + 32-bit + + + Auto-detect + + + There was an error launching the link. + + + There was an error launching the link: {0} + {0} is a URL. + + + Radial Blur + + + Angle + + + Quality + + + Low quality is useful for quick previews, small images, or small angle values. High quality is useful for final quality, large images, or large angle values. + + + Center + + + The remote service point could not be contacted at the transport level. + All of the WebExceptionStatus.* strings are copied straight from MSDN's documentation (except where explicitely noted), http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemnetwebexceptionstatusclasstopic.asp + + + The connection was prematurely closed. + + + The connection for a request that specifies the keep-alive header was closed unexpectedly. + + + A message was received that exceeded the specified limit when sending a request or receiving a response from the server. + + + The name resolver service could not resolve the host name. + + + An internal asynchronous request is pending. + + + There was an internal .NET error (WebExceptionStatus.PipelineFailure). + This text is different than what is in the MSDN documentation. + + + The response received from the server was complete but indicated a protocol-level error. For example, an HTTP protocol error such as 401 Access Denied would use this status. + + + The server returned the following error code: HTTP {1} ({0}) + {0} is a string name from the System.Net.HttpStatusCode enumeration. {1} is an integer, such as 404 + + + The name resolver service could not resolve the proxy host name. + + + A complete response was not received from the remote server. + + + There was an internal error (WebExceptionStatus.RequestCanceled) + This text is different than what is in the MSDN documentation. + + + An error occurred while establishing a connection using SSL. + + + A complete request could not be sent to the remote server. + + + The server response was not a valid HTTP response. + + + No error was encountered. + + + No response was received during the time-out period for a request. + + + A server certificate could not be validated. + + + An exception of unknown type has occurred. + + + Curves + + + Curves + + + Input Histogram + + + Transfer Map + + + Output Histogram + + + Red + + + Green + + + Blue + + + Luminosity + + + Tip: Right-click to remove control points. + + + ({0}, {1}) + + + &Reset + + + Luminosity + + + RGB + + + This text file was created because Paint.NET crashed. +Please e-mail this file to {0} so we can diagnose and fix the problem. + + {0} is an e-mail address, such as paint.net@hotmail.com + + + Swap Layer + + + There was not enough memory to perform the action. + + + There was an error performing the action. + + + None + + + Sharp + + + Smooth + + + &Tool: + + + Tolerance: + + + There was an error decompressing the download. + + + There was an unspecified error while downloading the update. + + + There was an unspecified error while checking for updates. + + + Unsaved Changes + + + The following images that are open have changes that have not been saved. You may click on a thumbnail to show the image in the main window. + + + Images + + + &Save + + + Save the images that are in the list above, and then exit. + + + Do&n't Save + + + Discard all unsaved changes, and then exit. + + + Cancel + + + Go back to Paint.NET + + + Cancel + + + + + + This is a pre-release version, and is recommended for testing purposes only. Expires on {1}. An update will be available before then. +www.getpaint.net + {0} is the full application name, e.g. "Paint.NET v3.0 (Beta 1 Release build 3.0.2460.34921). {1} is a date string. + + + Choose &defaults... + + + You may choose the tool that is active when starting Paint.NET, and what the defaults should be for the toolbar items. + + + &Load From Toolbar + + + &Reset + + + Default tool: + + + Move Selected Pixels + + + Shape, Brush, Style, and Fill + + + Text + + + Gradient + + + Magic Wand, Paint Bucket, and Recolor + + + Color Picker + + + Rasterization + + + Selection Tools + + + Choose Defaults + + + Unsaved Changes + + + &Save + + + Save the image, and then close it. + + + Do&n't Save + + + Discard the unsaved changes. + + + Cancel + + + Go back to Paint.NET. + + + {0} has unsaved changes. What would you like to do? + + + Linear + + + Linear (Reflected) + + + Diamond + + + Radial + + + Conical + + + Color Mode + + + Transparency Mode + + + Normal Blending + + + Overwrite + + + Antialiasing Enabled + + + Antialiasing Disabled + + + {0} +Shortcut key: {1} + {0} is the tool's name (e.g. "Magic Wand"). {1} is a character that is the shortcut key (e.g. "C") + + + Increase brush size +Shortcut key: ] (hold Ctrl to increase by 5) + + + Decrease brush size +Shortcut key: [ (hold Ctrl to decrease by 5) + + + Toggle start cap for line style +Shortcut key: , + + + Toggle dash type for line style +Shortcut key: . + + + Toggle end cap for line style +Shortcut key: / + + + Style: + + + Render + + + Photo + + + Noise + + + Artistic + + + Stylize + + + Mandelbrot Fractal + + + Factor + + + Quality + + + Zoom + + + Angle + + + Offset + + + Invert Colors + + + Julia Fractal + + + Factor + + + Angle + + + Quality + + + Zoom + + + Clouds + + + To change the color that the clouds are drawn in, you can set the primary and secondary colors in the Colors window. + + + Scale + + + Roughness + + + Random Noise + + + Blend Mode + + + &Reseed + + + Paste + + + The image being pasted is larger than the canvas size. What do you want to do? + + + &Expand canvas + + + Automatically expands the canvas to fit the image being pasted. + + + &Keep canvas size + + + Does not expand the canvas. You will have to move the pasted image around to make sure that the part you want is within the canvas boundaries. + + + Cancel + + + Cancels the paste action. + + + Flat + + + Arrow + + + Filled Arrow + + + Rounded + + + Custom_NotUsed + This string does not need to be translated. It is not used. + + + Solid + + + Dashes + + + Dotted + + + Dash, Dot + + + Dash, Dot, Dot + + + {0}/doc/latest/index.html + Note to translators: This string only needs to be changed if the UA (user assistance, aka help file, documentation) is also being translated. +{0} is the base website address, such as http://www.getpaint.net. This string is not required to have {0} in it. So if you want to distribute a language pack that hosts the help file elsewhere (that is to say, not on the official Paint.NET website), you can do that by specifying the URL directly, e.g. "http://www.wherever.net/paintnethelp/fr/". + + + Transferring ... + + + Initializing ... + + + Canceling ... + + + {0} of {1} + {0} is the number of the current item, {1} is the total number of items. Example: "5 of 7" + + + Transfer Error + + + There was an error transferring the image: + +{0} + {0} is an error message, which will be populated by the operation system in the language that it is configured for. + + + &Retry + + + Retries transferring the image. + + + &Skip + + + Skips this image, and moves on to the next one. + + + Cancel + + + Cancels the transfer, and lets you reselect which images to open. + + + Confirm Save As + + + {0} already exists. +Do you want to replace it? + {0} is a relative filename. e.g. "Untitled.png" + + + Ink Sketch + + + Ink Outline + + + Coloring + + + Soften Portrait + + + Softness + + + Lighting + + + Warmth + + + DirectDraw Surface (DDS) + + + Range fit (Fast/LQ) + + + Cluster fit (Slow/HQ) + + + Iterative fit (Slowest/HQ) + + + Uniform + + + Perceptual + + + Generate Mip Maps + + + Weight Color By Alpha + + + DXT1 (Opaque/1-bit Alpha) + + DXT3 (Explicit Alpha) + + DXT5 (Interpolated Alpha) + + A8R8G8B8 + + + X8R8G8B8 + + + A8B8G8R8 + + + X8B8G8R8 + + + A1R5G5B5 + + + A4R4G4B4 + + + R8G8B8 + + + R5G6B5 + + + Compressor Type + + + Error Metric + + + Additional Options + + + Selection Mode: + + + Replace + + + Add (union) + This is the name for a 'boolean OR' combination of two selections. More information is available at: http://en.wikipedia.org/wiki/Union_%28set_theory%29 + + + Subtract + + + Invert ("xor") + + + Intersect + This is the name for a 'boolean AND' combination of two selections. More information is available at: http://en.wikipedia.org/wiki/Intersection_%28set_theory%29 + + + Flood Mode: + + + Contiguous + + + Global + + + Draw mode: + + + Width: + + + Height: + + + Normal + + + Fixed Ratio + + + Fixed Size + + + Plugin Error + + + This plugin has encountered an error, and must be closed. + +Because of the unpredictable nature of these types of errors, it is strongly recommended that you restart Paint.NET before proceeding. Otherwise, there could be further stability problems or data corruption. + + + Restart Paint.NET (recommended) + + + Closes the plugin, and then closes your opened images and restarts Paint.NET. You will be asked to save changes if necessary. + + + Do not restart Paint.NET (not recommended) + + + Closes the plugin, but does not close or restart Paint.NET. + + + Error Details... + + + This plugin is known to cause stability problems, and has been blocked from loading. + + + This plugin is now built-in to Paint.NET. + + \ No newline at end of file diff --git a/src/Strings/Strings.vcproj b/src/Strings/Strings.vcproj new file mode 100644 index 0000000..c0b77a9 --- /dev/null +++ b/src/Strings/Strings.vcproj @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/StylusReader/AssemblyInfo.cs b/src/StylusReader/AssemblyInfo.cs new file mode 100644 index 0000000..4678e48 --- /dev/null +++ b/src/StylusReader/AssemblyInfo.cs @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(true)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET StylusReader class")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Sometimes)] diff --git a/src/StylusReader/IStylusReaderHooks.cs b/src/StylusReader/IStylusReaderHooks.cs new file mode 100644 index 0000000..c70fe9c --- /dev/null +++ b/src/StylusReader/IStylusReaderHooks.cs @@ -0,0 +1,24 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public interface IStylusReaderHooks + { + void PerformDocumentMouseMove(MouseButtons button, int clicks, float x, float y, int delta, float pressure); + void PerformDocumentMouseUp(MouseButtons button, int clicks, float x, float y, int delta, float pressure); + void PerformDocumentMouseDown(MouseButtons button, int clicks, float x, float y, int delta, float pressure); + System.Drawing.Graphics CreateGraphics(); + PointF ScreenToDocument(PointF pointF); + } +} diff --git a/src/StylusReader/StylusAsyncPlugin.cs b/src/StylusReader/StylusAsyncPlugin.cs new file mode 100644 index 0000000..3d03ff7 --- /dev/null +++ b/src/StylusReader/StylusAsyncPlugin.cs @@ -0,0 +1,187 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Ink; +using Microsoft.StylusInput; +using Microsoft.StylusInput.PluginData; +using PaintDotNet; +using System; +using System.Collections; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal sealed class StylusAsyncPlugin + : IStylusAsyncPlugin + { + private PointF ratio = PointF.Empty; + private IStylusReaderHooks subject; + private Control attachedControl; + + internal StylusAsyncPlugin(IStylusReaderHooks subject, Control attached) + { + Graphics g = subject.CreateGraphics(); + attachedControl = attached; + this.ratio = new PointF(g.DpiX / 2540.0f, g.DpiY / 2540.0f); + this.subject = subject; + } + + private PointF HimetricToPointF(int x, int y) + { + return new PointF(x * ratio.X, y * ratio.Y); + } + + private MouseButtons StatusToMouseButtons(int status) + { + if ((status & 0x1) != 0) + { + if ((status & 0x8) != 0) + { + return MouseButtons.Right; + } + else if ((status & 0x2) != 0) + { + return MouseButtons.Middle; + } + else + { + return MouseButtons.Left; + } + } + else + { + return MouseButtons.None; + } + } + + MouseButtons lastbutton = MouseButtons.None; + + private void Interpret(StylusDataBase data, int index) + { + Point offset = attachedControl.PointToScreen(new Point(0, 0)); + PointF relativePosition = HimetricToPointF(data[index], data[index + 1]); + PointF position = subject.ScreenToDocument(new PointF(relativePosition.X + offset.X, relativePosition.Y + offset.Y)); + float pressure = (data.PacketPropertyCount > 3 ? data[index + 2] : 255.0f) / 255.0f; + int status = data[index + data.PacketPropertyCount - 1]; + MouseButtons button = StatusToMouseButtons(status); + + bool didMouseMove = false; + + if (lastbutton != button) + { + //if a button was previously down, MouseUp it. + if (lastbutton != MouseButtons.None) + { + subject.PerformDocumentMouseMove(button, 1, position.X, position.Y, 0, pressure); + subject.PerformDocumentMouseUp(lastbutton, 1, position.X, position.Y, 0, pressure); + didMouseMove = true; + } + + //if a new button was pushed, MouseDown it. + if (button != MouseButtons.None) + { + subject.PerformDocumentMouseDown(button, 1, position.X, position.Y, 0, pressure); + subject.PerformDocumentMouseMove(button, 1, position.X, position.Y, 0, pressure); + didMouseMove = true; + } + } + + if (!didMouseMove) + { + //regardless of the button states, send a new MouseMove + subject.PerformDocumentMouseMove(button, 1, position.X, position.Y, 0, pressure); + } + + lastbutton = button; + } + + #region IStylusAsyncPlugin Members + + public Microsoft.StylusInput.DataInterestMask DataInterest + { + get + { + return DataInterestMask.AllStylusData; + } + } + + public void Packets(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.PacketsData data) + { + for (int i = 0; i < data.Count; i += data.PacketPropertyCount) + { + Interpret(data, i); + } + } + + public void InAirPackets(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.InAirPacketsData data) + { + for (int i = 0; i < data.Count; i += data.PacketPropertyCount) + { + Interpret(data, i); + } + } + + public void StylusDown(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.StylusDownData data) + { + Interpret(data, 0); + } + + public void StylusUp(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.StylusUpData data) + { + Interpret(data, 0); + } + + public void StylusButtonDown(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.StylusButtonDownData data) + { + } + + public void StylusButtonUp(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.StylusButtonUpData data) + { + } + + public void StylusInRange(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.StylusInRangeData data) + { + } + + public void StylusOutOfRange(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.StylusOutOfRangeData data) + { + } + + public void RealTimeStylusEnabled(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.RealTimeStylusEnabledData data) + { + } + + public void RealTimeStylusDisabled(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.RealTimeStylusDisabledData data) + { + } + + public void Error(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.ErrorData data) + { + } + + public void SystemGesture(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.SystemGestureData data) + { + } + + public void TabletAdded(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.TabletAddedData data) + { + } + + public void TabletRemoved(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.TabletRemovedData data) + { + } + + public void CustomStylusDataAdded(Microsoft.StylusInput.RealTimeStylus sender, Microsoft.StylusInput.PluginData.CustomStylusData data) + { + } + + #endregion + } +} diff --git a/src/StylusReader/StylusReader.cs b/src/StylusReader/StylusReader.cs new file mode 100644 index 0000000..01beac8 --- /dev/null +++ b/src/StylusReader/StylusReader.cs @@ -0,0 +1,96 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Ink; +using Microsoft.StylusInput; +using Microsoft.StylusInput.PluginData; +using PaintDotNet; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + public sealed class StylusReader + { + private StylusReader() + { + } + + // If we don't keep the styluses, they get garbagecollected. + private static Hashtable hookedControls = Hashtable.Synchronized(new Hashtable()); + + public static void HookStylus(IStylusReaderHooks subject, Control control) + { + if (hookedControls.Contains(control)) + { + throw new ApplicationException("control is already hooked"); + } + + RealTimeStylus stylus = new RealTimeStylus(control, true); + PaintDotNet.StylusAsyncPlugin stylusReader = new PaintDotNet.StylusAsyncPlugin(subject, control); + + stylus.AsyncPluginCollection.Add(stylusReader); + stylus.SetDesiredPacketDescription(new Guid[] { PacketProperty.X, + PacketProperty.Y, + PacketProperty.NormalPressure, + PacketProperty.PacketStatus}); + stylus.Enabled = true; + + control.Disposed += new EventHandler(control_Disposed); + + WeakReference weakRef = new WeakReference(control); + hookedControls.Add(weakRef, stylus); + } + + public static void UnhookStylus(Control control) + { + lock (hookedControls.SyncRoot) + { + List deleteUs = new List(); + + foreach (WeakReference weakRef in hookedControls.Keys) + { + object target = weakRef.Target; + + if (target == null) + { + deleteUs.Add(weakRef); + } + else + { + Control control2 = (Control)target; + + if (object.ReferenceEquals(control, control2)) + { + deleteUs.Add(weakRef); + } + } + } + + foreach (WeakReference weakRef in deleteUs) + { + RealTimeStylus stylus = (RealTimeStylus)hookedControls[weakRef]; + stylus.Enabled = false; + stylus.AsyncPluginCollection.Clear(); + hookedControls.Remove(weakRef); + } + } + } + + private static void control_Disposed(object sender, EventArgs e) + { + Control asControl = (Control)sender; + asControl.Disposed -= new EventHandler(control_Disposed); + UnhookStylus(asControl); + } + } +} diff --git a/src/StylusReader/StylusReader.csproj b/src/StylusReader/StylusReader.csproj new file mode 100644 index 0000000..dcd3dbb --- /dev/null +++ b/src/StylusReader/StylusReader.csproj @@ -0,0 +1,115 @@ + + + Local + 9.0.21022 + 2.0 + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} + Debug + AnyCPU + + + + + PaintDotNet.StylusReader + + + JScript + Grid + IE50 + false + Library + PaintDotNet + OnBuildSuccess + + + + + + + 2.0 + + + bin\Debug\ + false + 281018368 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 281018368 + false + + + TRACE + + + true + 512 + false + + + true + false + false + true + 4 + pdbonly + prompt + + + + False + ..\Microsoft.Ink\Microsoft.Ink.dll + + + System + + + System.Drawing + + + System.Windows.Forms + + + + + Code + + + Code + + + Code + + + Code + + + + + + + @rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/SurfaceForClipboard.cs b/src/SurfaceForClipboard.cs new file mode 100644 index 0000000..1168c63 --- /dev/null +++ b/src/SurfaceForClipboard.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace PaintDotNet +{ + // TODO: Eliminate poor code. + /// + /// Encapsulates a surface that can be copied to the clipboard. + /// + [Serializable] + internal class SurfaceForClipboard + { + public MaskedSurface MaskedSurface; + public Rectangle Bounds; + + public SurfaceForClipboard(MaskedSurface maskedSurface) + { + using (PdnRegion region = maskedSurface.CreateRegion()) + { + this.Bounds = region.GetBoundsInt(); + } + + this.MaskedSurface = maskedSurface; + } + } +} diff --git a/src/SystemLayer/AssemblyInfo.cs b/src/SystemLayer/AssemblyInfo.cs new file mode 100644 index 0000000..50c1818 --- /dev/null +++ b/src/SystemLayer/AssemblyInfo.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET System Layer")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Always)] +[assembly: Dependency("System.Windows.Forms", LoadHint.Always)] +[assembly: Dependency("System.Drawing", LoadHint.Always)] +[assembly: Dependency("StylusReader", LoadHint.Sometimes)] diff --git a/src/SystemLayer/ClassicFileDialog.cs b/src/SystemLayer/ClassicFileDialog.cs new file mode 100644 index 0000000..d31501a --- /dev/null +++ b/src/SystemLayer/ClassicFileDialog.cs @@ -0,0 +1,210 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal abstract class ClassicFileDialog + : IFileDialog + { + private FileDialog fileDialog; + + protected FileDialog FileDialog + { + get + { + return this.fileDialog; + } + } + + public bool CheckPathExists + { + get + { + return this.fileDialog.CheckPathExists; + } + + set + { + this.fileDialog.CheckPathExists = value; + } + } + + public bool DereferenceLinks + { + get + { + return this.fileDialog.DereferenceLinks; + } + set + { + this.fileDialog.DereferenceLinks = value; + } + } + + public string Filter + { + get + { + return this.fileDialog.Filter; + } + + set + { + this.fileDialog.Filter = value; + } + } + + public int FilterIndex + { + get + { + return this.fileDialog.FilterIndex; + } + + set + { + this.fileDialog.FilterIndex = value; + } + } + + public string InitialDirectory + { + get + { + return this.fileDialog.InitialDirectory; + } + + set + { + this.fileDialog.InitialDirectory = value; + } + } + + public string Title + { + set + { + this.fileDialog.Title = value; + } + } + + // This is a major hack to get the .NET's OFD to show with Thumbnail view by default! + // Luckily for us this is a covert hack, and not one where we're working around a bug + // in the framework or OS. + // This hack works by retrieving a private property of the OFD class after it has shown + // the dialog box. + // Based off code found here: http://vbnet.mvps.org/index.html?code/hooks/fileopensavedlghooklvview.htm + private static void EnableThumbnailView(FileDialog ofd) + { + // HACK: Must verify this still works with each new revision of .NET + try + { + Type ofdType = typeof(FileDialog); + FieldInfo fi = ofdType.GetField("dialogHWnd", BindingFlags.Instance | BindingFlags.NonPublic); + + if (fi != null) + { + object dialogHWndObject = fi.GetValue(ofd); + IntPtr dialogHWnd = (IntPtr)dialogHWndObject; + IntPtr hwndLV = SafeNativeMethods.FindWindowExW(dialogHWnd, IntPtr.Zero, "SHELLDLL_DefView", null); + + if (hwndLV != IntPtr.Zero) + { + SafeNativeMethods.SendMessageW(hwndLV, NativeConstants.WM_COMMAND, new IntPtr(NativeConstants.SHVIEW_THUMBNAIL), IntPtr.Zero); + } + } + } + + catch (Exception) + { + // Ignore. + } + } + + public DialogResult ShowDialog(IWin32Window owner, IFileDialogUICallbacks uiCallbacks) + { + Control ownerAsControl = owner as Control; + + if (uiCallbacks == null) + { + throw new ArgumentNullException("uiCallbacks"); + } + + Cursor.Current = Cursors.WaitCursor; + + if ((Control.ModifierKeys & Keys.Shift) != 0) + { + UI.InvokeThroughModalTrampoline( + owner, + delegate(IWin32Window modalOwner) + { + while ((Control.ModifierKeys & Keys.Shift) != 0) + { + System.Threading.Thread.Sleep(1); + Application.DoEvents(); + } + }); + } + + Cursor.Current = Cursors.Default; + + DialogResult result = DialogResult.Cancel; + + UI.InvokeThroughModalTrampoline( + owner, + delegate(IWin32Window modalOwner) + { + if (ownerAsControl != null && ownerAsControl.IsHandleCreated) + { + ownerAsControl.BeginInvoke(new Procedure(EnableThumbnailView), new object[] { this.fileDialog }); + } + + result = this.fileDialog.ShowDialog(modalOwner); + }); + + return result; + } + + protected ClassicFileDialog(FileDialog fileDialog) + { + this.fileDialog = fileDialog; + this.fileDialog.RestoreDirectory = true; + this.fileDialog.ShowHelp = false; + this.fileDialog.ValidateNames = true; + } + + ~ClassicFileDialog() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (this.fileDialog != null) + { + this.fileDialog.Dispose(); + this.fileDialog = null; + } + } + } + } +} diff --git a/src/SystemLayer/ClassicFileOpenDialog.cs b/src/SystemLayer/ClassicFileOpenDialog.cs new file mode 100644 index 0000000..adffa38 --- /dev/null +++ b/src/SystemLayer/ClassicFileOpenDialog.cs @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal sealed class ClassicFileOpenDialog + : ClassicFileDialog, + IFileOpenDialog + { + private OpenFileDialog OpenFileDialog + { + get + { + return this.FileDialog as OpenFileDialog; + } + } + + public bool CheckFileExists + { + get + { + return this.OpenFileDialog.CheckFileExists; + } + + set + { + this.OpenFileDialog.CheckFileExists = value; + } + } + + public bool Multiselect + { + get + { + return this.OpenFileDialog.Multiselect; + } + + set + { + this.OpenFileDialog.Multiselect = value; + } + } + + public string[] FileNames + { + get + { + return this.OpenFileDialog.FileNames; + } + } + + public ClassicFileOpenDialog() + : base(new System.Windows.Forms.OpenFileDialog()) + { + } + } +} diff --git a/src/SystemLayer/ClassicFileSaveDialog.cs b/src/SystemLayer/ClassicFileSaveDialog.cs new file mode 100644 index 0000000..d66c06d --- /dev/null +++ b/src/SystemLayer/ClassicFileSaveDialog.cs @@ -0,0 +1,71 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal sealed class ClassicFileSaveDialog + : ClassicFileDialog, + IFileSaveDialog + { + private SaveFileDialog SaveFileDialog + { + get + { + return this.FileDialog as SaveFileDialog; + } + } + + public bool AddExtension + { + get + { + return this.SaveFileDialog.AddExtension; + } + + set + { + this.SaveFileDialog.AddExtension = value; + } + } + + public string FileName + { + get + { + return this.SaveFileDialog.FileName; + } + + set + { + this.SaveFileDialog.FileName = value; + } + } + + public bool OverwritePrompt + { + get + { + return this.SaveFileDialog.OverwritePrompt; + } + + set + { + this.SaveFileDialog.OverwritePrompt = value; + } + } + + public ClassicFileSaveDialog() + : base(new SaveFileDialog()) + { + } + } +} diff --git a/src/SystemLayer/Clipboard.cs b/src/SystemLayer/Clipboard.cs new file mode 100644 index 0000000..78b4bf2 --- /dev/null +++ b/src/SystemLayer/Clipboard.cs @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public static class Clipboard + { + public static Image GetEmfFromClipboard(IWin32Window currentWindow) + { + Image returnVal = null; + + if (NativeMethods.OpenClipboard(currentWindow.Handle)) + { + try + { + if (NativeMethods.IsClipboardFormatAvailable(NativeConstants.CF_ENHMETAFILE)) + { + IntPtr hEmf = NativeMethods.GetClipboardData(NativeConstants.CF_ENHMETAFILE); + + if (hEmf != IntPtr.Zero) + { + try + { + Metafile metafile = new Metafile(hEmf, true); + returnVal = metafile; + } + + catch (Exception) + { + + } + } + } + } + + finally + { + NativeMethods.CloseClipboard(); + } + } + + GC.KeepAlive(currentWindow); + return returnVal; + } + } +} diff --git a/src/SystemLayer/CommonDialogs.cs b/src/SystemLayer/CommonDialogs.cs new file mode 100644 index 0000000..10a6fe2 --- /dev/null +++ b/src/SystemLayer/CommonDialogs.cs @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.SystemLayer +{ + public static class CommonDialogs + { + public static IFileOpenDialog CreateFileOpenDialog() + { + if (OS.IsVistaOrLater) + { + return new VistaFileOpenDialog(); + } + else + { + return new ClassicFileOpenDialog(); + } + } + + public static IFileSaveDialog CreateFileSaveDialog() + { + if (OS.IsVistaOrLater) + { + return new VistaFileSaveDialog(); + } + else + { + return new ClassicFileSaveDialog(); + } + } + } +} diff --git a/src/SystemLayer/ExecutePrivilege.cs b/src/SystemLayer/ExecutePrivilege.cs new file mode 100644 index 0000000..6686619 --- /dev/null +++ b/src/SystemLayer/ExecutePrivilege.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public enum ExecutePrivilege + { + /// + /// The process is started with default permissions: either the same as the invoker, + /// or those required by the executable's manifest. + /// + AsInvokerOrAsManifest, + + /// + /// The process is required to run with administrator privilege. If the user does not + /// have administrator privilege, nor has the ability to obtain it, then the operation + /// will fail. + /// + RequireAdmin, + + /// + /// The process is required to run with normal privilege. On some systems this may + /// not be possible, and as such this will have the same effect as AsInvokerOrAsManifest. + /// + /// + /// This flag only has an effect in Windows Vista from a process that already has + /// administrator privilege. + /// + RequireNonAdminIfPossible + } +} diff --git a/src/SystemLayer/ExecuteWaitType.cs b/src/SystemLayer/ExecuteWaitType.cs new file mode 100644 index 0000000..4c06b89 --- /dev/null +++ b/src/SystemLayer/ExecuteWaitType.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public enum ExecuteWaitType + { + /// + /// Returns immediately after executing without waiting for the task to finish. + /// + ReturnImmediately, + + /// + /// Waits until the task exits before returning control to the calling method. + /// + WaitForExit, + + /// + /// Returns immediately after executing without waiting for the task to finish. + /// However, another task will be spawned that will wait for the requested task + /// to finish, and it will then relaunch Paint.NET if the task was successful. + /// This is only intended to be used by the Paint.NET updater so that it can + /// relaunch Paint.NET with the same user and privilege-level that initiated + /// the update. + /// + RelaunchPdnOnExit + } +} diff --git a/src/SystemLayer/FileOverwriteAction.cs b/src/SystemLayer/FileOverwriteAction.cs new file mode 100644 index 0000000..7c86f86 --- /dev/null +++ b/src/SystemLayer/FileOverwriteAction.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public enum FileOverwriteAction + { + Overwrite, + Cancel + } +} diff --git a/src/SystemLayer/FileSystem.cs b/src/SystemLayer/FileSystem.cs new file mode 100644 index 0000000..ab458a6 --- /dev/null +++ b/src/SystemLayer/FileSystem.cs @@ -0,0 +1,654 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32.SafeHandles; +using System; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public static class FileSystem + { + private const string sessionLockFileName = "session.lock"; + private static string tempDir; + private static Stream sessionToken; + private static Random random = new Random(); + + /// + /// Creates a file stream with the given filename that is deleted when either the + /// stream is closed, or the application is terminated. + /// + /// + /// If the file already exists, it is overwritten without any error (CREATE_ALWAYS). + /// + /// The full path to the file to create. + /// A Stream with read and write access. + public static FileStream CreateTempFile(string fileName) + { + IntPtr hFile = SafeNativeMethods.CreateFileW( + fileName, + NativeConstants.GENERIC_READ | NativeConstants.GENERIC_WRITE, + NativeConstants.FILE_SHARE_READ, + IntPtr.Zero, + NativeConstants.CREATE_ALWAYS, + NativeConstants.FILE_ATTRIBUTE_TEMPORARY | + NativeConstants.FILE_FLAG_DELETE_ON_CLOSE | + NativeConstants.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + IntPtr.Zero); + + if (hFile == NativeConstants.INVALID_HANDLE_VALUE) + { + NativeMethods.ThrowOnWin32Error("CreateFileW returned INVALID_HANDLE_VALUE"); + } + + SafeFileHandle sfhFile = new SafeFileHandle(hFile, true); + FileStream stream; + + try + { + stream = new FileStream(sfhFile, FileAccess.ReadWrite); + } + + catch (Exception) + { + SafeNativeMethods.CloseHandle(hFile); + hFile = IntPtr.Zero; + throw; + } + + return stream; + } + + /// + /// Opens a file for streaming. This stream should be read from or written + /// to sequentially for best performance. Random I/O is still permissible, + /// but may not perform as well. + /// This file is created in such a way that is it NOT indexed by the system's + /// file indexer (e.g., Windows Desktop Search). + /// + /// The file to open. + /// A Stream object that may be used to read from or write to the file, depending on the fileMode parameter. + public static FileStream OpenStreamingFile(string fileName, FileAccess fileAccess) + { + uint dwDesiredAccess; + uint dwCreationDisposition; + + switch (fileAccess) + { + case FileAccess.Read: + dwDesiredAccess = NativeConstants.GENERIC_READ; + dwCreationDisposition = NativeConstants.OPEN_EXISTING; + break; + + case FileAccess.ReadWrite: + dwDesiredAccess = NativeConstants.GENERIC_READ | NativeConstants.GENERIC_WRITE; + dwCreationDisposition = NativeConstants.OPEN_ALWAYS; + break; + + case FileAccess.Write: + dwDesiredAccess = NativeConstants.GENERIC_WRITE; + dwCreationDisposition = NativeConstants.CREATE_NEW; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + uint dwFlagsAndAttributes = + NativeConstants.FILE_ATTRIBUTE_TEMPORARY | + NativeConstants.FILE_FLAG_SEQUENTIAL_SCAN | + NativeConstants.FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + + IntPtr hFile = SafeNativeMethods.CreateFileW( + fileName, + dwDesiredAccess, + NativeConstants.FILE_SHARE_READ, + IntPtr.Zero, + dwCreationDisposition, + dwFlagsAndAttributes, + IntPtr.Zero); + + if (hFile == NativeConstants.INVALID_HANDLE_VALUE) + { + NativeMethods.ThrowOnWin32Error("CreateFileW returned INVALID_HANDLE_VALUE"); + } + + FileStream stream; + + try + { + SafeFileHandle sfh = new SafeFileHandle(hFile, true); + stream = new FileStream(sfh, fileAccess, 512, false); + } + + catch + { + SafeNativeMethods.CloseHandle(hFile); + hFile = IntPtr.Zero; + throw; + } + + return stream; + } + + /// + /// Writes the given bytes to a stream. + /// + /// The stream to write data to. + /// A pointer to the data to write. + /// The number of bytes to write. + /// + /// This method is provided for performance (memory-usage) purposes. It relies on + /// the fact that FileStream provides a property for retrieving the Win32 file + /// handle. + /// + [CLSCompliant(false)] + public unsafe static void WriteToStream(FileStream output, void *pvBuffer, uint length) + { + IntPtr hFile = output.SafeFileHandle.DangerousGetHandle(); + WriteToStream(hFile, pvBuffer, length); + GC.KeepAlive(output); + } + + private unsafe static void WriteToStream(IntPtr hFile, void* pvBuffer, uint length) + { + if (hFile == NativeConstants.INVALID_HANDLE_VALUE) + { + throw new ArgumentException("output", "File is closed"); + } + + void* pvWrite = pvBuffer; + + while (length > 0) + { + uint written; + bool result = SafeNativeMethods.WriteFile(hFile, pvWrite, length, out written, IntPtr.Zero); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("WriteFile() returned false"); + } + + pvWrite = (void*)((byte*)pvWrite + written); + length -= written; + } + } + + /* + private unsafe static void WriteToStreamingFileGatherAsync(IntPtr hFile, void *[] ppvBuffers, uint[] lengths) + { + bool result = true; + uint dwResult = NativeConstants.ERROR_SUCCESS; + + long totalBytes = 0; + + // Compute total amount of bytes they want to write + for (int i = 0; i < lengths.Length; ++i) + { + totalBytes += (long)lengths[i]; + } + + // Resize the file to match how much they're writing to it + ulong newFilePointer; + + result = SafeNativeMethods.SetFilePointerEx(hFile, (ulong)totalBytes, out newFilePointer, NativeConstants.FILE_BEGIN); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("SetFilePointerEx returned false (1)"); + } + + result = SafeNativeMethods.SetEndOfFile(hFile); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("SetEndOfFile returned false"); + } + + result = SafeNativeMethods.SetFilePointerEx(hFile, 0, out newFilePointer, NativeConstants.FILE_BEGIN); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("SetFilePointerEx returned false (2)"); + } + + // Method 2 -- buffered + const uint bufferSize = 8192; // increasing this to 65536 or 131072 did not seem to affect performance for dumping out very large images (190mb), so we'll err on the side of using less memory + IntPtr pBuffer1 = IntPtr.Zero; + IntPtr pBuffer2 = IntPtr.Zero; + IntPtr hEvent = IntPtr.Zero; + ulong position = 0; + + try + { + NativeStructs.OVERLAPPED overlapped = new NativeStructs.OVERLAPPED(); + + hEvent = SafeNativeMethods.CreateEventW(IntPtr.Zero, true, true, null); + + if (hEvent == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("CreateEventW returned false"); + } + + overlapped.hEvent = hEvent; + + pBuffer1 = Memory.AllocateLarge((ulong)bufferSize); + pBuffer2 = Memory.AllocateLarge((ulong)bufferSize); + byte *pBufferBytes1 = (byte *)pBuffer1.ToPointer(); + byte *pBufferBytes2 = (byte *)pBuffer2.ToPointer(); + uint writeCursor = 0; + + for (int i = 0; i < ppvBuffers.Length; ++i) + { + uint readCursor = 0; + + while (readCursor < lengths[i]) + { + uint bytesToCopy = Math.Min(lengths[i] - readCursor, bufferSize - writeCursor); + + Memory.Copy((void *)(pBufferBytes1 + writeCursor), (void *)((byte *)ppvBuffers[i] + readCursor), + (ulong)bytesToCopy); + + writeCursor += bytesToCopy; + readCursor += bytesToCopy; + + // If we filled the write buffer, OR if this it the very last block to write + if (writeCursor == bufferSize || (i == ppvBuffers.Length - 1 && readCursor == lengths[i])) + { + // Wait for the previous I/O to finish + dwResult = SafeNativeMethods.WaitForSingleObject(hEvent, NativeConstants.INFINITE); + + if (dwResult != NativeConstants.WAIT_OBJECT_0) + { + NativeMethods.ThrowOnWin32Error("WaitForSingleObject did not return WAIT_OBJECT_0"); + } + + // Set up the new I/O + overlapped.Offset = (uint)(position & 0xffffffff); + overlapped.OffsetHigh = (uint)(position >> 32); + uint dwBytesWritten; + + result = SafeNativeMethods.WriteFile( + hFile, + pBufferBytes1, + writeCursor, + out dwBytesWritten, + ref overlapped); + + if (!result) + { + int error = Marshal.GetLastWin32Error(); + + if (error != NativeConstants.ERROR_IO_PENDING) + { + throw new Win32Exception(error, "WriteFile returned false and GetLastError() did not return ERROR_IO_PENDING"); + } + } + + // Adjust cursors and swap buffers + position += (ulong)bufferSize; + + byte *temp = pBufferBytes1; + pBufferBytes1 = pBufferBytes2; + pBufferBytes2 = temp; + writeCursor = 0; + } + } + } + + // Flush remaining data by waiting for the previous I/O to finish + dwResult = SafeNativeMethods.WaitForSingleObject(hEvent, NativeConstants.INFINITE); + + if (dwResult != NativeConstants.WAIT_OBJECT_0) + { + NativeMethods.ThrowOnWin32Error("WaitForSingleObject did not return WAIT_OBJECT_0"); + } + } + + finally + { + if (pBuffer1 != IntPtr.Zero) + { + Memory.FreeLarge(pBuffer1); + pBuffer1 = IntPtr.Zero; + } + + if (pBuffer2 != IntPtr.Zero) + { + Memory.FreeLarge(pBuffer2); + pBuffer2 = IntPtr.Zero; + } + + if (hEvent != IntPtr.Zero) + { + result = SafeNativeMethods.CloseHandle(hEvent); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("CloseHandle returned false on hEvent"); + } + } + } + } + * */ + + /// + /// Writes data to the file. This data may be scattered throughout memory, but is written contiguously + /// to the file such that ppvBuffers[n][m] is written to file location m + summation of lengths[0 through n - 1]. + /// If n is 0, then ppvBuffers[0][m] is written to file location m. + /// Or, in pseudo code: + /// for (int n = 0; n < lengths.Length; ++n) + /// { + /// for (int m = 0; m < lengths[n]; ++m) + /// { + /// WriteByte(outputHandle, ppvBuffers[n][m]); + /// } + /// } + /// + /// The stream to write to. + /// Pointers to buffers to write from. + /// The lengths of each buffer. + /// + /// ppvBuffers.Length must equal lengths.Length + /// + public unsafe static void WriteToStreamingFileGather(FileStream outputStream, void *[] ppvBuffers, uint[] lengths) + { + if (ppvBuffers.Length != lengths.Length) + { + throw new ArgumentException("ppvBuffers.Length != lengths.Length"); + } + + if (!outputStream.CanWrite) + { + throw new ArgumentException("outputStream.CanWrite == false"); + } + + IntPtr hFile = outputStream.SafeFileHandle.DangerousGetHandle(); + + for (int i = 0; i < ppvBuffers.Length; ++i) + { + WriteToStream(hFile, ppvBuffers[i], lengths[i]); + } + + GC.KeepAlive(outputStream); + } + + /// + /// Reads data from the file. This data is read contiguously from the file, but the buffers may + /// be scattered throughout memory such that ppvBuffers[n][m] is read from file location + /// m + summation of lengths[0 through n - 1]. If n is 0, then ppvBuffers[0][m] is read from + /// file location m. + /// Or, in pseudo code: + /// for (int n = 0; n < lengths.Length; ++n) + /// { + /// for (int m = 0; m < lengths[n]; ++m) + /// { + /// ppvBuffers[n][m] = ReadByte(input); + /// } + /// } + /// + /// + /// + /// + /// This method is the counter to WriteToStreamingFileGather. ppvBuffers.Length must equal lengths.Length. + public unsafe static void ReadFromStreamScatter(FileStream input, void*[] ppvBuffers, uint[] lengths) + { + if (ppvBuffers.Length != lengths.Length) + { + throw new ArgumentException("ppvBuffers.Length != lengths.Length"); + } + + for (int i = 0; i < ppvBuffers.Length; ++i) + { + if (lengths[i] > 0) + { + ReadFromStream(input, ppvBuffers[i], lengths[i]); + } + } + } + + /// + /// Reads bytes from a stream. + /// + /// + /// + /// + /// + /// This method is provided for performance (memory-usage) purposes. + /// + [CLSCompliant(false)] + public unsafe static void ReadFromStream(FileStream input, void* pvBuffer, uint length) + { + SafeFileHandle sfhFile = input.SafeFileHandle; + + if (sfhFile.IsInvalid) + { + throw new ArgumentException("input", "File is closed"); + } + + void *pvRead = pvBuffer; + + while (length > 0) + { + uint read; + bool result = SafeNativeMethods.ReadFile(sfhFile, pvRead, length, out read, IntPtr.Zero); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("ReadFile() returned false"); + } + + if (result && read == 0) + { + throw new EndOfStreamException(); + } + + pvRead = (void *)((byte *)pvRead + read); + length -= read; + } + + GC.KeepAlive(input); + } + + static FileSystem() + { + // Determine root path of where we store our persisted data + string localSettingsDir = Shell.GetVirtualPath(VirtualFolderName.UserLocalAppData, true); + string tempDirRoot = Path.Combine(localSettingsDir, "Paint.NET"); + + DirectoryInfo tempDirRootInfo = new DirectoryInfo(tempDirRoot); + + if (!tempDirRootInfo.Exists) + { + tempDirRootInfo.Create(); + } + + // Clean up old session data + string[] oldDirPaths = Directory.GetDirectories(tempDirRoot); + + foreach (string oldDirPath in oldDirPaths) + { + bool cleanUp = false; + string lockPath = Path.Combine(oldDirPath, sessionLockFileName); + + // If the file doesn't exists, then clean up + if (!cleanUp) + { + FileInfo lockFileInfo = new FileInfo(lockPath); + + if (!lockFileInfo.Exists) + { + cleanUp = true; + } + } + + // If we can delete the lock file, then definitely clean up + if (!cleanUp) + { + cleanUp = FileSystem.TryDeleteFile(lockPath); + } + + if (cleanUp) + { + string[] filesToCleanUp = Directory.GetFiles(oldDirPath, "*"); + + foreach (string fileToCleanUp in filesToCleanUp) + { + bool result1 = TryDeleteFile(fileToCleanUp); + } + + FileSystem.TryDeleteDirectory(oldDirPath); + } + } + + // Determine the directory where we will store this session's data + while (true) + { + int subDirOrdinal = random.Next(); + string subDir = Path.Combine(tempDirRoot, subDirOrdinal.ToString(CultureInfo.InvariantCulture)); + DirectoryInfo dirInfo = new DirectoryInfo(subDir); + + if (!dirInfo.Exists) + { + dirInfo.Create(); + tempDir = subDir; + EnableCompression(tempDir); + break; + } + } + + // Create our session lock cookie -- this file is locked for our process' lifetime + // If our process is terminated, the file is deleted but our session files are not + // However, if the system is abnormally shut down, neither the session lock file nor + // the session temp files are deleted. + string sessionTokenPath = Path.Combine(tempDir, sessionLockFileName); + sessionToken = FileSystem.CreateTempFile(sessionTokenPath); + + // Cleanup when the app exits. + Application.ApplicationExit += new EventHandler(Application_ApplicationExit); + } + + public static bool TryDeleteFile(string filePath) + { + return NativeMethods.DeleteFileW(filePath); + } + + public static bool TryDeleteFile(string dirPath, string fileName) + { + return Do.TryBool(() => + Do.GenerateTest(() => + Path.Combine(dirPath, fileName), + s => File.Exists(s), + s => TryDeleteFile(s), + s => { })); + } + + public static bool TryDeleteDirectory(string dirPath) + { + return NativeMethods.RemoveDirectoryW(dirPath); + } + + private static bool EnableCompression(string filePath) + { + IntPtr hFile = IntPtr.Zero; + + try + { + hFile = SafeNativeMethods.CreateFileW( + filePath, + NativeConstants.GENERIC_READ | NativeConstants.GENERIC_WRITE, + NativeConstants.FILE_SHARE_READ | NativeConstants.FILE_SHARE_WRITE | NativeConstants.FILE_SHARE_DELETE, + IntPtr.Zero, + NativeConstants.OPEN_EXISTING, + NativeConstants.FILE_FLAG_BACKUP_SEMANTICS, + IntPtr.Zero); + + if (hFile == NativeConstants.INVALID_HANDLE_VALUE) + { + int dwError = Marshal.GetLastWin32Error(); + return false; + } + + ushort cType = NativeConstants.COMPRESSION_FORMAT_DEFAULT; + uint dwBytes = 0; + bool bResult; + + unsafe + { + bResult = NativeMethods.DeviceIoControl( + hFile, + NativeConstants.FSCTL_SET_COMPRESSION, + new IntPtr(&cType), + sizeof(ushort), + IntPtr.Zero, + 0, + ref dwBytes, + IntPtr.Zero); + } + + return bResult; + } + + finally + { + if (hFile != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(hFile); + hFile = IntPtr.Zero; + } + } + } + + private static void Application_ApplicationExit(object sender, EventArgs e) + { + if (sessionToken != null) + { + sessionToken.Close(); + sessionToken = null; + } + } + + /// + /// Generates a random filename for a file in the app's per-user temporary directory. + /// + /// + /// The full path for a temporary filename. The file does not exist at the time this method returns. + /// + public static string GetTempFileName() + { + string returnPath; + + while (true) + { + int ordinal = random.Next(); + string path = Path.Combine(tempDir, ordinal.ToString(CultureInfo.InvariantCulture)); + FileInfo fileInfo = new FileInfo(path); + + if (!fileInfo.Exists) + { + returnPath = path; + break; + } + } + + return returnPath; + } + + public static string GetTempPathName(string fileName) + { + string fileName2 = Path.GetFileName(fileName); + string pathName = Path.Combine(tempDir, fileName2); + return pathName; + } + } +} diff --git a/src/SystemLayer/FontSmoothing.cs b/src/SystemLayer/FontSmoothing.cs new file mode 100644 index 0000000..22a7159 --- /dev/null +++ b/src/SystemLayer/FontSmoothing.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public enum FontSmoothing + { + /// + /// Specifies that text should be anti-aliased, but sharp. This should employ + /// the same method that the OS uses for UI text, albeit with AA forced on. + /// + /// + /// On Windows, this uses GDI for rendering. ClearType is not used. + /// + Sharp, + + /// + /// Specifies that text should be anti-aliased in a smoother manner. + /// + /// + /// On Windows, this uses GDI+ for rendering. ClearType is not used. + /// + Smooth + } +} diff --git a/src/SystemLayer/Fonts.cs b/src/SystemLayer/Fonts.cs new file mode 100644 index 0000000..333df39 --- /dev/null +++ b/src/SystemLayer/Fonts.cs @@ -0,0 +1,273 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Static methods related to font handling. + /// + public static class Fonts + { + /// + /// Determines whether a font uses the 'symbol' character set. + /// + /// + /// Symbol fonts do not typically contain glyphs that represent letters of the alphabet. + /// Instead they might contain pictures and symbols. As such, they are not useful for + /// drawing text. Which means you can't use a symbol font to write out its own name for + /// illustrative purposes (like for the font drop-down chooser). + /// + public static bool IsSymbolFont(Font font) + { + NativeStructs.LOGFONT logFont = new NativeStructs.LOGFONT(); + font.ToLogFont(logFont); + return logFont.lfCharSet == NativeConstants.SYMBOL_CHARSET; + } + + private static IntPtr CreateFontObject(Font font, bool antiAliasing) + { + NativeStructs.LOGFONT logFont = new NativeStructs.LOGFONT(); + font.ToLogFont(logFont); + + int nHeight = logFont.lfHeight; + int nWidth = logFont.lfWidth; + int nEscapement = logFont.lfEscapement; + int nOrientation = logFont.lfOrientation; + int fnWeight = logFont.lfWeight; + uint fdwItalic = logFont.lfItalic; + uint fdwUnderline = logFont.lfUnderline; + uint fdwStrikeOut = logFont.lfStrikeOut; + uint fdwCharSet = logFont.lfCharSet; + uint fdwOutputPrecision = logFont.lfOutPrecision; + uint fdwClipPrecision = logFont.lfClipPrecision; + uint fdwQuality; + + if (antiAliasing) + { + fdwQuality = NativeConstants.ANTIALIASED_QUALITY; + } + else + { + fdwQuality = NativeConstants.NONANTIALIASED_QUALITY; + } + + uint fdwPitchAndFamily = logFont.lfPitchAndFamily; + string lpszFace = logFont.lfFaceName; + + IntPtr hFont = SafeNativeMethods.CreateFontW( + nHeight, + nWidth, + nEscapement, + nOrientation, + fnWeight, + fdwItalic, + fdwUnderline, + fdwStrikeOut, + fdwCharSet, + fdwOutputPrecision, + fdwClipPrecision, + fdwQuality, + fdwPitchAndFamily, + lpszFace); + + if (hFont == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("CreateFontW returned NULL"); + } + + return hFont; + } + + /// + /// Measures text with the given graphics context, font, string, location, and anti-aliasing flag. + /// + /// The Graphics context to measure for. + /// The Font to measure with. + /// The string of text to measure. + /// Whether the font should be rendered with anti-aliasing. + public static Size MeasureString(Graphics g, Font font, string text, bool antiAliasing, FontSmoothing fontSmoothing) + { + if (fontSmoothing == FontSmoothing.Smooth && antiAliasing) + { + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.Half; + + TextRenderingHint oldTRH = g.TextRenderingHint; + g.TextRenderingHint = TextRenderingHint.AntiAlias; + + StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone(); + format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; + + SizeF sf = g.MeasureString(text, font, new PointF(0, 0), format); + sf.Height = font.GetHeight(); + + g.PixelOffsetMode = oldPOM; + g.TextRenderingHint = oldTRH; + return Size.Ceiling(sf); + } + else if (fontSmoothing == FontSmoothing.Sharp || !antiAliasing) + { + IntPtr hdc = IntPtr.Zero; + IntPtr hFont = IntPtr.Zero; + IntPtr hOldObject = IntPtr.Zero; + + try + { + hdc = g.GetHdc(); + hFont = CreateFontObject(font, antiAliasing); + hOldObject = SafeNativeMethods.SelectObject(hdc, hFont); + + NativeStructs.RECT rect = new NativeStructs.RECT(); + rect.left = 0; + rect.top = 0; + rect.right = rect.left; + rect.bottom = rect.top; + + int result = SafeNativeMethods.DrawTextW( + hdc, + text, + text.Length, + ref rect, + NativeConstants.DT_CALCRECT | + NativeConstants.DT_LEFT | + NativeConstants.DT_NOCLIP | + NativeConstants.DT_NOPREFIX | + NativeConstants.DT_SINGLELINE | + NativeConstants.DT_TOP); + + if (result == 0) + { + NativeMethods.ThrowOnWin32Error("DrawTextW returned 0"); + } + + return new Size(rect.right - rect.left, rect.bottom - rect.top); + } + + finally + { + if (hOldObject != IntPtr.Zero) + { + SafeNativeMethods.SelectObject(hdc, hOldObject); + hOldObject = IntPtr.Zero; + } + + if (hFont != IntPtr.Zero) + { + SafeNativeMethods.DeleteObject(hFont); + hFont = IntPtr.Zero; + } + + if (hdc != IntPtr.Zero) + { + g.ReleaseHdc(hdc); + hdc = IntPtr.Zero; + } + } + } + else + { + throw new InvalidEnumArgumentException("fontSmoothing = " + (int)fontSmoothing); + } + } + + /// + /// Renders text with the given graphics context, font, string, location, and anti-aliasing flag. + /// + /// The Graphics context to render to. + /// The Font to render with. + /// The string of text to draw. + /// The offset of where to start drawing (upper-left of rendering rectangle). + /// Whether the font should be rendered with anti-aliasing. + public static void DrawText(Graphics g, Font font, string text, Point pt, bool antiAliasing, FontSmoothing fontSmoothing) + { + if (fontSmoothing == FontSmoothing.Smooth && antiAliasing) + { + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.Half; + + TextRenderingHint oldTRH = g.TextRenderingHint; + g.TextRenderingHint = TextRenderingHint.AntiAlias; + + StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone(); + format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; + + g.DrawString(text, font, Brushes.Black, pt, format); + + g.PixelOffsetMode = oldPOM; + g.TextRenderingHint = oldTRH; + } + else if (fontSmoothing == FontSmoothing.Sharp || !antiAliasing) + { + IntPtr hdc = IntPtr.Zero; + IntPtr hFont = IntPtr.Zero; + IntPtr hOldObject = IntPtr.Zero; + + try + { + hdc = g.GetHdc(); + hFont = CreateFontObject(font, antiAliasing); + hOldObject = SafeNativeMethods.SelectObject(hdc, hFont); + + NativeStructs.RECT rect = new NativeStructs.RECT(); + rect.left = pt.X; + rect.top = pt.Y; + rect.right = rect.left; + rect.bottom = rect.top; + + int result = SafeNativeMethods.DrawTextW( + hdc, + text, + text.Length, + ref rect, + NativeConstants.DT_LEFT | + NativeConstants.DT_NOCLIP | + NativeConstants.DT_NOPREFIX | + NativeConstants.DT_SINGLELINE | + NativeConstants.DT_TOP); + + if (result == 0) + { + NativeMethods.ThrowOnWin32Error("DrawTextW returned 0"); + } + } + + finally + { + if (hOldObject != IntPtr.Zero) + { + SafeNativeMethods.SelectObject(hdc, hOldObject); + hOldObject = IntPtr.Zero; + } + + if (hFont != IntPtr.Zero) + { + SafeNativeMethods.DeleteObject(hFont); + hFont = IntPtr.Zero; + } + + if (hdc != IntPtr.Zero) + { + g.ReleaseHdc(hdc); + hdc = IntPtr.Zero; + } + } + } + else + { + throw new InvalidEnumArgumentException("fontSmoothing = " + (int)fontSmoothing); + } + } + } +} diff --git a/src/SystemLayer/FormEx.cs b/src/SystemLayer/FormEx.cs new file mode 100644 index 0000000..b05155c --- /dev/null +++ b/src/SystemLayer/FormEx.cs @@ -0,0 +1,245 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Provides special methods and properties that must be implemented in a + /// system-specific manner. It is implemented as an object that is hosted + /// by the PdnBaseForm class. This way there is no inheritance hierarchy + /// extending into the SystemLayer assembly. + /// + public sealed class FormEx + : Control + { + private Form host; + private RealParentWndProcDelegate realParentWndProc; + private bool forceActiveTitleBar = false; + + /// + /// Gets or sets the titlebar rendering behavior for when the form is deactivated. + /// + /// + /// If this property is false, the titlebar will be rendered in a different color when the form + /// is inactive as opposed to active. If this property is true, it will always render with the + /// active style. If the whole application is deactivated, the title bar will still be drawn in + /// an inactive state. + /// + public bool ForceActiveTitleBar + { + get + { + return this.forceActiveTitleBar; + } + + set + { + this.forceActiveTitleBar = value; + } + } + + public FormEx(Form host, RealParentWndProcDelegate realParentWndProc) + { + this.host = host; + this.realParentWndProc = realParentWndProc; + } + + public class ProcessCmdKeyEventArgs + : EventArgs + { + private bool handled; + public bool Handled + { + get + { + return this.handled; + } + + set + { + this.handled = value; + } + } + + private Keys keyData; + public Keys KeyData + { + get + { + return this.keyData; + } + } + + public ProcessCmdKeyEventArgs(Keys keyData, bool handled) + { + this.keyData = keyData; + this.handled = handled; + } + } + + public event EventHandler ProcessCmdKeyRelay; + + public bool RelayProcessCmdKey(Keys keyData) + { + bool handled = false; + + if (ProcessCmdKeyRelay != null) + { + ProcessCmdKeyEventArgs e = new ProcessCmdKeyEventArgs(keyData, false); + ProcessCmdKeyRelay(this, e); + handled = e.Handled; + } + + return handled; + } + + internal static FormEx FindFormEx(Form host) + { + if (host != null) + { + Control.ControlCollection controls = host.Controls; + + for (int i = 0; i < controls.Count; ++i) + { + FormEx formEx = controls[i] as FormEx; + + if (formEx != null) + { + return formEx; + } + } + } + + return null; + } + + private int ignoreNcActivate = 0; + + /// + /// Manages some special handling of window messages. + /// + /// + /// true if the message was handled, false if the caller should handle the message. + public bool HandleParentWndProc(ref Message m) + { + bool returnVal = true; + + switch (m.Msg) + { + case NativeConstants.WM_NCPAINT: + goto default; + + case NativeConstants.WM_NCACTIVATE: + if (this.forceActiveTitleBar && m.WParam == IntPtr.Zero) + { + if (ignoreNcActivate > 0) + { + --ignoreNcActivate; + goto default; + } + else if (Form.ActiveForm != this.host || // Gets rid of: if you have the form active, then click on the desktop --> desktop refreshes + !this.host.Visible) // Gets rid of: desktop refresh on exit + { + goto default; + } + else + { + // Only 'lock' for the topmost form in the application. Otherwise you get the whole system + // refreshing (i.e. the dreaded "repaint the whole desktop 5 times" glitch) when you do things + // like minimize the window + // And only lock if we aren't minimized. Otherwise the desktop refreshes. + bool locked = false; + if (this.host.Owner == null && + this.host.WindowState != FormWindowState.Minimized) + { + //UI.SetControlRedraw(this.host, false); + locked = true; + } + + this.realParentWndProc(ref m); + + SafeNativeMethods.SendMessageW(this.host.Handle, NativeConstants.WM_NCACTIVATE, + new IntPtr(1), IntPtr.Zero); + + if (locked) + { + //UI.SetControlRedraw(this.host, true); + //this.host.Invalidate(true); + } + + break; + } + } + else + { + goto default; + } + + case NativeConstants.WM_ACTIVATE: + goto default; + + case NativeConstants.WM_ACTIVATEAPP: + this.realParentWndProc(ref m); + + // Check if the app is being deactivated + if (this.forceActiveTitleBar && m.WParam == IntPtr.Zero) + { + // If so, put our titlebar in the inactive state + SafeNativeMethods.PostMessageW(this.host.Handle, NativeConstants.WM_NCACTIVATE, + IntPtr.Zero, IntPtr.Zero); + + ++ignoreNcActivate; + } + + if (m.WParam == new IntPtr(1)) + { + foreach (Form childForm in this.host.OwnedForms) + { + FormEx childFormEx = FindFormEx(childForm); + + if (childFormEx != null) + { + if (childFormEx.ForceActiveTitleBar && childForm.IsHandleCreated) + { + SafeNativeMethods.PostMessageW(childForm.Handle, NativeConstants.WM_NCACTIVATE, + new IntPtr(1), IntPtr.Zero); + } + } + } + + FormEx ownerEx = FindFormEx(this.host.Owner); + if (ownerEx != null) + { + if (ownerEx.ForceActiveTitleBar && this.host.Owner.IsHandleCreated) + { + SafeNativeMethods.PostMessageW(this.host.Owner.Handle, NativeConstants.WM_NCACTIVATE, + new IntPtr(1), IntPtr.Zero); + } + } + } + + break; + + default: + returnVal = false; + break; + } + + GC.KeepAlive(this.host); + return returnVal; + } + } +} diff --git a/src/SystemLayer/GpcWrapper/NativeConstants.cs b/src/SystemLayer/GpcWrapper/NativeConstants.cs new file mode 100644 index 0000000..da2099f --- /dev/null +++ b/src/SystemLayer/GpcWrapper/NativeConstants.cs @@ -0,0 +1,24 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer.GpcWrapper +{ + internal static class NativeConstants + { + public enum gpc_op + { + GPC_DIFF = 0, + GPC_INT = 1, + GPC_XOR = 2, + GPC_UNION = 3 + } + } +} diff --git a/src/SystemLayer/GpcWrapper/NativeMethods.cs b/src/SystemLayer/GpcWrapper/NativeMethods.cs new file mode 100644 index 0000000..df9fb5a --- /dev/null +++ b/src/SystemLayer/GpcWrapper/NativeMethods.cs @@ -0,0 +1,80 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer.GpcWrapper +{ + internal static class NativeMethods + { + private static class X64 + { + [DllImport("ShellExtension_x64.dll")] + public static extern void gpc_polygon_clip( + [In] NativeConstants.gpc_op set_operation, + [In] ref NativeStructs.gpc_polygon subject_polygon, + [In] ref NativeStructs.gpc_polygon clip_polygon, + [In, Out] ref NativeStructs.gpc_polygon result_polygon); + + [DllImport("ShellExtension_x64.dll")] + public static extern void gpc_free_polygon([In] ref NativeStructs.gpc_polygon polygon); + } + + private static class X86 + { + [DllImport("ShellExtension_x86.dll")] + public static extern void gpc_polygon_clip( + [In] NativeConstants.gpc_op set_operation, + [In] ref NativeStructs.gpc_polygon subject_polygon, + [In] ref NativeStructs.gpc_polygon clip_polygon, + [In, Out] ref NativeStructs.gpc_polygon result_polygon); + + [DllImport("ShellExtension_x86.dll")] + public static extern void gpc_free_polygon([In] ref NativeStructs.gpc_polygon polygon); + } + + public static void gpc_polygon_clip( + [In] NativeConstants.gpc_op set_operation, + [In] ref NativeStructs.gpc_polygon subject_polygon, + [In] ref NativeStructs.gpc_polygon clip_polygon, + [In, Out] ref NativeStructs.gpc_polygon result_polygon) + { + if (Processor.Architecture == ProcessorArchitecture.X64) + { + X64.gpc_polygon_clip(set_operation, ref subject_polygon, ref clip_polygon, ref result_polygon); + } + else if (Processor.Architecture == ProcessorArchitecture.X86) + { + X86.gpc_polygon_clip(set_operation, ref subject_polygon, ref clip_polygon, ref result_polygon); + } + else + { + throw new InvalidOperationException(); + } + } + + public static void gpc_free_polygon([In] ref NativeStructs.gpc_polygon polygon) + { + if (Processor.Architecture == ProcessorArchitecture.X64) + { + X64.gpc_free_polygon(ref polygon); + } + else if (Processor.Architecture == ProcessorArchitecture.X86) + { + X86.gpc_free_polygon(ref polygon); + } + else + { + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/SystemLayer/GpcWrapper/NativeStructs.cs b/src/SystemLayer/GpcWrapper/NativeStructs.cs new file mode 100644 index 0000000..c12cdb5 --- /dev/null +++ b/src/SystemLayer/GpcWrapper/NativeStructs.cs @@ -0,0 +1,41 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace PaintDotNet.SystemLayer.GpcWrapper +{ + internal static class NativeStructs + { + [StructLayout(LayoutKind.Sequential)] + public struct gpc_vertex /* Polygon vertex structure */ + { + public double x; /* Vertex x component */ + public double y; /* vertex y component */ + } + + [StructLayout(LayoutKind.Sequential)] + public struct gpc_vertex_list /* Vertex list structure */ + { + public int num_vertices; /* Number of vertices in list */ + public IntPtr vertex; /* Vertex array pointer */ + } + + [StructLayout(LayoutKind.Sequential)] + public struct gpc_polygon /* Polygon set structure */ + { + public int num_contours; /* Number of contours in polygon */ + public IntPtr hole; /* Hole / external contour flags */ + public IntPtr contour; /* Contour array pointer */ + } + } +} diff --git a/src/SystemLayer/GpcWrapper/Polygon.cs b/src/SystemLayer/GpcWrapper/Polygon.cs new file mode 100644 index 0000000..07583ef --- /dev/null +++ b/src/SystemLayer/GpcWrapper/Polygon.cs @@ -0,0 +1,263 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer.GpcWrapper +{ + internal sealed class Polygon + { + public int NofContours; + public bool[] ContourIsHole; + public VertexList[] Contour; + + public Polygon() + { + } + + // path should contain only polylines ( use Flatten ) + // furthermore the constructor assumes that all Subpathes of path except the first one are holes + public Polygon(GraphicsPath path) + { + byte[] pathTypes = path.PathTypes; + + NofContours = 0; + foreach (byte b in pathTypes) + { + if ((b & ((byte)PathPointType.CloseSubpath)) != 0) + { + NofContours++; + } + } + + ContourIsHole = new bool[NofContours]; + Contour = new VertexList[NofContours]; + + for (int i = 0; i < NofContours; i++) + { + ContourIsHole[i] = (i == 0); + } + + int contourNr = 0; + + List contour = new List(); + + PointF[] pathPoints = path.PathPoints; + + for (int i = 0; i < pathPoints.Length; i++) + { + contour.Add(pathPoints[i]); + + if ((pathTypes[i] & ((byte)PathPointType.CloseSubpath)) != 0) + { + PointF[] pointArray = contour.ToArray(); + VertexList vl = new VertexList(pointArray); + Contour[contourNr++] = vl; + contour.Clear(); + } + } + } + + public GraphicsPath ToGraphicsPath() + { + GraphicsPath path = new GraphicsPath(); + + for (int i = 0; i < NofContours; i++) + { + PointF[] points = Contour[i].ToPoints(); + + if (ContourIsHole[i]) + { + Array.Reverse(points); + } + + path.AddPolygon(points); + } + + return path; + } + + public Polygon Clip(CombineMode operation, Polygon polygon) + { + return Clip(operation, this, polygon); + } + + public static bool Check(CombineMode combineMode) + { + switch (combineMode) + { + case CombineMode.Exclude: + case CombineMode.Intersect: + case CombineMode.Union: + case CombineMode.Xor: + return true; + + case CombineMode.Complement: + case CombineMode.Replace: + default: + return false; + } + } + + public static void Validate(CombineMode combineMode) + { + if (Check(combineMode)) + { + return; + } + else + { + throw new InvalidEnumArgumentException(); + } + } + + private static NativeConstants.gpc_op Convert(CombineMode combineMode) + { + switch (combineMode) + { + case CombineMode.Exclude: + return NativeConstants.gpc_op.GPC_DIFF; + + case CombineMode.Intersect: + return NativeConstants.gpc_op.GPC_INT; + + case CombineMode.Union: + return NativeConstants.gpc_op.GPC_UNION; + + case CombineMode.Xor: + return NativeConstants.gpc_op.GPC_XOR; + + case CombineMode.Complement: + case CombineMode.Replace: + default: + throw new InvalidEnumArgumentException(); + } + } + + public static Polygon Clip(CombineMode clipMode, Polygon subject_polygon, Polygon clip_polygon) + { + Validate(clipMode); + + NativeConstants.gpc_op gpcOp = Convert(clipMode); + + NativeStructs.gpc_polygon gpc_polygon = new NativeStructs.gpc_polygon(); + NativeStructs.gpc_polygon gpc_subject_polygon = PolygonTo_gpc_polygon(subject_polygon); + NativeStructs.gpc_polygon gpc_clip_polygon = PolygonTo_gpc_polygon(clip_polygon); + + NativeMethods.gpc_polygon_clip(gpcOp, ref gpc_subject_polygon, ref gpc_clip_polygon, ref gpc_polygon); + + Polygon polygon = gpc_polygon_ToPolygon(gpc_polygon); + + Free_gpc_polygon(gpc_subject_polygon); + Free_gpc_polygon(gpc_clip_polygon); + NativeMethods.gpc_free_polygon(ref gpc_polygon); + + return polygon; + } + + private static NativeStructs.gpc_polygon PolygonTo_gpc_polygon(Polygon polygon) + { + NativeStructs.gpc_polygon gpc_pol = new NativeStructs.gpc_polygon(); + gpc_pol.num_contours = polygon.NofContours; + + int[] hole = new int[polygon.NofContours]; + + for (int i = 0; i < polygon.NofContours; i++) + { + hole[i] = (polygon.ContourIsHole[i] ? 1 : 0); + } + + gpc_pol.hole = Marshal.AllocCoTaskMem(polygon.NofContours * Marshal.SizeOf(typeof(int) /*hole[0]*/)); + + if (polygon.NofContours > 0) + { + Marshal.Copy(hole, 0, gpc_pol.hole, polygon.NofContours); + } + + gpc_pol.contour = Marshal.AllocCoTaskMem(polygon.NofContours * Marshal.SizeOf(typeof(NativeStructs.gpc_vertex_list))); + IntPtr ptr = gpc_pol.contour; + for (int i = 0; i < polygon.NofContours; i++) + { + NativeStructs.gpc_vertex_list gpc_vtx_list = new NativeStructs.gpc_vertex_list(); + gpc_vtx_list.num_vertices = polygon.Contour[i].NofVertices; + gpc_vtx_list.vertex = Marshal.AllocCoTaskMem(polygon.Contour[i].NofVertices * Marshal.SizeOf(typeof(NativeStructs.gpc_vertex))); + IntPtr ptr2 = gpc_vtx_list.vertex; + for (int j = 0; j < polygon.Contour[i].NofVertices; j++) + { + NativeStructs.gpc_vertex gpc_vtx = new NativeStructs.gpc_vertex(); + gpc_vtx.x = polygon.Contour[i].Vertex[j].X; + gpc_vtx.y = polygon.Contour[i].Vertex[j].Y; + Marshal.StructureToPtr(gpc_vtx, ptr2, false); + ptr2 = (IntPtr)(((int)ptr2) + Marshal.SizeOf(gpc_vtx)); + } + Marshal.StructureToPtr(gpc_vtx_list, ptr, false); + ptr = (IntPtr)(((int)ptr) + Marshal.SizeOf(gpc_vtx_list)); + } + + return gpc_pol; + } + + private static Polygon gpc_polygon_ToPolygon(NativeStructs.gpc_polygon gpc_polygon) + { + Polygon polygon = new Polygon(); + + polygon.NofContours = gpc_polygon.num_contours; + polygon.ContourIsHole = new bool[polygon.NofContours]; + polygon.Contour = new VertexList[polygon.NofContours]; + short[] holeShort = new short[polygon.NofContours]; + IntPtr ptr = gpc_polygon.hole; + + if (polygon.NofContours > 0) + { + Marshal.Copy(gpc_polygon.hole, holeShort, 0, polygon.NofContours); + } + + for (int i = 0; i < polygon.NofContours; i++) + polygon.ContourIsHole[i] = (holeShort[i] != 0); + + ptr = gpc_polygon.contour; + for (int i = 0; i < polygon.NofContours; i++) + { + NativeStructs.gpc_vertex_list gpc_vtx_list = (NativeStructs.gpc_vertex_list)Marshal.PtrToStructure(ptr, typeof(NativeStructs.gpc_vertex_list)); + polygon.Contour[i] = new VertexList(); + polygon.Contour[i].NofVertices = gpc_vtx_list.num_vertices; + polygon.Contour[i].Vertex = new Vertex[polygon.Contour[i].NofVertices]; + IntPtr ptr2 = gpc_vtx_list.vertex; + for (int j = 0; j < polygon.Contour[i].NofVertices; j++) + { + NativeStructs.gpc_vertex gpc_vtx = (NativeStructs.gpc_vertex)Marshal.PtrToStructure(ptr2, typeof(NativeStructs.gpc_vertex)); + polygon.Contour[i].Vertex[j].X = gpc_vtx.x; + polygon.Contour[i].Vertex[j].Y = gpc_vtx.y; + + ptr2 = (IntPtr)(((int)ptr2) + Marshal.SizeOf(gpc_vtx)); + } + ptr = (IntPtr)(((int)ptr) + Marshal.SizeOf(gpc_vtx_list)); + } + + return polygon; + } + + private static void Free_gpc_polygon(NativeStructs.gpc_polygon gpc_pol) + { + Marshal.FreeCoTaskMem(gpc_pol.hole); + IntPtr ptr = gpc_pol.contour; + for (int i = 0; i < gpc_pol.num_contours; i++) + { + NativeStructs.gpc_vertex_list gpc_vtx_list = (NativeStructs.gpc_vertex_list)Marshal.PtrToStructure(ptr, typeof(NativeStructs.gpc_vertex_list)); + Marshal.FreeCoTaskMem(gpc_vtx_list.vertex); + ptr = (IntPtr)(((int)ptr) + Marshal.SizeOf(gpc_vtx_list)); + } + } + } +} diff --git a/src/SystemLayer/GpcWrapper/Vertex.cs b/src/SystemLayer/GpcWrapper/Vertex.cs new file mode 100644 index 0000000..5f58fb7 --- /dev/null +++ b/src/SystemLayer/GpcWrapper/Vertex.cs @@ -0,0 +1,25 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer.GpcWrapper +{ + internal struct Vertex + { + public double X; + public double Y; + + public Vertex(double x, double y) + { + this.X = x; + this.Y = y; + } + } +} diff --git a/src/SystemLayer/GpcWrapper/VertexList.cs b/src/SystemLayer/GpcWrapper/VertexList.cs new file mode 100644 index 0000000..4f1699c --- /dev/null +++ b/src/SystemLayer/GpcWrapper/VertexList.cs @@ -0,0 +1,97 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Collections.Generic; + +namespace PaintDotNet.SystemLayer.GpcWrapper +{ + internal sealed class VertexList + { + public int NofVertices; + public Vertex[] Vertex; + + public VertexList() + { + } + + public VertexList(Vertex[] v) + : this(v, false) + { + } + + public VertexList(Vertex[] v, bool takeOwnership) + { + if (takeOwnership) + { + this.Vertex = v; + this.NofVertices = v.Length; + } + else + { + this.Vertex = (Vertex[])v.Clone(); + this.NofVertices = v.Length; + } + } + + public VertexList(PointF[] p) + { + NofVertices = p.Length; + Vertex = new Vertex[NofVertices]; + for (int i = 0; i < p.Length; i++) + Vertex[i] = new Vertex((double)p[i].X, (double)p[i].Y); + } + + public GraphicsPath ToGraphicsPath() + { + GraphicsPath graphicsPath = new GraphicsPath(); + graphicsPath.AddLines(ToPoints()); + return graphicsPath; + } + + public PointF[] ToPoints() + { + PointF[] vertexArray = new PointF[NofVertices]; + for (int i = 0; i < NofVertices; i++) + { + vertexArray[i] = new PointF((float)Vertex[i].X, (float)Vertex[i].Y); + } + return vertexArray; + } + + public GraphicsPath TristripToGraphicsPath() + { + GraphicsPath graphicsPath = new GraphicsPath(); + + for (int i = 0; i < NofVertices - 2; i++) + { + graphicsPath.AddPolygon(new PointF[3]{ new PointF( (float)Vertex[i].X, (float)Vertex[i].Y ), + new PointF( (float)Vertex[i+1].X, (float)Vertex[i+1].Y ), + new PointF( (float)Vertex[i+2].X, (float)Vertex[i+2].Y ) }); + } + + return graphicsPath; + } + + public override string ToString() + { + string s = "Polygon with " + NofVertices + " vertices: "; + + for (int i = 0; i < NofVertices; i++) + { + s += Vertex[i].ToString(); + if (i != NofVertices - 1) + s += ","; + } + return s; + } + } +} diff --git a/src/SystemLayer/IFileDialog.cs b/src/SystemLayer/IFileDialog.cs new file mode 100644 index 0000000..b9e1562 --- /dev/null +++ b/src/SystemLayer/IFileDialog.cs @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public interface IFileDialog + : IDisposable + { + bool CheckPathExists + { + get; + set; + } + + bool DereferenceLinks + { + get; + set; + } + + string Filter + { + get; + set; + } + + int FilterIndex + { + get; + set; + } + + string InitialDirectory + { + get; + set; + } + + string Title + { + set; + } + + /// + /// Shows the common file dialog. + /// + /// The owning window for this dialog. + /// + /// A reference to an object that implements the IFileDialogUICallbacks interface. This + /// may not be null. + DialogResult ShowDialog(IWin32Window owner, IFileDialogUICallbacks uiCallbacks); + } +} diff --git a/src/SystemLayer/IFileDialogUICallbacks.cs b/src/SystemLayer/IFileDialogUICallbacks.cs new file mode 100644 index 0000000..510de0e --- /dev/null +++ b/src/SystemLayer/IFileDialogUICallbacks.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public interface IFileDialogUICallbacks + { + FileOverwriteAction ShowOverwritePrompt(IWin32Window owner, string pathName); + bool ShowError(IWin32Window owner, string pathName, Exception ex); // return true if error dialog shown, or false to cause caller to re-throw + IFileTransferProgressEvents CreateFileTransferProgressEvents(); + } +} diff --git a/src/SystemLayer/IFileOpenDialog.cs b/src/SystemLayer/IFileOpenDialog.cs new file mode 100644 index 0000000..8dfa73c --- /dev/null +++ b/src/SystemLayer/IFileOpenDialog.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public interface IFileOpenDialog + : IFileDialog + { + bool CheckFileExists + { + get; + set; + } + + bool Multiselect + { + get; + set; + } + + string[] FileNames + { + get; + } + } +} diff --git a/src/SystemLayer/IFileSaveDialog.cs b/src/SystemLayer/IFileSaveDialog.cs new file mode 100644 index 0000000..1b98355 --- /dev/null +++ b/src/SystemLayer/IFileSaveDialog.cs @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public interface IFileSaveDialog + : IFileDialog + { + bool AddExtension + { + get; + set; + } + + bool OverwritePrompt + { + get; + set; + } + + string FileName + { + get; + set; + } + } +} diff --git a/src/SystemLayer/IFileTransferProgressEvents.cs b/src/SystemLayer/IFileTransferProgressEvents.cs new file mode 100644 index 0000000..8116e16 --- /dev/null +++ b/src/SystemLayer/IFileTransferProgressEvents.cs @@ -0,0 +1,20 @@ +//////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using PaintDotNet; + +namespace PaintDotNet.SystemLayer +{ + public interface IFileTransferProgressEvents + : IProgressEvents + { + } +} + diff --git a/src/SystemLayer/IInkHooks.cs b/src/SystemLayer/IInkHooks.cs new file mode 100644 index 0000000..aa22dc8 --- /dev/null +++ b/src/SystemLayer/IInkHooks.cs @@ -0,0 +1,24 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public interface IInkHooks + { + void PerformDocumentMouseMove(MouseButtons button, int clicks, float x, float y, int delta, float pressure); + void PerformDocumentMouseUp(MouseButtons button, int clicks, float x, float y, int delta, float pressure); + void PerformDocumentMouseDown(MouseButtons button, int clicks, float x, float y, int delta, float pressure); + System.Drawing.Graphics CreateGraphics(); + PointF ScreenToDocument(PointF pointF); + } +} diff --git a/src/SystemLayer/IWiaInterface.cs b/src/SystemLayer/IWiaInterface.cs new file mode 100644 index 0000000..fb80b61 --- /dev/null +++ b/src/SystemLayer/IWiaInterface.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal interface IWiaInterface + { + bool IsComponentAvailable + { + get; + } + + bool CanPrint + { + get; + } + + bool CanScan + { + get; + } + + void Print(Control owner, string fileName); + + ScanResult Scan(Control owner, string fileName); + } +} diff --git a/src/SystemLayer/Ink.cs b/src/SystemLayer/Ink.cs new file mode 100644 index 0000000..6daaddc --- /dev/null +++ b/src/SystemLayer/Ink.cs @@ -0,0 +1,176 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +//#define ENABLE_INK_IN_DEBUG_BUILDS + +//#if WIN64 +//#define NOINK +//#endif + +using System; +using System.Collections; +using System.Reflection; +using System.Windows.Forms; +using PaintDotNet; + +namespace PaintDotNet.SystemLayer +{ + public static class Ink + { + private static bool isInkAvailable = false; + private static bool isInkAvailableInit = false; + + /// + /// Adapts an IInkHook instance to work with the IStylusReaderHooks interface. + /// + private sealed class HookAdapter + : IStylusReaderHooks + { + private IInkHooks subject; + + public HookAdapter(IInkHooks subject) + { + this.subject = subject; + } + + public System.Drawing.Graphics CreateGraphics() + { + return subject.CreateGraphics(); + } + + public void PerformDocumentMouseMove(System.Windows.Forms.MouseButtons button, int clicks, float x, float y, int delta, float pressure) + { + subject.PerformDocumentMouseMove(button, clicks, x, y, delta, pressure); + } + + public System.Drawing.PointF ScreenToDocument(System.Drawing.PointF pointF) + { + return subject.ScreenToDocument(pointF); + } + + public void PerformDocumentMouseUp(System.Windows.Forms.MouseButtons button, int clicks, float x, float y, int delta, float pressure) + { + subject.PerformDocumentMouseUp(button, clicks, x, y, delta, pressure); + } + + public void PerformDocumentMouseDown(System.Windows.Forms.MouseButtons button, int clicks, float x, float y, int delta, float pressure) + { + subject.PerformDocumentMouseDown(button, clicks, x, y, delta, pressure); + } + } + + /// + /// Gets a value indicating whether Ink is available. + /// + /// true if Ink is available, or false if it is not + /// + /// If ink is not available, then the other static methods or properties of this class will not + /// be usable and will throw NotSupportedException. + /// + public static bool IsAvailable() + { + if (!isInkAvailableInit) + { + // For debug builds we try to load the assembly. This enables us to work with ink + // if we have the Tablet PC SDK installed. + // For retail builds we only enable ink on true blue Tablet PC's. Calling GetSystemMetrics + // is much faster than attempting to load an assembly. +#if NOINK + isInkAvailable = false; +#elif DEBUG + #if ENABLE_INK_IN_DEBUG_BUILDS + try + { + Assembly inkAssembly = Assembly.Load("Microsoft.Ink, Version=1.7.2600.2180, Culture=\"\", PublicKeyToken=31bf3856ad364e35"); + isInkAvailable = true; + } + + catch (Exception) + { + isInkAvailable = false; + } + #else + isInkAvailable = false; + #endif +#else + if (SafeNativeMethods.GetSystemMetrics(NativeConstants.SM_TABLETPC) != 0) + { + // Only enable ink if the system states it is a Tablet PC. + // In other words, don't incur the performance penalty and a few other + // weird things for regular PC's that just happen to have the SDK + // installed, or that have something like Vista Ultimate. + try + { + Assembly inkAssembly = Assembly.Load("Microsoft.Ink, Version=1.7.2600.2180, Culture=\"\", PublicKeyToken=31bf3856ad364e35"); + isInkAvailable = true; + } + + catch (Exception) + { + isInkAvailable = false; + } + } + else + { + isInkAvailable = false; + } +#endif + + isInkAvailableInit = true; + } + + return isInkAvailable; + } + + private static void HookInkImpl(IInkHooks subject, Control control) + { + HookAdapter adapter = new HookAdapter(subject); + control.CreateControl(); + StylusReader.HookStylus(adapter, control); + } + + /// + /// Hooks Ink support in to a control. + /// + /// + /// + /// IsAvailable() returned false + /// Ink support will be automatically unhooked when the control's Disposed event is raised. + public static void HookInk(IInkHooks subject, Control control) + { + if (!IsAvailable()) + { + throw new NotSupportedException("Ink is not available"); + } + + HookInkImpl(subject, control); + } + + private static void UnhookInkImpl(Control control) + { + StylusReader.UnhookStylus(control); + } + + /// + /// Unhooks Ink support from a control. + /// + /// + /// + /// IsAvailable() returned false + public static void UnhookInk(Control control) + { + if (!IsAvailable()) + { + throw new NotSupportedException("Ink is not available"); + } + + UnhookInkImpl(control); + } + } +} diff --git a/src/SystemLayer/Memory.cs b/src/SystemLayer/Memory.cs new file mode 100644 index 0000000..c58b1f1 --- /dev/null +++ b/src/SystemLayer/Memory.cs @@ -0,0 +1,355 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +//#define REPORTLEAKS + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Forms; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Contains methods for allocating, freeing, and performing operations on memory + /// that is fixed (pinned) in memory. + /// + [CLSCompliant(false)] + public unsafe static class Memory + { + private static IntPtr hHeap; + + static Memory() + { + hHeap = SafeNativeMethods.HeapCreate(0, IntPtr.Zero, IntPtr.Zero); + + uint info = 2; + + try + { + // Enable the low-fragmentation heap (LFH) + SafeNativeMethods.HeapSetInformation(hHeap, + NativeConstants.HeapCompatibilityInformation, + (void *)&info, + sizeof(uint)); + } + + catch (Exception) + { + // If that method isn't available, like on Win2K, don't worry about it. + } + + Application.ApplicationExit += new EventHandler(Application_ApplicationExit); + } + + /// + /// Gets the total amount of physical memory (RAM) in the system. + /// + public static ulong TotalPhysicalBytes + { + get + { + NativeStructs.MEMORYSTATUSEX mse = new NativeStructs.MEMORYSTATUSEX(); + mse.dwLength = (uint)sizeof(NativeStructs.MEMORYSTATUSEX); + + bool result = NativeMethods.GlobalMemoryStatusEx(ref mse); + + if (!result) + { + NativeMethods.ThrowOnWin32Error("GlobalMemoryStatusEx"); + } + + return mse.ullTotalPhys; + } + } + + private static void DestroyHeap() + { + IntPtr hHeap2 = hHeap; + hHeap = IntPtr.Zero; + SafeNativeMethods.HeapDestroy(hHeap2); + } + + private static void Application_ApplicationExit(object sender, EventArgs e) + { + DestroyHeap(); + } + + /// + /// Allocates a block of memory at least as large as the amount requested. + /// + /// The number of bytes you want to allocate. + /// A pointer to a block of memory at least as large as bytes. + /// Thrown if the memory manager could not fulfill the request for a memory block at least as large as bytes. + public static IntPtr Allocate(ulong bytes) + { + if (hHeap == IntPtr.Zero) + { + throw new InvalidOperationException("heap has already been destroyed"); + } + else + { + IntPtr block = SafeNativeMethods.HeapAlloc(hHeap, 0, new UIntPtr(bytes)); + + if (block == IntPtr.Zero) + { + throw new OutOfMemoryException("HeapAlloc returned a null pointer"); + } + + if (bytes > 0) + { + GC.AddMemoryPressure((long)bytes); + } + + return block; + } + } + + /// + /// Allocates a block of memory at least as large as the amount requested. + /// + /// The number of bytes you want to allocate. + /// A pointer to a block of memory at least as large as bytes + /// + /// This method uses an alternate method for allocating memory (VirtualAlloc in Windows). The allocation + /// granularity is the page size of the system (usually 4K). Blocks allocated with this method may also + /// be protected using the ProtectBlock method. + /// + public static IntPtr AllocateLarge(ulong bytes) + { + IntPtr block = SafeNativeMethods.VirtualAlloc(IntPtr.Zero, new UIntPtr(bytes), + NativeConstants.MEM_COMMIT, NativeConstants.PAGE_READWRITE); + + if (block == IntPtr.Zero) + { + throw new OutOfMemoryException("VirtualAlloc returned a null pointer"); + } + + if (bytes > 0) + { + GC.AddMemoryPressure((long)bytes); + } + + return block; + } + + /// + /// Allocates a bitmap of the given height and width. Pixel data may be read/written directly, + /// and it may be drawn to the screen using PdnGraphics.DrawBitmap(). + /// + /// The width of the bitmap to allocate. + /// The height of the bitmap to allocate. + /// Receives a handle to the bitmap. + /// A pointer to the bitmap's pixel data. + /// + /// The following invariants may be useful for implementors: + /// * The bitmap is always 32-bits per pixel, BGRA. + /// * Stride for the bitmap is always width * 4. + /// * The upper-left pixel of the bitmap (0,0) is located at the first memory location pointed to by the returned pointer. + /// * The bitmap is top-down ("memory correct" ordering). + /// * The 'handle' may be any type of data you want, but must be unique for the lifetime of the bitmap, and must not be IntPtr.Zero. + /// * The handle's value must be understanded by PdnGraphics.DrawBitmap(). + /// * The bitmap is always modified by directly reading and writing to the memory pointed to by the return value. + /// * PdnGraphics.DrawBitmap() must always render from this memory location (i.e. it must treat the memory as 'volatile') + /// + public static IntPtr AllocateBitmap(int width, int height, out IntPtr handle) + { + NativeStructs.BITMAPINFO bmi = new NativeStructs.BITMAPINFO(); + bmi.bmiHeader.biSize = (uint)sizeof(NativeStructs.BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = NativeConstants.BI_RGB; + bmi.bmiHeader.biSizeImage = 0; + bmi.bmiHeader.biXPelsPerMeter = 96; + bmi.bmiHeader.biYPelsPerMeter = 96; + bmi.bmiHeader.biClrUsed = 0; + bmi.bmiHeader.biClrImportant = 0; + + IntPtr pvBits; + IntPtr hBitmap = SafeNativeMethods.CreateDIBSection( + IntPtr.Zero, + ref bmi, + NativeConstants.DIB_RGB_COLORS, + out pvBits, + IntPtr.Zero, + 0); + + if (hBitmap == IntPtr.Zero) + { + throw new OutOfMemoryException("CreateDIBSection returned NULL (" + Marshal.GetLastWin32Error().ToString() + ") while attempting to allocate " + width + "x" + height + " bitmap"); + } + + handle = hBitmap; + long bytes = (long)width * (long)height * 4; + + if (bytes > 0) + { + GC.AddMemoryPressure(bytes); + } + + return pvBits; + } + + /// + /// Frees a bitmap previously allocated with AllocateBitmap. + /// + /// The handle that was returned from a previous call to AllocateBitmap. + /// The width of the bitmap, as specified in the original call to AllocateBitmap. + /// The height of the bitmap, as specified in the original call to AllocateBitmap. + public static void FreeBitmap(IntPtr handle, int width, int height) + { + long bytes = (long)width * (long)height * 4; + + bool bResult = SafeNativeMethods.DeleteObject(handle); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("DeleteObject returned false"); + } + + if (bytes > 0) + { + GC.RemoveMemoryPressure(bytes); + } + } + + /// + /// Frees a block of memory previously allocated with Allocate(). + /// + /// The block to free. + /// There was an error freeing the block. + public static void Free(IntPtr block) + { + if (Memory.hHeap != IntPtr.Zero) + { + long bytes = (long)SafeNativeMethods.HeapSize(hHeap, 0, block); + + bool result = SafeNativeMethods.HeapFree(hHeap, 0, block); + + if (!result) + { + int error = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); + throw new InvalidOperationException("HeapFree returned an error: " + error.ToString()); + } + + if (bytes > 0) + { + GC.RemoveMemoryPressure(bytes); + } + } + else + { +#if REPORTLEAKS + throw new InvalidOperationException("memory leak! check the debug output for more info, and http://blogs.msdn.com/ricom/archive/2004/12/10/279612.aspx to track it down"); +#endif + } + } + + /// + /// Frees a block of memory previous allocated with AllocateLarge(). + /// + /// The block to free. + /// The size of the block. + public static void FreeLarge(IntPtr block, ulong bytes) + { + bool result = SafeNativeMethods.VirtualFree(block, UIntPtr.Zero, NativeConstants.MEM_RELEASE); + + if (!result) + { + int error = System.Runtime.InteropServices.Marshal.GetLastWin32Error(); + throw new InvalidOperationException("VirtualFree returned an error: " + error.ToString()); + } + + if (bytes > 0) + { + GC.RemoveMemoryPressure((long)bytes); + } + } + + /// + /// Sets protection on a block previously allocated with AllocateLarge. + /// + /// The starting memory address to set protection for. + /// The size of the block. + /// Whether to allow read access. + /// Whether to allow write access. + /// + /// You may not specify false for read access without also specifying false for write access. + /// Note to implementors: This method is not guaranteed to actually set read/write-ability + /// on a block of memory, and may instead be implemented as a no-op after parameter validation. + /// + public static void ProtectBlockLarge(IntPtr block, ulong size, bool readAccess, bool writeAccess) + { + uint flOldProtect; + uint flNewProtect; + + if (readAccess && writeAccess) + { + flNewProtect = NativeConstants.PAGE_READWRITE; + } + else if (readAccess && !writeAccess) + { + flNewProtect = NativeConstants.PAGE_READONLY; + } + else if (!readAccess && !writeAccess) + { + flNewProtect = NativeConstants.PAGE_NOACCESS; + } + else + { + throw new InvalidOperationException("May not specify a page to be write-only"); + } + +#if DEBUGSPEW + Tracing.Ping("ProtectBlockLarge: block #" + block.ToString() + ", read: " + readAccess + ", write: " + writeAccess); +#endif + + SafeNativeMethods.VirtualProtect(block, new UIntPtr(size), flNewProtect, out flOldProtect); + } + + /// + /// Copies bytes from one area of memory to another. Since this function only + /// takes pointers, it can not do any bounds checking. + /// + /// The starting address of where to copy bytes to. + /// The starting address of where to copy bytes from. + /// The number of bytes to copy + public static void Copy(IntPtr dst, IntPtr src, ulong length) + { + Copy(dst.ToPointer(), src.ToPointer(), length); + } + + /// + /// Copies bytes from one area of memory to another. Since this function only + /// takes pointers, it can not do any bounds checking. + /// + /// The starting address of where to copy bytes to. + /// The starting address of where to copy bytes from. + /// The number of bytes to copy + public static void Copy(void *dst, void *src, ulong length) + { + SafeNativeMethods.memcpy(dst, src, new UIntPtr(length)); + } + + public static void SetToZero(IntPtr dst, ulong length) + { + SetToZero(dst.ToPointer(), length); + } + + public static void SetToZero(void *dst, ulong length) + { + SafeNativeMethods.memset(dst, 0, new UIntPtr(length)); + } + } +} diff --git a/src/SystemLayer/MenuStripEx.cs b/src/SystemLayer/MenuStripEx.cs new file mode 100644 index 0000000..9affe33 --- /dev/null +++ b/src/SystemLayer/MenuStripEx.cs @@ -0,0 +1,102 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// This class adds on to the functionality provided in System.Windows.Forms.MenuStrip. + /// + /// + /// The first aggravating thing I found out about the new toolstrips is that they do not "click through." + /// If the form that is hosting a toolstrip is not active and you click on a button in the toolstrip, it + /// sets focus to the form but does NOT click the button. This makes sense in many situations, but + /// definitely not for Paint.NET. + /// + public class MenuStripEx + : MenuStrip + { + private bool clickThrough = true; + private static int openCount = 0; + + public MenuStripEx() + { + this.ImageScalingSize = new System.Drawing.Size(UI.ScaleWidth(16), UI.ScaleHeight(16)); + } + + /// + /// Gets or sets whether the ToolStripEx honors item clicks when its containing form does + /// not have input focus. + /// + /// + /// Default value is true, which is the opposite of the behavior provided by the base + /// ToolStrip class. + /// + public bool ClickThrough + { + get + { + return this.clickThrough; + } + + set + { + this.clickThrough = value; + } + } + + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + + if (this.clickThrough) + { + UI.ClickThroughWndProc(ref m); + } + } + + /// + /// Gets a value indicating whether any menu is currently open. + /// + /// + /// To be precise, this will return true if any menu has raised its MenuActivate event + /// but has yet to raise its MenuDeactivate event. + public static bool IsAnyMenuActive + { + get + { + return openCount > 0; + } + } + + public static void PushMenuActivate() + { + ++openCount; + } + + public static void PopMenuActivate() + { + --openCount; + } + + protected override void OnMenuActivate(EventArgs e) + { + ++openCount; + base.OnMenuActivate(e); + } + + protected override void OnMenuDeactivate(EventArgs e) + { + --openCount; + base.OnMenuDeactivate(e); + } + } +} diff --git a/src/SystemLayer/NativeConstants.cs b/src/SystemLayer/NativeConstants.cs new file mode 100644 index 0000000..e1a8d1c --- /dev/null +++ b/src/SystemLayer/NativeConstants.cs @@ -0,0 +1,1076 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32.SafeHandles; +using System; + +namespace PaintDotNet.SystemLayer +{ + internal static class NativeConstants + { + public const int MAX_PATH = 260; + + public const int CSIDL_DESKTOP_DIRECTORY = 0x0010; // C:\Users\[user]\Desktop\ + public const int CSIDL_MYPICTURES = 0x0027; + public const int CSIDL_PERSONAL = 0x0005; + + public const int CSIDL_PROGRAM_FILES = 0x0026; // C:\Program Files\ + public const int CSIDL_APPDATA = 0x001a; // C:\Users\[user]\AppData\Roaming\ + public const int CSIDL_LOCAL_APPDATA = 0x001c; // C:\Users\[user]\AppData\Local\ + public const int CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019; // C:\Users\All Users\Desktop + + public const int CSIDL_FLAG_CREATE = 0x8000; // new for Win2K, or this in to force creation of folder + + public const uint SHGFP_TYPE_CURRENT = 0; + public const uint SHGFP_TYPE_DEFAULT = 1; + + public const int BP_COMMANDLINK = 6; + + public const int CMDLS_NORMAL = 1; + public const int CMDLS_HOT = 2; + public const int CMDLS_PRESSED = 3; + public const int CMDLS_DISABLED = 4; + public const int CMDLS_DEFAULTED = 5; + public const int CMDLS_DEFAULTED_ANIMATING = 6; + + public enum SECURITY_IMPERSONATION_LEVEL + { + SecurityAnonymous = 0, + SecurityIdentification = 1, + SecurityImpersonation = 2, + SecurityDelegation = 3 + } + + public enum TOKEN_TYPE + { + TokenPrimary = 1, + TokenImpersonation = 2 + } + + public const uint TOKEN_ASSIGN_PRIMARY = 0x0001; + public const uint TOKEN_DUPLICATE = 0x0002; + public const uint TOKEN_IMPERSONATE = 0x0004; + public const uint TOKEN_QUERY = 0x0008; + public const uint TOKEN_QUERY_SOURCE = 0x0010; + public const uint TOKEN_ADJUST_PRIVILEGES = 0x0020; + public const uint TOKEN_ADJUST_GROUPS = 0x0040; + public const uint TOKEN_ADJUST_DEFAULT = 0x0080; + public const uint TOKEN_ADJUST_SESSIONID = 0x0100; + + public const uint TOKEN_ALL_ACCESS_P = + STANDARD_RIGHTS_REQUIRED | + TOKEN_ASSIGN_PRIMARY | + TOKEN_DUPLICATE | + TOKEN_IMPERSONATE | + TOKEN_QUERY | + TOKEN_QUERY_SOURCE | + TOKEN_ADJUST_PRIVILEGES | + TOKEN_ADJUST_GROUPS | + TOKEN_ADJUST_DEFAULT; + + public const uint TOKEN_ALL_ACCESS = TOKEN_ALL_ACCESS_P | TOKEN_ADJUST_SESSIONID; + public const uint TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY; + public const uint TOKEN_WRITE = STANDARD_RIGHTS_WRITE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT; + public const uint TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE; + + public const uint MAXIMUM_ALLOWED = 0x02000000; + + public const uint PROCESS_TERMINATE = 0x0001; + public const uint PROCESS_CREATE_THREAD = 0x0002; + public const uint PROCESS_SET_SESSIONID = 0x0004; + public const uint PROCESS_VM_OPERATION = 0x0008; + public const uint PROCESS_VM_READ = 0x0010; + public const uint PROCESS_VM_WRITE = 0x0020; + public const uint PROCESS_DUP_HANDLE = 0x0040; + public const uint PROCESS_CREATE_PROCESS = 0x0080; + public const uint PROCESS_SET_QUOTA = 0x0100; + public const uint PROCESS_SET_INFORMATION = 0x0200; + public const uint PROCESS_QUERY_INFORMATION = 0x0400; + public const uint PROCESS_SUSPEND_RESUME = 0x0800; + public const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000; + public const uint PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFFF; + + public const uint PF_NX_ENABLED = 12; + public const uint PF_XMMI_INSTRUCTIONS_AVAILABLE = 6; + public const uint PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10; + public const uint PF_SSE3_INSTRUCTIONS_AVAILABLE = 13; + + public const uint CF_ENHMETAFILE = 14; + + public static Guid BHID_Stream + { + get + { + return new Guid(0x1cebb3ab, 0x7c10, 0x499a, 0xa4, 0x17, 0x92, 0xca, 0x16, 0xc4, 0xcb, 0x83); + } + } + + public const string IID_IOleWindow = "00000114-0000-0000-C000-000000000046"; + public const string IID_IModalWindow = "b4db1657-70d7-485e-8e3e-6fcb5a5c1802"; + public const string IID_IFileDialog = "42f85136-db7e-439c-85f1-e4075d135fc8"; + public const string IID_IFileOpenDialog = "d57c7288-d4ad-4768-be02-9d969532d960"; + public const string IID_IFileSaveDialog = "84bccd23-5fde-4cdb-aea4-af64b83d78ab"; + public const string IID_IFileDialogEvents = "973510DB-7D7F-452B-8975-74A85828D354"; + public const string IID_IFileDialogControlEvents = "36116642-D713-4b97-9B83-7484A9D00433"; + public const string IID_IFileDialogCustomize = "8016b7b3-3d49-4504-a0aa-2a37494e606f"; + public const string IID_IShellItem = "43826D1E-E718-42EE-BC55-A1E261C37BFE"; + public const string IID_IShellItemArray = "B63EA76D-1F85-456F-A19C-48159EFA858B"; + public const string IID_IKnownFolder = "38521333-6A87-46A7-AE10-0F16706816C3"; + public const string IID_IKnownFolderManager = "44BEAAEC-24F4-4E90-B3F0-23D258FBB146"; + public const string IID_IPropertyStore = "886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"; + + public const string IID_ISequentialStream = "0c733a30-2a1c-11ce-ade5-00aa0044773d"; + public const string IID_IStream = "0000000C-0000-0000-C000-000000000046"; + + public const string IID_IFileOperation = "947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8"; + public const string IID_IFileOperationProgressSink = "04b0f1a7-9490-44bc-96e1-4296a31252e2"; + + public const string CLSID_FileOpenDialog = "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7"; + public const string CLSID_FileSaveDialog = "C0B4E2F3-BA21-4773-8DBA-335EC946EB8B"; + public const string CLSID_KnownFolderManager = "4df0c730-df9d-4ae3-9153-aa6b82e9795a"; + public const string CLSID_FileOperation = "3ad05575-8857-4850-9277-11b85bdb8e09"; + + public enum FOF + : uint + { + FOF_MULTIDESTFILES = 0x0001, + FOF_CONFIRMMOUSE = 0x0002, + FOF_SILENT = 0x0004, // don't display progress UI (confirm prompts may be displayed still) + FOF_RENAMEONCOLLISION = 0x0008, // automatically rename the source files to avoid the collisions + FOF_NOCONFIRMATION = 0x0010, // don't display confirmation UI, assume "yes" for cases that can be bypassed, "no" for those that can not + FOF_WANTMAPPINGHANDLE = 0x0020, // Fill in SHFILEOPSTRUCT.hNameMappings + // Must be freed using SHFreeNameMappings + FOF_ALLOWUNDO = 0x0040, // enable undo including Recycle behavior for IFileOperation::Delete() + FOF_FILESONLY = 0x0080, // only operate on the files (non folders), both files and folders are assumed without this + FOF_SIMPLEPROGRESS = 0x0100, // means don't show names of files + FOF_NOCONFIRMMKDIR = 0x0200, // don't dispplay confirmatino UI before making any needed directories, assume "Yes" in these cases + FOF_NOERRORUI = 0x0400, // don't put up error UI, other UI may be displayed, progress, confirmations + FOF_NOCOPYSECURITYATTRIBS = 0x0800, // dont copy file security attributes (ACLs) + FOF_NORECURSION = 0x1000, // don't recurse into directories for operations that would recurse + FOF_NO_CONNECTED_ELEMENTS = 0x2000, // don't operate on connected elements ("xxx_files" folders that go with .htm files) + FOF_WANTNUKEWARNING = 0x4000, // during delete operation, warn if nuking instead of recycling (partially overrides FOF_NOCONFIRMATION) + FOF_NORECURSEREPARSE = 0x8000, // deprecated; the operations engine always does the right thing on FolderLink objects (symlinks, reparse points, folder shortcuts) + + FOF_NO_UI = (FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR), // don't display any UI at all + + FOFX_NOSKIPJUNCTIONS = 0x00010000, // Don't avoid binding to junctions (like Task folder, Recycle-Bin) + FOFX_PREFERHARDLINK = 0x00020000, // Create hard link if possible + FOFX_SHOWELEVATIONPROMPT = 0x00040000, // Show elevation prompts when error UI is disabled (use with FOF_NOERRORUI) + FOFX_EARLYFAILURE = 0x00100000, // Fail operation as soon as a single error occurs rather than trying to process other items (applies only when using FOF_NOERRORUI) + FOFX_PRESERVEFILEEXTENSIONS = 0x00200000, // Rename collisions preserve file extns (use with FOF_RENAMEONCOLLISION) + FOFX_KEEPNEWERFILE = 0x00400000, // Keep newer file on naming conflicts + FOFX_NOCOPYHOOKS = 0x00800000, // Don't use copy hooks + FOFX_NOMINIMIZEBOX = 0x01000000, // Don't allow minimizing the progress dialog + FOFX_MOVEACLSACROSSVOLUMES = 0x02000000, // Copy security information when performing a cross-volume move operation + FOFX_DONTDISPLAYSOURCEPATH = 0x04000000, // Don't display the path of source file in progress dialog + FOFX_DONTDISPLAYDESTPATH = 0x08000000, // Don't display the path of destination file in progress dialog + } + + public enum STATFLAG + : uint + { + STATFLAG_DEFAULT = 0, + STATFLAG_NONAME = 1, + STATFLAG_NOOPEN = 2 + } + + public enum STGTY + : uint + { + STGTY_STORAGE = 1, + STGTY_STREAM = 2, + STGTY_LOCKBYTES = 3, + STGTY_PROPERTY = 4 + } + + [Flags] + public enum STGC + : uint + { + STGC_DEFAULT = 0, + STGC_OVERWRITE = 1, + STGC_ONLYIFCURRENT = 2, + STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 4, + STGC_CONSOLIDATE = 8 + } + + public enum CDCONTROLSTATE + { + CDCS_INACTIVE = 0x00000000, + CDCS_ENABLED = 0x00000001, + CDCS_VISIBLE = 0x00000002 + } + + public enum FFFP_MODE + { + FFFP_EXACTMATCH, + FFFP_NEARESTPARENTMATCH + } + + public enum SIATTRIBFLAGS + { + SIATTRIBFLAGS_AND = 0x00000001, // if multiple items and the attirbutes together. + SIATTRIBFLAGS_OR = 0x00000002, // if multiple items or the attributes together. + SIATTRIBFLAGS_APPCOMPAT = 0x00000003, // Call GetAttributes directly on the ShellFolder for multiple attributes + } + + public enum SIGDN : uint + { + SIGDN_NORMALDISPLAY = 0x00000000, // SHGDN_NORMAL + SIGDN_PARENTRELATIVEPARSING = 0x80018001, // SHGDN_INFOLDER | SHGDN_FORPARSING + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, // SHGDN_FORPARSING + SIGDN_PARENTRELATIVEEDITING = 0x80031001, // SHGDN_INFOLDER | SHGDN_FOREDITING + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, // SHGDN_FORPARSING | SHGDN_FORADDRESSBAR + SIGDN_FILESYSPATH = 0x80058000, // SHGDN_FORPARSING + SIGDN_URL = 0x80068000, // SHGDN_FORPARSING + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, // SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR + SIGDN_PARENTRELATIVE = 0x80080001 // SHGDN_INFOLDER + } + + public const uint DROPEFFECT_COPY = 1; + public const uint DROPEFFECT_MOVE = 2; + public const uint DROPEFFECT_LINK = 4; + + [Flags] + public enum SFGAO : uint + { + SFGAO_CANCOPY = DROPEFFECT_COPY, // Objects can be copied (0x1) + SFGAO_CANMOVE = DROPEFFECT_MOVE, // Objects can be moved (0x2) + SFGAO_CANLINK = DROPEFFECT_LINK, // Objects can be linked (0x4) + SFGAO_STORAGE = 0x00000008, // supports BindToObject(IID_IStorage) + SFGAO_CANRENAME = 0x00000010, // Objects can be renamed + SFGAO_CANDELETE = 0x00000020, // Objects can be deleted + SFGAO_HASPROPSHEET = 0x00000040, // Objects have property sheets + SFGAO_DROPTARGET = 0x00000100, // Objects are drop target + SFGAO_CAPABILITYMASK = 0x00000177, + SFGAO_ENCRYPTED = 0x00002000, // Object is encrypted (use alt color) + SFGAO_ISSLOW = 0x00004000, // 'Slow' object + SFGAO_GHOSTED = 0x00008000, // Ghosted icon + SFGAO_LINK = 0x00010000, // Shortcut (link) + SFGAO_SHARE = 0x00020000, // Shared + SFGAO_READONLY = 0x00040000, // Read-only + SFGAO_HIDDEN = 0x00080000, // Hidden object + SFGAO_DISPLAYATTRMASK = 0x000FC000, + SFGAO_FILESYSANCESTOR = 0x10000000, // May contain children with SFGAO_FILESYSTEM + SFGAO_FOLDER = 0x20000000, // Support BindToObject(IID_IShellFolder) + SFGAO_FILESYSTEM = 0x40000000, // Is a win32 file system object (file/folder/root) + SFGAO_HASSUBFOLDER = 0x80000000, // May contain children with SFGAO_FOLDER (may be slow) + SFGAO_CONTENTSMASK = 0x80000000, + SFGAO_VALIDATE = 0x01000000, // Invalidate cached information (may be slow) + SFGAO_REMOVABLE = 0x02000000, // Is this removeable media? + SFGAO_COMPRESSED = 0x04000000, // Object is compressed (use alt color) + SFGAO_BROWSABLE = 0x08000000, // Supports IShellFolder, but only implements CreateViewObject() (non-folder view) + SFGAO_NONENUMERATED = 0x00100000, // Is a non-enumerated object (should be hidden) + SFGAO_NEWCONTENT = 0x00200000, // Should show bold in explorer tree + SFGAO_STREAM = 0x00400000, // Supports BindToObject(IID_IStream) + SFGAO_CANMONIKER = 0x00400000, // Obsolete + SFGAO_HASSTORAGE = 0x00400000, // Obsolete + SFGAO_STORAGEANCESTOR = 0x00800000, // May contain children with SFGAO_STORAGE or SFGAO_STREAM + SFGAO_STORAGECAPMASK = 0x70C50008, // For determining storage capabilities, ie for open/save semantics + SFGAO_PKEYSFGAOMASK = 0x81044010 // Attributes that are masked out for PKEY_SFGAOFlags because they are considered to cause slow calculations or lack context (SFGAO_VALIDATE | SFGAO_ISSLOW | SFGAO_HASSUBFOLDER and others) + } + + public enum FDE_OVERWRITE_RESPONSE + { + FDEOR_DEFAULT = 0x00000000, + FDEOR_ACCEPT = 0x00000001, + FDEOR_REFUSE = 0x00000002 + } + + public enum FDE_SHAREVIOLATION_RESPONSE + { + FDESVR_DEFAULT = 0x00000000, + FDESVR_ACCEPT = 0x00000001, + FDESVR_REFUSE = 0x00000002 + } + + public enum FDAP + { + FDAP_BOTTOM = 0x00000000, + FDAP_TOP = 0x00000001, + } + + [Flags] + public enum FOS : uint + { + FOS_OVERWRITEPROMPT = 0x00000002, + FOS_STRICTFILETYPES = 0x00000004, + FOS_NOCHANGEDIR = 0x00000008, + FOS_PICKFOLDERS = 0x00000020, + FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items. + FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage. + FOS_NOVALIDATE = 0x00000100, + FOS_ALLOWMULTISELECT = 0x00000200, + FOS_PATHMUSTEXIST = 0x00000800, + FOS_FILEMUSTEXIST = 0x00001000, + FOS_CREATEPROMPT = 0x00002000, + FOS_SHAREAWARE = 0x00004000, + FOS_NOREADONLYRETURN = 0x00008000, + FOS_NOTESTFILECREATE = 0x00010000, + FOS_HIDEMRUPLACES = 0x00020000, + FOS_HIDEPINNEDPLACES = 0x00040000, + FOS_NODEREFERENCELINKS = 0x00100000, + FOS_DONTADDTORECENT = 0x02000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000 + } + + public enum KF_CATEGORY + { + KF_CATEGORY_VIRTUAL = 0x00000001, + KF_CATEGORY_FIXED = 0x00000002, + KF_CATEGORY_COMMON = 0x00000003, + KF_CATEGORY_PERUSER = 0x00000004 + } + + [Flags] + public enum KF_DEFINITION_FLAGS + { + KFDF_PERSONALIZE = 0x00000001, + KFDF_LOCAL_REDIRECT_ONLY = 0x00000002, + KFDF_ROAMABLE = 0x00000004, + } + + public const uint DWMWA_NCRENDERING_ENABLED = 1; // [get] Is non-client rendering enabled/disabled + public const uint DWMWA_NCRENDERING_POLICY = 2; // [set] Non-client rendering policy + public const uint DWMWA_TRANSITIONS_FORCEDISABLED = 3; // [set] Potentially enable/forcibly disable transitions + public const uint DWMWA_ALLOW_NCPAINT = 4; // [set] Allow contents rendered in the non-client area to be visible on the DWM-drawn frame. + public const uint DWMWA_CAPTION_BUTTON_BOUNDS = 5; // [get] Bounds of the caption button area in window-relative space. + public const uint DWMWA_NONCLIENT_RTL_LAYOUT = 6; // [set] Is non-client content RTL mirrored + public const uint DWMWA_FORCE_ICONIC_REPRESENTATION = 7; // [set] Force this window to display iconic thumbnails. + public const uint DWMWA_FLIP3D_POLICY = 8; // [set] Designates how Flip3D will treat the window. + public const uint DWMWA_EXTENDED_FRAME_BOUNDS = 9; // [get] Gets the extended frame bounds rectangle in screen space + public const uint DWMWA_LAST = 10; + + public const uint DWMNCRP_USEWINDOWSTYLE = 0; + public const uint DWMNCRP_DISABLED = 1; + public const uint DWMNCRP_ENABLED = 2; + public const uint DWMNCRP_LAST = 3; + + public const byte VER_EQUAL = 1; + public const byte VER_GREATER = 2; + public const byte VER_GREATER_EQUAL = 3; + public const byte VER_LESS = 4; + public const byte VER_LESS_EQUAL = 5; + public const byte VER_AND = 6; + public const byte VER_OR = 7; + + public const uint VER_CONDITION_MASK = 7; + public const uint VER_NUM_BITS_PER_CONDITION_MASK = 3; + + public const uint VER_MINORVERSION = 0x0000001; + public const uint VER_MAJORVERSION = 0x0000002; + public const uint VER_BUILDNUMBER = 0x0000004; + public const uint VER_PLATFORMID = 0x0000008; + public const uint VER_SERVICEPACKMINOR = 0x0000010; + public const uint VER_SERVICEPACKMAJOR = 0x0000020; + public const uint VER_SUITENAME = 0x0000040; + public const uint VER_PRODUCT_TYPE = 0x0000080; + + public const uint VER_PLATFORM_WIN32s = 0; + public const uint VER_PLATFORM_WIN32_WINDOWS = 1; + public const uint VER_PLATFORM_WIN32_NT = 2; + + public const int THREAD_MODE_BACKGROUND_BEGIN = 0x10000; + public const int THREAD_MODE_BACKGROUND_END = 0x20000; + + private static uint CTL_CODE(uint deviceType, uint function, uint method, uint access) + { + return (deviceType << 16) | (access << 14) | (function << 2) | method; + } + + public const uint FILE_DEVICE_FILE_SYSTEM = 0x00000009; + public const uint METHOD_BUFFERED = 0; + + public static readonly uint FSCTL_SET_COMPRESSION = + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 16, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA); + + public static ushort COMPRESSION_FORMAT_DEFAULT = 1; + + public const int SW_HIDE = 0; + public const int SW_SHOWNORMAL = 1; + public const int SW_NORMAL = 1; + public const int SW_SHOWMINIMIZED = 2; + public const int SW_SHOWMAXIMIZED = 3; + public const int SW_MAXIMIZE = 3; + public const int SW_SHOWNOACTIVATE = 4; + public const int SW_SHOW = 5; + public const int SW_MINIMIZE = 6; + public const int SW_SHOWMINNOACTIVE = 7; + public const int SW_SHOWNA = 8; + public const int SW_RESTORE = 9; + public const int SW_SHOWDEFAULT = 10; + public const int SW_FORCEMINIMIZE = 11; + public const int SW_MAX = 11; + + public const uint MF_BYCOMMAND = 0; + public const uint MF_GRAYED = 1; + public const uint MF_DISABLED = 2; + public const uint SC_CLOSE = 0xf060; + + public const uint SEE_MASK_CLASSNAME = 0x00000001; + public const uint SEE_MASK_CLASSKEY = 0x00000003; + public const uint SEE_MASK_IDLIST = 0x00000004; + public const uint SEE_MASK_INVOKEIDLIST = 0x0000000c; + public const uint SEE_MASK_ICON = 0x00000010; + public const uint SEE_MASK_HOTKEY = 0x00000020; + public const uint SEE_MASK_NOCLOSEPROCESS = 0x00000040; + public const uint SEE_MASK_CONNECTNETDRV = 0x00000080; + public const uint SEE_MASK_FLAG_DDEWAIT = 0x00000100; + public const uint SEE_MASK_DOENVSUBST = 0x00000200; + public const uint SEE_MASK_FLAG_NO_UI = 0x00000400; + public const uint SEE_MASK_UNICODE = 0x00004000; + public const uint SEE_MASK_NO_CONSOLE = 0x00008000; + public const uint SEE_MASK_ASYNCOK = 0x00100000; + public const uint SEE_MASK_HMONITOR = 0x00200000; + public const uint SEE_MASK_NOZONECHECKS = 0x00800000; + public const uint SEE_MASK_NOQUERYCLASSSTORE = 0x01000000; + public const uint SEE_MASK_WAITFORINPUTIDLE = 0x02000000; + public const uint SEE_MASK_FLAG_LOG_USAGE = 0x04000000; + + public const uint SHARD_PIDL = 0x00000001; + public const uint SHARD_PATHA = 0x00000002; + public const uint SHARD_PATHW = 0x00000003; + + public const uint VER_NT_WORKSTATION = 0x0000001; + public const uint VER_NT_DOMAIN_CONTROLLER = 0x0000002; + public const uint VER_NT_SERVER = 0x0000003; + + public const uint LWA_COLORKEY = 0x00000001; + public const uint LWA_ALPHA = 0x00000002; + public const uint WS_EX_LAYERED = 0x00080000; + + public const ushort PROCESSOR_ARCHITECTURE_INTEL = 0; + public const ushort PROCESSOR_ARCHITECTURE_IA64 = 6; + public const ushort PROCESSOR_ARCHITECTURE_AMD64 = 9; + public const ushort PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF; + + public const uint SHVIEW_THUMBNAIL = 0x702d; + + public const uint MA_ACTIVATE = 1; + public const uint MA_ACTIVATEANDEAT = 2; + public const uint MA_NOACTIVATE = 3; + public const uint MA_NOACTIVATEANDEAT = 4; + + public const uint IDI_APPLICATION = 32512; + + public const int ERROR_SUCCESS = 0; + public const int ERROR_ALREADY_EXISTS = 183; + public const int ERROR_CANCELLED = 1223; + public const int ERROR_IO_PENDING = 0x3e5; + public const int ERROR_NO_MORE_ITEMS = 259; + public const int ERROR_TIMEOUT = 1460; + + public const uint DIGCF_PRESENT = 2; + + public const int GWL_STYLE = -16; + public const int GWL_EXSTYLE = -20; + + public const int GWLP_WNDPROC = -4; + public const int GWLP_HINSTANCE = -6; + public const int GWLP_HWNDPARENT = -8; + public const int GWLP_USERDATA = -21; + public const int GWLP_ID = -12; + + public const uint PBS_SMOOTH = 0x01; + public const uint PBS_MARQUEE = 0x08; + public const int PBM_SETMARQUEE = WM_USER + 10; + + public const int SBM_SETPOS = 0x00E0; + public const int SBM_SETRANGE = 0x00E2; + public const int SBM_SETRANGEREDRAW = 0x00E6; + public const int SBM_SETSCROLLINFO = 0x00E9; + + public const int BCM_FIRST = 0x1600; + public const int BCM_SETSHIELD = BCM_FIRST + 0x000C; + + public const int CB_SHOWDROPDOWN = 0x014f; + + public const uint WM_COMMAND = 0x111; + public const uint WM_MOUSEACTIVATE = 0x21; + public const uint WM_COPYDATA = 0x004a; + + public const uint SMTO_NORMAL = 0x0000; + public const uint SMTO_BLOCK = 0x0001; + public const uint SMTO_ABORTIFHUNG = 0x0002; + public const uint SMTO_NOTIMEOUTIFNOTHUNG = 0x0008; + + public const int WM_USER = 0x400; + public const int WM_HSCROLL = 0x114; + public const int WM_VSCROLL = 0x115; + public const int WM_SETFOCUS = 7; + public const int WM_QUERYENDSESSION = 0x0011; + public const int WM_ACTIVATE = 0x006; + public const int WM_ACTIVATEAPP = 0x01C; + public const int WM_PAINT = 0x000f; + public const int WM_NCPAINT = 0x0085; + public const int WM_NCACTIVATE = 0x086; + public const int WM_SETREDRAW = 0x000B; + + public const uint WS_VSCROLL = 0x00200000; + public const uint WS_HSCROLL = 0x00100000; + + public const uint BS_MULTILINE = 0x00002000; + + public const uint ANSI_CHARSET = 0; + public const uint DEFAULT_CHARSET = 1; + public const uint SYMBOL_CHARSET = 2; + public const uint SHIFTJIS_CHARSET = 128; + public const uint HANGEUL_CHARSET = 129; + public const uint HANGUL_CHARSET = 129; + public const uint GB2312_CHARSET = 134; + public const uint CHINESEBIG5_CHARSET = 136; + public const uint OEM_CHARSET = 255; + public const uint JOHAB_CHARSET = 130; + public const uint HEBREW_CHARSET = 177; + public const uint ARABIC_CHARSET = 178; + public const uint GREEK_CHARSET = 161; + public const uint TURKISH_CHARSET = 162; + public const uint VIETNAMESE_CHARSET = 163; + public const uint THAI_CHARSET = 222; + public const uint EASTEUROPE_CHARSET = 238; + public const uint RUSSIAN_CHARSET = 204; + public const uint MAC_CHARSET = 77; + public const uint BALTIC_CHARSET = 186; + + public const uint SPI_GETBEEP = 0x0001; + public const uint SPI_SETBEEP = 0x0002; + public const uint SPI_GETMOUSE = 0x0003; + public const uint SPI_SETMOUSE = 0x0004; + public const uint SPI_GETBORDER = 0x0005; + public const uint SPI_SETBORDER = 0x0006; + public const uint SPI_GETKEYBOARDSPEED = 0x000A; + public const uint SPI_SETKEYBOARDSPEED = 0x000B; + public const uint SPI_LANGDRIVER = 0x000C; + public const uint SPI_ICONHORIZONTALSPACING = 0x000D; + public const uint SPI_GETSCREENSAVETIMEOUT = 0x000E; + public const uint SPI_SETSCREENSAVETIMEOUT = 0x000F; + public const uint SPI_GETSCREENSAVEACTIVE = 0x0010; + public const uint SPI_SETSCREENSAVEACTIVE = 0x0011; + public const uint SPI_GETGRIDGRANULARITY = 0x0012; + public const uint SPI_SETGRIDGRANULARITY = 0x0013; + public const uint SPI_SETDESKWALLPAPER = 0x0014; + public const uint SPI_SETDESKPATTERN = 0x0015; + public const uint SPI_GETKEYBOARDDELAY = 0x0016; + public const uint SPI_SETKEYBOARDDELAY = 0x0017; + public const uint SPI_ICONVERTICALSPACING = 0x0018; + public const uint SPI_GETICONTITLEWRAP = 0x0019; + public const uint SPI_SETICONTITLEWRAP = 0x001A; + public const uint SPI_GETMENUDROPALIGNMENT = 0x001B; + public const uint SPI_SETMENUDROPALIGNMENT = 0x001C; + public const uint SPI_SETDOUBLECLKWIDTH = 0x001D; + public const uint SPI_SETDOUBLECLKHEIGHT = 0x001E; + public const uint SPI_GETICONTITLELOGFONT = 0x001F; + public const uint SPI_SETDOUBLECLICKTIME = 0x0020; + public const uint SPI_SETMOUSEBUTTONSWAP = 0x0021; + public const uint SPI_SETICONTITLELOGFONT = 0x0022; + public const uint SPI_GETFASTTASKSWITCH = 0x0023; + public const uint SPI_SETFASTTASKSWITCH = 0x0024; + public const uint SPI_SETDRAGFULLWINDOWS = 0x0025; + public const uint SPI_GETDRAGFULLWINDOWS = 0x0026; + public const uint SPI_GETNONCLIENTMETRICS = 0x0029; + public const uint SPI_SETNONCLIENTMETRICS = 0x002A; + public const uint SPI_GETMINIMIZEDMETRICS = 0x002B; + public const uint SPI_SETMINIMIZEDMETRICS = 0x002C; + public const uint SPI_GETICONMETRICS = 0x002D; + public const uint SPI_SETICONMETRICS = 0x002E; + public const uint SPI_SETWORKAREA = 0x002F; + public const uint SPI_GETWORKAREA = 0x0030; + public const uint SPI_SETPENWINDOWS = 0x0031; + public const uint SPI_GETHIGHCONTRAST = 0x0042; + public const uint SPI_SETHIGHCONTRAST = 0x0043; + public const uint SPI_GETKEYBOARDPREF = 0x0044; + public const uint SPI_SETKEYBOARDPREF = 0x0045; + public const uint SPI_GETSCREENREADER = 0x0046; + public const uint SPI_SETSCREENREADER = 0x0047; + public const uint SPI_GETANIMATION = 0x0048; + public const uint SPI_SETANIMATION = 0x0049; + public const uint SPI_GETFONTSMOOTHING = 0x004A; + public const uint SPI_SETFONTSMOOTHING = 0x004B; + public const uint SPI_SETDRAGWIDTH = 0x004C; + public const uint SPI_SETDRAGHEIGHT = 0x004D; + public const uint SPI_SETHANDHELD = 0x004E; + public const uint SPI_GETLOWPOWERTIMEOUT = 0x004F; + public const uint SPI_GETPOWEROFFTIMEOUT = 0x0050; + public const uint SPI_SETLOWPOWERTIMEOUT = 0x0051; + public const uint SPI_SETPOWEROFFTIMEOUT = 0x0052; + public const uint SPI_GETLOWPOWERACTIVE = 0x0053; + public const uint SPI_GETPOWEROFFACTIVE = 0x0054; + public const uint SPI_SETLOWPOWERACTIVE = 0x0055; + public const uint SPI_SETPOWEROFFACTIVE = 0x0056; + public const uint SPI_SETCURSORS = 0x0057; + public const uint SPI_SETICONS = 0x0058; + public const uint SPI_GETDEFAULTINPUTLANG = 0x0059; + public const uint SPI_SETDEFAULTINPUTLANG = 0x005A; + public const uint SPI_SETLANGTOGGLE = 0x005B; + public const uint SPI_GETWINDOWSEXTENSION = 0x005C; + public const uint SPI_SETMOUSETRAILS = 0x005D; + public const uint SPI_GETMOUSETRAILS = 0x005E; + public const uint SPI_SETSCREENSAVERRUNNING = 0x0061; + public const uint SPI_SCREENSAVERRUNNING = SPI_SETSCREENSAVERRUNNING; + public const uint SPI_GETFILTERKEYS = 0x0032; + public const uint SPI_SETFILTERKEYS = 0x0033; + public const uint SPI_GETTOGGLEKEYS = 0x0034; + public const uint SPI_SETTOGGLEKEYS = 0x0035; + public const uint SPI_GETMOUSEKEYS = 0x0036; + public const uint SPI_SETMOUSEKEYS = 0x0037; + public const uint SPI_GETSHOWSOUNDS = 0x0038; + public const uint SPI_SETSHOWSOUNDS = 0x0039; + public const uint SPI_GETSTICKYKEYS = 0x003A; + public const uint SPI_SETSTICKYKEYS = 0x003B; + public const uint SPI_GETACCESSTIMEOUT = 0x003C; + public const uint SPI_SETACCESSTIMEOUT = 0x003D; + public const uint SPI_GETSERIALKEYS = 0x003E; + public const uint SPI_SETSERIALKEYS = 0x003F; + public const uint SPI_GETSOUNDSENTRY = 0x0040; + public const uint SPI_SETSOUNDSENTRY = 0x0041; + public const uint SPI_GETSNAPTODEFBUTTON = 0x005F; + public const uint SPI_SETSNAPTODEFBUTTON = 0x0060; + public const uint SPI_GETMOUSEHOVERWIDTH = 0x0062; + public const uint SPI_SETMOUSEHOVERWIDTH = 0x0063; + public const uint SPI_GETMOUSEHOVERHEIGHT = 0x0064; + public const uint SPI_SETMOUSEHOVERHEIGHT = 0x0065; + public const uint SPI_GETMOUSEHOVERTIME = 0x0066; + public const uint SPI_SETMOUSEHOVERTIME = 0x0067; + public const uint SPI_GETWHEELSCROLLLINES = 0x0068; + public const uint SPI_SETWHEELSCROLLLINES = 0x0069; + public const uint SPI_GETMENUSHOWDELAY = 0x006A; + public const uint SPI_SETMENUSHOWDELAY = 0x006B; + public const uint SPI_GETSHOWIMEUI = 0x006E; + public const uint SPI_SETSHOWIMEUI = 0x006F; + public const uint SPI_GETMOUSESPEED = 0x0070; + public const uint SPI_SETMOUSESPEED = 0x0071; + public const uint SPI_GETSCREENSAVERRUNNING = 0x0072; + public const uint SPI_GETDESKWALLPAPER = 0x0073; + public const uint SPI_GETACTIVEWINDOWTRACKING = 0x1000; + public const uint SPI_SETACTIVEWINDOWTRACKING = 0x1001; + public const uint SPI_GETMENUANIMATION = 0x1002; + public const uint SPI_SETMENUANIMATION = 0x1003; + public const uint SPI_GETCOMBOBOXANIMATION = 0x1004; + public const uint SPI_SETCOMBOBOXANIMATION = 0x1005; + public const uint SPI_GETLISTBOXSMOOTHSCROLLING = 0x1006; + public const uint SPI_SETLISTBOXSMOOTHSCROLLING = 0x1007; + public const uint SPI_GETGRADIENTCAPTIONS = 0x1008; + public const uint SPI_SETGRADIENTCAPTIONS = 0x1009; + public const uint SPI_GETKEYBOARDCUES = 0x100A; + public const uint SPI_SETKEYBOARDCUES = 0x100B; + public const uint SPI_GETMENUUNDERLINES = SPI_GETKEYBOARDCUES; + public const uint SPI_SETMENUUNDERLINES = SPI_SETKEYBOARDCUES; + public const uint SPI_GETACTIVEWNDTRKZORDER = 0x100C; + public const uint SPI_SETACTIVEWNDTRKZORDER = 0x100D; + public const uint SPI_GETHOTTRACKING = 0x100E; + public const uint SPI_SETHOTTRACKING = 0x100F; + public const uint SPI_GETMENUFADE = 0x1012; + public const uint SPI_SETMENUFADE = 0x1013; + public const uint SPI_GETSELECTIONFADE = 0x1014; + public const uint SPI_SETSELECTIONFADE = 0x1015; + public const uint SPI_GETTOOLTIPANIMATION = 0x1016; + public const uint SPI_SETTOOLTIPANIMATION = 0x1017; + public const uint SPI_GETTOOLTIPFADE = 0x1018; + public const uint SPI_SETTOOLTIPFADE = 0x1019; + public const uint SPI_GETCURSORSHADOW = 0x101A; + public const uint SPI_SETCURSORSHADOW = 0x101B; + public const uint SPI_GETMOUSESONAR = 0x101C; + public const uint SPI_SETMOUSESONAR = 0x101D; + public const uint SPI_GETMOUSECLICKLOCK = 0x101E; + public const uint SPI_SETMOUSECLICKLOCK = 0x101F; + public const uint SPI_GETMOUSEVANISH = 0x1020; + public const uint SPI_SETMOUSEVANISH = 0x1021; + public const uint SPI_GETFLATMENU = 0x1022; + public const uint SPI_SETFLATMENU = 0x1023; + public const uint SPI_GETDROPSHADOW = 0x1024; + public const uint SPI_SETDROPSHADOW = 0x1025; + public const uint SPI_GETBLOCKSENDINPUTRESETS = 0x1026; + public const uint SPI_SETBLOCKSENDINPUTRESETS = 0x1027; + public const uint SPI_GETUIEFFECTS = 0x103E; + public const uint SPI_SETUIEFFECTS = 0x103F; + public const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000; + public const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001; + public const uint SPI_GETACTIVEWNDTRKTIMEOUT = 0x2002; + public const uint SPI_SETACTIVEWNDTRKTIMEOUT = 0x2003; + public const uint SPI_GETFOREGROUNDFLASHCOUNT = 0x2004; + public const uint SPI_SETFOREGROUNDFLASHCOUNT = 0x2005; + public const uint SPI_GETCARETWIDTH = 0x2006; + public const uint SPI_SETCARETWIDTH = 0x2007; + public const uint SPI_GETMOUSECLICKLOCKTIME = 0x2008; + public const uint SPI_SETMOUSECLICKLOCKTIME = 0x2009; + public const uint SPI_GETFONTSMOOTHINGTYPE = 0x200A; + public const uint SPI_SETFONTSMOOTHINGTYPE = 0x200B; + public const uint SPI_GETFONTSMOOTHINGCONTRAST = 0x200C; + public const uint SPI_SETFONTSMOOTHINGCONTRAST = 0x200D; + public const uint SPI_GETFOCUSBORDERWIDTH = 0x200E; + public const uint SPI_SETFOCUSBORDERWIDTH = 0x200F; + public const uint SPI_GETFOCUSBORDERHEIGHT = 0x2010; + public const uint SPI_SETFOCUSBORDERHEIGHT = 0x2011; + public const uint SPI_GETFONTSMOOTHINGORIENTATION = 0x2012; + public const uint SPI_SETFONTSMOOTHINGORIENTATION = 0x2013; + + public const uint INFINITE = 0xffffffff; + public const uint STATUS_WAIT_0 = 0; + public const uint STATUS_ABANDONED_WAIT_0 = 0x80; + public const uint WAIT_FAILED = 0xffffffff; + public const uint WAIT_TIMEOUT = 258; + public const uint WAIT_ABANDONED = STATUS_ABANDONED_WAIT_0 + 0; + public const uint WAIT_OBJECT_0 = STATUS_WAIT_0 + 0; + public const uint WAIT_ABANDONED_0 = STATUS_ABANDONED_WAIT_0 + 0; + public const uint STATUS_USER_APC = 0x000000C0; + public const uint WAIT_IO_COMPLETION = STATUS_USER_APC; + + public const int SM_REMOTESESSION = 0x1000; + public const int WM_WTSSESSION_CHANGE = 0x2b1; + public const int WM_MOVING = 0x0216; + public const uint NOTIFY_FOR_ALL_SESSIONS = 1; + public const uint NOTIFY_FOR_THIS_SESSION = 0; + + public const int BP_PUSHBUTTON = 1; + public const int PBS_NORMAL = 1; + public const int PBS_HOT = 2; + public const int PBS_PRESSED = 3; + public const int PBS_DISABLED = 4; + public const int PBS_DEFAULTED = 5; + + public const int PS_SOLID = 0; + public const int PS_DASH = 1; /* ------- */ + public const int PS_DOT = 2; /* ....... */ + public const int PS_DASHDOT = 3; /* _._._._ */ + public const int PS_DASHDOTDOT = 4; /* _.._.._ */ + public const int PS_NULL = 5; + public const int PS_INSIDEFRAME = 6; + public const int PS_USERSTYLE = 7; + public const int PS_ALTERNATE = 8; + + public const int PS_ENDCAP_ROUND = 0x00000000; + public const int PS_ENDCAP_SQUARE = 0x00000100; + public const int PS_ENDCAP_FLAT = 0x00000200; + public const int PS_ENDCAP_MASK = 0x00000F00; + + public const int PS_JOIN_ROUND = 0x00000000; + public const int PS_JOIN_BEVEL = 0x00001000; + public const int PS_JOIN_MITER = 0x00002000; + public const int PS_JOIN_MASK = 0x0000F000; + + public const int PS_COSMETIC = 0x00000000; + public const int PS_GEOMETRIC = 0x00010000; + public const int PS_TYPE_MASK = 0x000F0000; + + public const int BS_SOLID = 0; + public const int BS_NULL = 1; + public const int BS_HOLLOW = BS_NULL; + public const int BS_HATCHED = 2; + public const int BS_PATTERN = 3; + public const int BS_INDEXED = 4; + public const int BS_DIBPATTERN = 5; + public const int BS_DIBPATTERNPT = 6; + public const int BS_PATTERN8X8 = 7; + public const int BS_DIBPATTERN8X8 = 8; + public const int BS_MONOPATTERN = 9; + + public const uint SRCCOPY = 0x00CC0020; /* dest = source */ + public const uint SRCPAINT = 0x00EE0086; /* dest = source OR dest */ + public const uint SRCAND = 0x008800C6; /* dest = source AND dest */ + public const uint SRCINVERT = 0x00660046; /* dest = source XOR dest */ + public const uint SRCERASE = 0x00440328; /* dest = source AND (NOT dest ) */ + public const uint NOTSRCCOPY = 0x00330008; /* dest = (NOT source) */ + public const uint NOTSRCERASE = 0x001100A6; /* dest = (NOT src) AND (NOT dest) */ + public const uint MERGECOPY = 0x00C000CA; /* dest = (source AND pattern) */ + public const uint MERGEPAINT = 0x00BB0226; /* dest = (NOT source) OR dest */ + public const uint PATCOPY = 0x00F00021; /* dest = pattern */ + public const uint PATPAINT = 0x00FB0A09; /* dest = DPSnoo */ + public const uint PATINVERT = 0x005A0049; /* dest = pattern XOR dest */ + public const uint DSTINVERT = 0x00550009; /* dest = (NOT dest) */ + public const uint BLACKNESS = 0x00000042; /* dest = BLACK */ + public const uint WHITENESS = 0x00FF0062; /* dest = WHITE */ + + public const uint NOMIRRORBITMAP = 0x80000000; /* Do not Mirror the bitmap in this call */ + public const uint CAPTUREBLT = 0x40000000; /* Include layered windows */ + + // StretchBlt() Modes + public const int BLACKONWHITE = 1; + public const int WHITEONBLACK = 2; + public const int COLORONCOLOR = 3; + public const int HALFTONE = 4; + public const int MAXSTRETCHBLTMODE = 4; + + public const int HeapCompatibilityInformation = 0; + public const uint HEAP_NO_SERIALIZE = 0x00000001; + public const uint HEAP_GROWABLE = 0x00000002; + public const uint HEAP_GENERATE_EXCEPTIONS = 0x00000004; + public const uint HEAP_ZERO_MEMORY = 0x00000008; + public const uint HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010; + public const uint HEAP_TAIL_CHECKING_ENABLED = 0x00000020; + public const uint HEAP_FREE_CHECKING_ENABLED = 0x00000040; + public const uint HEAP_DISABLE_COALESCE_ON_FREE = 0x00000080; + public const uint HEAP_CREATE_ALIGN_16 = 0x00010000; + public const uint HEAP_CREATE_ENABLE_TRACING = 0x00020000; + public const uint HEAP_MAXIMUM_TAG = 0x0FFF; + public const uint HEAP_PSEUDO_TAG_FLAG = 0x8000; + public const uint HEAP_TAG_SHIFT = 18; + + public const int SM_TABLETPC = 86; + + public const uint MONITOR_DEFAULTTONULL = 0x00000000; + public const uint MONITOR_DEFAULTTOPRIMARY = 0x00000001; + public const uint MONITOR_DEFAULTTONEAREST = 0x00000002; + + public const uint WTD_UI_ALL = 1; + public const uint WTD_UI_NONE = 2; + public const uint WTD_UI_NOBAD = 3; + public const uint WTD_UI_NOGOOD = 4; + + public const uint WTD_REVOKE_NONE = 0; + public const uint WTD_REVOKE_WHOLECHAIN = 1; + + public const uint WTD_CHOICE_FILE = 1; + public const uint WTD_CHOICE_CATALOG = 2; + public const uint WTD_CHOICE_BLOB = 3; + public const uint WTD_CHOICE_SIGNER = 4; + public const uint WTD_CHOICE_CERT = 5; + + public const uint WTD_STATEACTION_IGNORE = 0; + public const uint WTD_STATEACTION_VERIFY = 1; + public const uint WTD_STATEACTION_CLOSE = 2; + public const uint WTD_STATEACTION_AUTO_CACHE = 3; + public const uint WTD_STATEACTION_AUTO_CACHE_FLUSH = 4; + + public const uint WTD_PROV_FLAGS_MASK = 0x0000FFFF; + public const uint WTD_USE_IE4_TRUST_FLAG = 0x00000001; + public const uint WTD_NO_IE4_CHAIN_FLAG = 0x00000002; + public const uint WTD_NO_POLICY_USAGE_FLAG = 0x00000004; + public const uint WTD_REVOCATION_CHECK_NONE = 0x00000010; + public const uint WTD_REVOCATION_CHECK_END_CERT = 0x00000020; + public const uint WTD_REVOCATION_CHECK_CHAIN = 0x00000040; + public const uint WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x00000080; + public const uint WTD_SAFER_FLAG = 0x00000100; + public const uint WTD_HASH_ONLY_FLAG = 0x00000200; + public const uint WTD_USE_DEFAULT_OSVER_CHECK = 0x00000400; + public const uint WTD_LIFETIME_SIGNING_FLAG = 0x00000800; + public const uint WTD_CACHE_ONLY_URL_RETRIEVAL = 0x00001000; + + public static Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 + { + get + { + return new Guid(0xaac56b, 0xcd44, 0x11d0, 0x8c, 0xc2, 0x0, 0xc0, 0x4f, 0xc2, 0x95, 0xee); + } + } + + public const uint FILE_SHARE_READ = 0x00000001; + public const uint FILE_SHARE_WRITE = 0x00000002; + public const uint FILE_SHARE_DELETE = 0x00000004; + + public const uint FILE_READ_DATA = 0x0001; + public const uint FILE_LIST_DIRECTORY = 0x0001; + public const uint FILE_WRITE_DATA = 0x0002; + public const uint FILE_ADD_FILE = 0x0002; + public const uint FILE_APPEND_DATA = 0x0004; + public const uint FILE_ADD_SUBDIRECTORY = 0x0004; + public const uint FILE_CREATE_PIPE_INSTANCE = 0x0004; + + public const uint FILE_READ_EA = 0x0008; + public const uint FILE_WRITE_EA = 0x0010; + public const uint FILE_EXECUTE = 0x0020; + public const uint FILE_TRAVERSE = 0x0020; + public const uint FILE_DELETE_CHILD = 0x0040; + public const uint FILE_READ_ATTRIBUTES = 0x0080; + public const uint FILE_WRITE_ATTRIBUTES = 0x0100; + public const uint FILE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF); + public const uint FILE_GENERIC_READ = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE); + public const uint FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE); + public const uint FILE_GENERIC_EXECUTE = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE); + + public const uint READ_CONTROL = 0x00020000; + public const uint SYNCHRONIZE = 0x00100000; + public const uint STANDARD_RIGHTS_READ = READ_CONTROL; + public const uint STANDARD_RIGHTS_WRITE = READ_CONTROL; + public const uint STANDARD_RIGHTS_EXECUTE = READ_CONTROL; + public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000; + + public const uint GENERIC_READ = 0x80000000; + public const uint GENERIC_WRITE = 0x40000000; + public const uint GENERIC_EXECUTE = 0x20000000; + + public const uint CREATE_NEW = 1; + public const uint CREATE_ALWAYS = 2; + public const uint OPEN_EXISTING = 3; + public const uint OPEN_ALWAYS = 4; + public const uint TRUNCATE_EXISTING = 5; + + public const uint FILE_ATTRIBUTE_READONLY = 0x00000001; + public const uint FILE_ATTRIBUTE_HIDDEN = 0x00000002; + public const uint FILE_ATTRIBUTE_SYSTEM = 0x00000004; + public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + public const uint FILE_ATTRIBUTE_ARCHIVE = 0x00000020; + public const uint FILE_ATTRIBUTE_DEVICE = 0x00000040; + public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; + public const uint FILE_ATTRIBUTE_TEMPORARY = 0x00000100; + public const uint FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200; + public const uint FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; + public const uint FILE_ATTRIBUTE_COMPRESSED = 0x00000800; + public const uint FILE_ATTRIBUTE_OFFLINE = 0x00001000; + public const uint FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000; + public const uint FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; + + public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; + public const uint FILE_FLAG_OVERLAPPED = 0x40000000; + public const uint FILE_FLAG_NO_BUFFERING = 0x20000000; + public const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000; + public const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; + public const uint FILE_FLAG_DELETE_ON_CLOSE = 0x04000000; + public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + public const uint FILE_FLAG_POSIX_SEMANTICS = 0x01000000; + public const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; + public const uint FILE_FLAG_OPEN_NO_RECALL = 0x00100000; + public const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; + + public const uint FILE_BEGIN = 0; + public const uint FILE_CURRENT = 1; + public const uint FILE_END = 2; + + public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + + public const uint HANDLE_FLAG_INHERIT = 0x1; + public const uint HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x2; + + public const uint MEM_COMMIT = 0x1000; + public const uint MEM_RESERVE = 0x2000; + public const uint MEM_DECOMMIT = 0x4000; + public const uint MEM_RELEASE = 0x8000; + public const uint MEM_RESET = 0x80000; + public const uint MEM_TOP_DOWN = 0x100000; + public const uint MEM_PHYSICAL = 0x400000; + + public const uint PAGE_NOACCESS = 0x01; + public const uint PAGE_READONLY = 0x02; + public const uint PAGE_READWRITE = 0x04; + public const uint PAGE_WRITECOPY = 0x08; + public const uint PAGE_EXECUTE = 0x10; + public const uint PAGE_EXECUTE_READ = 0x20; + public const uint PAGE_EXECUTE_READWRITE = 0x40; + public const uint PAGE_EXECUTE_WRITECOPY = 0x80; + public const uint PAGE_GUARD = 0x100; + public const uint PAGE_NOCACHE = 0x200; + public const uint PAGE_WRITECOMBINE = 0x400; + + public const uint SEC_IMAGE = 0x1000000; + public const uint SEC_RESERVE = 0x4000000; + public const uint SEC_COMMIT = 0x8000000; + public const uint SEC_NOCACHE = 0x10000000; + + public const uint SECTION_QUERY = 0x0001; + public const uint SECTION_MAP_WRITE = 0x0002; + public const uint SECTION_MAP_READ = 0x0004; + public const uint SECTION_MAP_EXECUTE_EXPLICIT = 0x0020; + + public const uint FILE_MAP_COPY = SECTION_QUERY; + public const uint FILE_MAP_WRITE = SECTION_MAP_WRITE; + public const uint FILE_MAP_READ = SECTION_MAP_READ; + public const uint FILE_MAP_EXECUTE = SECTION_MAP_EXECUTE_EXPLICIT; + + public const uint GMEM_FIXED = 0x0000; + public const uint GMEM_MOVEABLE = 0x0002; + public const uint GMEM_ZEROINIT = 0x0040; + public const uint GHND = 0x0042; + public const uint GPTR = 0x0040; + + public const uint DIB_RGB_COLORS = 0; /* color table in RGBs */ + public const uint DIB_PAL_COLORS = 1; /* color table in palette indices */ + + public const uint BI_RGB = 0; + public const uint BI_RLE8 = 1; + public const uint BI_RLE4 = 2; + public const uint BI_BITFIELDS = 3; + public const uint BI_JPEG = 4; + public const uint BI_PNG = 5; + + public const uint DT_TOP = 0x00000000; + public const uint DT_LEFT = 0x00000000; + public const uint DT_CENTER = 0x00000001; + public const uint DT_RIGHT = 0x00000002; + public const uint DT_VCENTER = 0x00000004; + public const uint DT_BOTTOM = 0x00000008; + public const uint DT_WORDBREAK = 0x00000010; + public const uint DT_SINGLELINE = 0x00000020; + public const uint DT_EXPANDTABS = 0x00000040; + public const uint DT_TABSTOP = 0x00000080; + public const uint DT_NOCLIP = 0x00000100; + public const uint DT_EXTERNALLEADING = 0x00000200; + public const uint DT_CALCRECT = 0x00000400; + public const uint DT_NOPREFIX = 0x00000800; + public const uint DT_public = 0x00001000; + + public const uint DT_EDITCONTROL = 0x00002000; + public const uint DT_PATH_ELLIPSIS = 0x00004000; + public const uint DT_END_ELLIPSIS = 0x00008000; + public const uint DT_MODIFYSTRING = 0x00010000; + public const uint DT_RTLREADING = 0x00020000; + public const uint DT_WORD_ELLIPSIS = 0x00040000; + public const uint DT_NOFULLWIDTHCHARBREAK = 0x00080000; + public const uint DT_HIDEPREFIX = 0x00100000; + public const uint DT_PREFIXONLY = 0x00200000; + + public const uint FW_DONTCARE = 0; + public const uint FW_THIN = 100; + public const uint FW_EXTRALIGHT = 200; + public const uint FW_LIGHT = 300; + public const uint FW_NORMAL = 400; + public const uint FW_MEDIUM = 500; + public const uint FW_SEMIBOLD = 600; + public const uint FW_BOLD = 700; + public const uint FW_EXTRABOLD = 800; + public const uint FW_HEAVY = 900; + + public const uint OUT_DEFAULT_PRECIS = 0; + public const uint OUT_STRING_PRECIS = 1; + public const uint OUT_CHARACTER_PRECIS = 2; + public const uint OUT_STROKE_PRECIS = 3; + public const uint OUT_TT_PRECIS = 4; + public const uint OUT_DEVICE_PRECIS = 5; + public const uint OUT_RASTER_PRECIS = 6; + public const uint OUT_TT_ONLY_PRECIS = 7; + public const uint OUT_OUTLINE_PRECIS = 8; + public const uint OUT_SCREEN_OUTLINE_PRECIS = 9; + public const uint OUT_PS_ONLY_PRECIS = 10; + + public const uint CLIP_DEFAULT_PRECIS = 0; + public const uint CLIP_CHARACTER_PRECIS = 1; + public const uint CLIP_STROKE_PRECIS = 2; + public const uint CLIP_MASK = 0xf; + public const uint CLIP_LH_ANGLES = (1 << 4); + public const uint CLIP_TT_ALWAYS = (2 << 4); + public const uint CLIP_EMBEDDED = (8 << 4); + + public const uint DEFAULT_QUALITY = 0; + public const uint DRAFT_QUALITY = 1; + public const uint PROOF_QUALITY = 2; + public const uint NONANTIALIASED_QUALITY = 3; + public const uint ANTIALIASED_QUALITY = 4; + + public const uint CLEARTYPE_QUALITY = 5; + + public const uint CLEARTYPE_NATURAL_QUALITY = 6; + + public const uint DEFAULT_PITCH = 0; + public const uint FIXED_PITCH = 1; + public const uint VARIABLE_PITCH = 2; + public const uint MONO_FONT = 8; + + public const uint FF_DONTCARE = (0 << 4); + public const uint FF_ROMAN = (1 << 4); + public const uint FF_SWISS = (2 << 4); + public const uint FF_MODERN = (3 << 4); + public const uint FF_SCRIPT = (4 << 4); + public const uint FF_DECORATIVE = (5 << 4); + + public const int SB_HORZ = 0; + + public const int S_OK = 0; + public const int S_FALSE = 1; + public const int E_NOTIMPL = unchecked((int)0x80004001); + } +} diff --git a/src/SystemLayer/NativeDelegates.cs b/src/SystemLayer/NativeDelegates.cs new file mode 100644 index 0000000..cb986d2 --- /dev/null +++ b/src/SystemLayer/NativeDelegates.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer +{ + internal static class NativeDelegates + { + [return: MarshalAs(UnmanagedType.Bool)] + public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam); + } +} diff --git a/src/SystemLayer/NativeErrors.cs b/src/SystemLayer/NativeErrors.cs new file mode 100644 index 0000000..00ab036 --- /dev/null +++ b/src/SystemLayer/NativeErrors.cs @@ -0,0 +1,1832 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + // Copied from System.IO.MonoIOError.cs, (C) 2002 Dan Lewis + + // + // System.IO.MonoIOError.cs: Win32 error codes. Yuck. + // + // Author: + // Dan Lewis (dihlewis@yahoo.co.uk) + // + // (C) 2002 + // + + // + // Permission is hereby granted, free of charge, to any person obtaining + // a copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to + // permit persons to whom the Software is furnished to do so, subject to + // the following conditions: + // + // The above copyright notice and this permission notice shall be + // included in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + // + + internal enum NativeErrors + { + ERROR_SUCCESS = 0, + ERROR_INVALID_FUNCTION = 1, + ERROR_FILE_NOT_FOUND = 2, + ERROR_PATH_NOT_FOUND = 3, + ERROR_TOO_MANY_OPEN_FILES = 4, + ERROR_ACCESS_DENIED = 5, + ERROR_INVALID_HANDLE = 6, + ERROR_ARENA_TRASHED = 7, + ERROR_NOT_ENOUGH_MEMORY = 8, + ERROR_INVALID_BLOCK = 9, + ERROR_BAD_ENVIRONMENT = 10, + ERROR_BAD_FORMAT = 11, + ERROR_INVALID_ACCESS = 12, + ERROR_INVALID_DATA = 13, + ERROR_OUTOFMEMORY = 14, + ERROR_INVALID_DRIVE = 15, + ERROR_CURRENT_DIRECTORY = 16, + ERROR_NOT_SAME_DEVICE = 17, + ERROR_NO_MORE_FILES = 18, + ERROR_WRITE_PROTECT = 19, + ERROR_BAD_UNIT = 20, + ERROR_NOT_READY = 21, + ERROR_BAD_COMMAND = 22, + ERROR_CRC = 23, + ERROR_BAD_LENGTH = 24, + ERROR_SEEK = 25, + ERROR_NOT_DOS_DISK = 26, + ERROR_SECTOR_NOT_FOUND = 27, + ERROR_OUT_OF_PAPER = 28, + ERROR_WRITE_FAULT = 29, + ERROR_READ_FAULT = 30, + ERROR_GEN_FAILURE = 31, + ERROR_SHARING_VIOLATION = 32, + ERROR_LOCK_VIOLATION = 33, + ERROR_WRONG_DISK = 34, + ERROR_SHARING_BUFFER_EXCEEDED = 36, + ERROR_HANDLE_EOF = 38, + ERROR_HANDLE_DISK_FULL = 39, + ERROR_NOT_SUPPORTED = 50, + ERROR_REM_NOT_LIST = 51, + ERROR_DUP_NAME = 52, + ERROR_BAD_NETPATH = 53, + ERROR_NETWORK_BUSY = 54, + ERROR_DEV_NOT_EXIST = 55, + ERROR_TOO_MANY_CMDS = 56, + ERROR_ADAP_HDW_ERR = 57, + ERROR_BAD_NET_RESP = 58, + ERROR_UNEXP_NET_ERR = 59, + ERROR_BAD_REM_ADAP = 60, + ERROR_PRINTQ_FULL = 61, + ERROR_NO_SPOOL_SPACE = 62, + ERROR_PRINT_CANCELLED = 63, + ERROR_NETNAME_DELETED = 64, + ERROR_NETWORK_ACCESS_DENIED = 65, + ERROR_BAD_DEV_TYPE = 66, + ERROR_BAD_NET_NAME = 67, + ERROR_TOO_MANY_NAMES = 68, + ERROR_TOO_MANY_SESS = 69, + ERROR_SHARING_PAUSED = 70, + ERROR_REQ_NOT_ACCEP = 71, + ERROR_REDIR_PAUSED = 72, + ERROR_FILE_EXISTS = 80, + ERROR_CANNOT_MAKE = 82, + ERROR_FAIL_I24 = 83, + ERROR_OUT_OF_STRUCTURES = 84, + ERROR_ALREADY_ASSIGNED = 85, + ERROR_INVALID_PASSWORD = 86, + ERROR_INVALID_PARAMETER = 87, + ERROR_NET_WRITE_FAULT = 88, + ERROR_NO_PROC_SLOTS = 89, + ERROR_TOO_MANY_SEMAPHORES = 100, + ERROR_EXCL_SEM_ALREADY_OWNED = 101, + ERROR_SEM_IS_SET = 102, + ERROR_TOO_MANY_SEM_REQUESTS = 103, + ERROR_INVALID_AT_INTERRUPT_TIME = 104, + ERROR_SEM_OWNER_DIED = 105, + ERROR_SEM_USER_LIMIT = 106, + ERROR_DISK_CHANGE = 107, + ERROR_DRIVE_LOCKED = 108, + ERROR_BROKEN_PIPE = 109, + ERROR_OPEN_FAILED = 110, + ERROR_BUFFER_OVERFLOW = 111, + ERROR_DISK_FULL = 112, + ERROR_NO_MORE_SEARCH_HANDLES = 113, + ERROR_INVALID_TARGET_HANDLE = 114, + ERROR_INVALID_CATEGORY = 117, + ERROR_INVALID_VERIFY_SWITCH = 118, + ERROR_BAD_DRIVER_LEVEL = 119, + ERROR_CALL_NOT_IMPLEMENTED = 120, + ERROR_SEM_TIMEOUT = 121, + ERROR_INSUFFICIENT_BUFFER = 122, + ERROR_INVALID_NAME = 123, + ERROR_INVALID_LEVEL = 124, + ERROR_NO_VOLUME_LABEL = 125, + ERROR_MOD_NOT_FOUND = 126, + ERROR_PROC_NOT_FOUND = 127, + ERROR_WAIT_NO_CHILDREN = 128, + ERROR_CHILD_NOT_COMPLETE = 129, + ERROR_DIRECT_ACCESS_HANDLE = 130, + ERROR_NEGATIVE_SEEK = 131, + ERROR_SEEK_ON_DEVICE = 132, + ERROR_IS_JOIN_TARGET = 133, + ERROR_IS_JOINED = 134, + ERROR_IS_SUBSTED = 135, + ERROR_NOT_JOINED = 136, + ERROR_NOT_SUBSTED = 137, + ERROR_JOIN_TO_JOIN = 138, + ERROR_SUBST_TO_SUBST = 139, + ERROR_JOIN_TO_SUBST = 140, + ERROR_SUBST_TO_JOIN = 141, + ERROR_BUSY_DRIVE = 142, + ERROR_SAME_DRIVE = 143, + ERROR_DIR_NOT_ROOT = 144, + ERROR_DIR_NOT_EMPTY = 145, + ERROR_IS_SUBST_PATH = 146, + ERROR_IS_JOIN_PATH = 147, + ERROR_PATH_BUSY = 148, + ERROR_IS_SUBST_TARGET = 149, + ERROR_SYSTEM_TRACE = 150, + ERROR_INVALID_EVENT_COUNT = 151, + ERROR_TOO_MANY_MUXWAITERS = 152, + ERROR_INVALID_LIST_FORMAT = 153, + ERROR_LABEL_TOO_LONG = 154, + ERROR_TOO_MANY_TCBS = 155, + ERROR_SIGNAL_REFUSED = 156, + ERROR_DISCARDED = 157, + ERROR_NOT_LOCKED = 158, + ERROR_BAD_THREADID_ADDR = 159, + ERROR_BAD_ARGUMENTS = 160, + ERROR_BAD_PATHNAME = 161, + ERROR_SIGNAL_PENDING = 162, + ERROR_MAX_THRDS_REACHED = 164, + ERROR_LOCK_FAILED = 167, + ERROR_BUSY = 170, + ERROR_CANCEL_VIOLATION = 173, + ERROR_ATOMIC_LOCKS_NOT_SUPPORTED = 174, + ERROR_INVALID_SEGMENT_NUMBER = 180, + ERROR_INVALID_ORDINAL = 182, + ERROR_ALREADY_EXISTS = 183, + ERROR_INVALID_FLAG_NUMBER = 186, + ERROR_SEM_NOT_FOUND = 187, + ERROR_INVALID_STARTING_CODESEG = 188, + ERROR_INVALID_STACKSEG = 189, + ERROR_INVALID_MODULETYPE = 190, + ERROR_INVALID_EXE_SIGNATURE = 191, + ERROR_EXE_MARKED_INVALID = 192, + ERROR_BAD_EXE_FORMAT = 193, + ERROR_ITERATED_DATA_EXCEEDS_64k = 194, + ERROR_INVALID_MINALLOCSIZE = 195, + ERROR_DYNLINK_FROM_INVALID_RING = 196, + ERROR_IOPL_NOT_ENABLED = 197, + ERROR_INVALID_SEGDPL = 198, + ERROR_AUTODATASEG_EXCEEDS_64k = 199, + ERROR_RING2SEG_MUST_BE_MOVABLE = 200, + ERROR_RELOC_CHAIN_XEEDS_SEGLIM = 201, + ERROR_INFLOOP_IN_RELOC_CHAIN = 202, + ERROR_ENVVAR_NOT_FOUND = 203, + ERROR_NO_SIGNAL_SENT = 205, + ERROR_FILENAME_EXCED_RANGE = 206, + ERROR_RING2_STACK_IN_USE = 207, + ERROR_META_EXPANSION_TOO_LONG = 208, + ERROR_INVALID_SIGNAL_NUMBER = 209, + ERROR_THREAD_1_INACTIVE = 210, + ERROR_LOCKED = 212, + ERROR_TOO_MANY_MODULES = 214, + ERROR_NESTING_NOT_ALLOWED = 215, + ERROR_EXE_MACHINE_TYPE_MISMATCH = 216, + ERROR_BAD_PIPE = 230, + ERROR_PIPE_BUSY = 231, + ERROR_NO_DATA = 232, + ERROR_PIPE_NOT_CONNECTED = 233, + ERROR_MORE_DATA = 234, + ERROR_VC_DISCONNECTED = 240, + ERROR_INVALID_EA_NAME = 254, + ERROR_EA_LIST_INCONSISTENT = 255, + WAIT_TIMEOUT = 258, + ERROR_NO_MORE_ITEMS = 259, + ERROR_CANNOT_COPY = 266, + ERROR_DIRECTORY = 267, + ERROR_EAS_DIDNT_FIT = 275, + ERROR_EA_FILE_CORRUPT = 276, + ERROR_EA_TABLE_FULL = 277, + ERROR_INVALID_EA_HANDLE = 278, + ERROR_EAS_NOT_SUPPORTED = 282, + ERROR_NOT_OWNER = 288, + ERROR_TOO_MANY_POSTS = 298, + ERROR_PARTIAL_COPY = 299, + ERROR_OPLOCK_NOT_GRANTED = 300, + ERROR_INVALID_OPLOCK_PROTOCOL = 301, + ERROR_DISK_TOO_FRAGMENTED = 302, + ERROR_DELETE_PENDING = 303, + ERROR_MR_MID_NOT_FOUND = 317, + ERROR_INVALID_ADDRESS = 487, + ERROR_ARITHMETIC_OVERFLOW = 534, + ERROR_PIPE_CONNECTED = 535, + ERROR_PIPE_LISTENING = 536, + ERROR_EA_ACCESS_DENIED = 994, + ERROR_OPERATION_ABORTED = 995, + ERROR_IO_INCOMPLETE = 996, + ERROR_IO_PENDING = 997, + ERROR_NOACCESS = 998, + ERROR_SWAPERROR = 999, + ERROR_STACK_OVERFLOW = 1001, + ERROR_INVALID_MESSAGE = 1002, + ERROR_CAN_NOT_COMPLETE = 1003, + ERROR_INVALID_FLAGS = 1004, + ERROR_UNRECOGNIZED_VOLUME = 1005, + ERROR_FILE_INVALID = 1006, + ERROR_FULLSCREEN_MODE = 1007, + ERROR_NO_TOKEN = 1008, + ERROR_BADDB = 1009, + ERROR_BADKEY = 1010, + ERROR_CANTOPEN = 1011, + ERROR_CANTREAD = 1012, + ERROR_CANTWRITE = 1013, + ERROR_REGISTRY_RECOVERED = 1014, + ERROR_REGISTRY_CORRUPT = 1015, + ERROR_REGISTRY_IO_FAILED = 1016, + ERROR_NOT_REGISTRY_FILE = 1017, + ERROR_KEY_DELETED = 1018, + ERROR_NO_LOG_SPACE = 1019, + ERROR_KEY_HAS_CHILDREN = 1020, + ERROR_CHILD_MUST_BE_VOLATILE = 1021, + ERROR_NOTIFY_ENUM_DIR = 1022, + ERROR_DEPENDENT_SERVICES_RUNNING = 1051, + ERROR_INVALID_SERVICE_CONTROL = 1052, + ERROR_SERVICE_REQUEST_TIMEOUT = 1053, + ERROR_SERVICE_NO_THREAD = 1054, + ERROR_SERVICE_DATABASE_LOCKED = 1055, + ERROR_SERVICE_ALREADY_RUNNING = 1056, + ERROR_INVALID_SERVICE_ACCOUNT = 1057, + ERROR_SERVICE_DISABLED = 1058, + ERROR_CIRCULAR_DEPENDENCY = 1059, + ERROR_SERVICE_DOES_NOT_EXIST = 1060, + ERROR_SERVICE_CANNOT_ACCEPT_CTRL = 1061, + ERROR_SERVICE_NOT_ACTIVE = 1062, + ERROR_FAILED_SERVICE_CONTROLLER_CONNECT = 1063, + ERROR_EXCEPTION_IN_SERVICE = 1064, + ERROR_DATABASE_DOES_NOT_EXIST = 1065, + ERROR_SERVICE_SPECIFIC_ERROR = 1066, + ERROR_PROCESS_ABORTED = 1067, + ERROR_SERVICE_DEPENDENCY_FAIL = 1068, + ERROR_SERVICE_LOGON_FAILED = 1069, + ERROR_SERVICE_START_HANG = 1070, + ERROR_INVALID_SERVICE_LOCK = 1071, + ERROR_SERVICE_MARKED_FOR_DELETE = 1072, + ERROR_SERVICE_EXISTS = 1073, + ERROR_ALREADY_RUNNING_LKG = 1074, + ERROR_SERVICE_DEPENDENCY_DELETED = 1075, + ERROR_BOOT_ALREADY_ACCEPTED = 1076, + ERROR_SERVICE_NEVER_STARTED = 1077, + ERROR_DUPLICATE_SERVICE_NAME = 1078, + ERROR_DIFFERENT_SERVICE_ACCOUNT = 1079, + ERROR_CANNOT_DETECT_DRIVER_FAILURE = 1080, + ERROR_CANNOT_DETECT_PROCESS_ABORT = 1081, + ERROR_NO_RECOVERY_PROGRAM = 1082, + ERROR_SERVICE_NOT_IN_EXE = 1083, + ERROR_NOT_SAFEBOOT_SERVICE = 1084, + ERROR_END_OF_MEDIA = 1100, + ERROR_FILEMARK_DETECTED = 1101, + ERROR_BEGINNING_OF_MEDIA = 1102, + ERROR_SETMARK_DETECTED = 1103, + ERROR_NO_DATA_DETECTED = 1104, + ERROR_PARTITION_FAILURE = 1105, + ERROR_INVALID_BLOCK_LENGTH = 1106, + ERROR_DEVICE_NOT_PARTITIONED = 1107, + ERROR_UNABLE_TO_LOCK_MEDIA = 1108, + ERROR_UNABLE_TO_UNLOAD_MEDIA = 1109, + ERROR_MEDIA_CHANGED = 1110, + ERROR_BUS_RESET = 1111, + ERROR_NO_MEDIA_IN_DRIVE = 1112, + ERROR_NO_UNICODE_TRANSLATION = 1113, + ERROR_DLL_INIT_FAILED = 1114, + ERROR_SHUTDOWN_IN_PROGRESS = 1115, + ERROR_NO_SHUTDOWN_IN_PROGRESS = 1116, + ERROR_IO_DEVICE = 1117, + ERROR_SERIAL_NO_DEVICE = 1118, + ERROR_IRQ_BUSY = 1119, + ERROR_MORE_WRITES = 1120, + ERROR_COUNTER_TIMEOUT = 1121, + ERROR_FLOPPY_ID_MARK_NOT_FOUND = 1122, + ERROR_FLOPPY_WRONG_CYLINDER = 1123, + ERROR_FLOPPY_UNKNOWN_ERROR = 1124, + ERROR_FLOPPY_BAD_REGISTERS = 1125, + ERROR_DISK_RECALIBRATE_FAILED = 1126, + ERROR_DISK_OPERATION_FAILED = 1127, + ERROR_DISK_RESET_FAILED = 1128, + ERROR_EOM_OVERFLOW = 1129, + ERROR_NOT_ENOUGH_SERVER_MEMORY = 1130, + ERROR_POSSIBLE_DEADLOCK = 1131, + ERROR_MAPPED_ALIGNMENT = 1132, + ERROR_SET_POWER_STATE_VETOED = 1140, + ERROR_SET_POWER_STATE_FAILED = 1141, + ERROR_TOO_MANY_LINKS = 1142, + ERROR_OLD_WIN_VERSION = 1150, + ERROR_APP_WRONG_OS = 1151, + ERROR_SINGLE_INSTANCE_APP = 1152, + ERROR_RMODE_APP = 1153, + ERROR_INVALID_DLL = 1154, + ERROR_NO_ASSOCIATION = 1155, + ERROR_DDE_FAIL = 1156, + ERROR_DLL_NOT_FOUND = 1157, + ERROR_NO_MORE_USER_HANDLES = 1158, + ERROR_MESSAGE_SYNC_ONLY = 1159, + ERROR_SOURCE_ELEMENT_EMPTY = 1160, + ERROR_DESTINATION_ELEMENT_FULL = 1161, + ERROR_ILLEGAL_ELEMENT_ADDRESS = 1162, + ERROR_MAGAZINE_NOT_PRESENT = 1163, + ERROR_DEVICE_REINITIALIZATION_NEEDED = 1164, + ERROR_DEVICE_REQUIRES_CLEANING = 1165, + ERROR_DEVICE_DOOR_OPEN = 1166, + ERROR_DEVICE_NOT_CONNECTED = 1167, + ERROR_NOT_FOUND = 1168, + ERROR_NO_MATCH = 1169, + ERROR_SET_NOT_FOUND = 1170, + ERROR_POINT_NOT_FOUND = 1171, + ERROR_NO_TRACKING_SERVICE = 1172, + ERROR_NO_VOLUME_ID = 1173, + ERROR_UNABLE_TO_REMOVE_REPLACED = 1175, + ERROR_UNABLE_TO_MOVE_REPLACEMENT = 1176, + ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 = 1177, + ERROR_JOURNAL_DELETE_IN_PROGRESS = 1178, + ERROR_JOURNAL_NOT_ACTIVE = 1179, + ERROR_POTENTIAL_FILE_FOUND = 1180, + ERROR_JOURNAL_ENTRY_DELETED = 1181, + ERROR_BAD_DEVICE = 1200, + ERROR_CONNECTION_UNAVAIL = 1201, + ERROR_DEVICE_ALREADY_REMEMBERED = 1202, + ERROR_NO_NET_OR_BAD_PATH = 1203, + ERROR_BAD_PROVIDER = 1204, + ERROR_CANNOT_OPEN_PROFILE = 1205, + ERROR_BAD_PROFILE = 1206, + ERROR_NOT_CONTAINER = 1207, + ERROR_EXTENDED_ERROR = 1208, + ERROR_INVALID_GROUPNAME = 1209, + ERROR_INVALID_COMPUTERNAME = 1210, + ERROR_INVALID_EVENTNAME = 1211, + ERROR_INVALID_DOMAINNAME = 1212, + ERROR_INVALID_SERVICENAME = 1213, + ERROR_INVALID_NETNAME = 1214, + ERROR_INVALID_SHARENAME = 1215, + ERROR_INVALID_PASSWORDNAME = 1216, + ERROR_INVALID_MESSAGENAME = 1217, + ERROR_INVALID_MESSAGEDEST = 1218, + ERROR_SESSION_CREDENTIAL_CONFLICT = 1219, + ERROR_REMOTE_SESSION_LIMIT_EXCEEDED = 1220, + ERROR_DUP_DOMAINNAME = 1221, + ERROR_NO_NETWORK = 1222, + ERROR_CANCELLED = 1223, + ERROR_USER_MAPPED_FILE = 1224, + ERROR_CONNECTION_REFUSED = 1225, + ERROR_GRACEFUL_DISCONNECT = 1226, + ERROR_ADDRESS_ALREADY_ASSOCIATED = 1227, + ERROR_ADDRESS_NOT_ASSOCIATED = 1228, + ERROR_CONNECTION_INVALID = 1229, + ERROR_CONNECTION_ACTIVE = 1230, + ERROR_NETWORK_UNREACHABLE = 1231, + ERROR_HOST_UNREACHABLE = 1232, + ERROR_PROTOCOL_UNREACHABLE = 1233, + ERROR_PORT_UNREACHABLE = 1234, + ERROR_REQUEST_ABORTED = 1235, + ERROR_CONNECTION_ABORTED = 1236, + ERROR_RETRY = 1237, + ERROR_CONNECTION_COUNT_LIMIT = 1238, + ERROR_LOGIN_TIME_RESTRICTION = 1239, + ERROR_LOGIN_WKSTA_RESTRICTION = 1240, + ERROR_INCORRECT_ADDRESS = 1241, + ERROR_ALREADY_REGISTERED = 1242, + ERROR_SERVICE_NOT_FOUND = 1243, + ERROR_NOT_AUTHENTICATED = 1244, + ERROR_NOT_LOGGED_ON = 1245, + ERROR_CONTINUE = 1246, + ERROR_ALREADY_INITIALIZED = 1247, + ERROR_NO_MORE_DEVICES = 1248, + ERROR_NO_SUCH_SITE = 1249, + ERROR_DOMAIN_CONTROLLER_EXISTS = 1250, + ERROR_ONLY_IF_CONNECTED = 1251, + ERROR_OVERRIDE_NOCHANGES = 1252, + ERROR_BAD_USER_PROFILE = 1253, + ERROR_NOT_SUPPORTED_ON_SBS = 1254, + ERROR_SERVER_SHUTDOWN_IN_PROGRESS = 1255, + ERROR_HOST_DOWN = 1256, + ERROR_NON_ACCOUNT_SID = 1257, + ERROR_NON_DOMAIN_SID = 1258, + ERROR_APPHELP_BLOCK = 1259, + ERROR_ACCESS_DISABLED_BY_POLICY = 1260, + ERROR_REG_NAT_CONSUMPTION = 1261, + ERROR_CSCSHARE_OFFLINE = 1262, + ERROR_PKINIT_FAILURE = 1263, + ERROR_SMARTCARD_SUBSYSTEM_FAILURE = 1264, + ERROR_DOWNGRADE_DETECTED = 1265, + SEC_E_SMARTCARD_CERT_REVOKED = 1266, + SEC_E_ISSUING_CA_UNTRUSTED = 1267, + SEC_E_REVOCATION_OFFLINE_C = 1268, + SEC_E_PKINIT_CLIENT_FAILUR = 1269, + SEC_E_SMARTCARD_CERT_EXPIRED = 1270, + ERROR_MACHINE_LOCKED = 1271, + ERROR_CALLBACK_SUPPLIED_INVALID_DATA = 1273, + ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED = 1274, + ERROR_DRIVER_BLOCKED = 1275, + ERROR_INVALID_IMPORT_OF_NON_DLL = 1276, + ERROR_NOT_ALL_ASSIGNED = 1300, + ERROR_SOME_NOT_MAPPED = 1301, + ERROR_NO_QUOTAS_FOR_ACCOUNT = 1302, + ERROR_LOCAL_USER_SESSION_KEY = 1303, + ERROR_NULL_LM_PASSWORD = 1304, + ERROR_UNKNOWN_REVISION = 1305, + ERROR_REVISION_MISMATCH = 1306, + ERROR_INVALID_OWNER = 1307, + ERROR_INVALID_PRIMARY_GROUP = 1308, + ERROR_NO_IMPERSONATION_TOKEN = 1309, + ERROR_CANT_DISABLE_MANDATORY = 1310, + ERROR_NO_LOGON_SERVERS = 1311, + ERROR_NO_SUCH_LOGON_SESSION = 1312, + ERROR_NO_SUCH_PRIVILEGE = 1313, + ERROR_PRIVILEGE_NOT_HELD = 1314, + ERROR_INVALID_ACCOUNT_NAME = 1315, + ERROR_USER_EXISTS = 1316, + ERROR_NO_SUCH_USER = 1317, + ERROR_GROUP_EXISTS = 1318, + ERROR_NO_SUCH_GROUP = 1319, + ERROR_MEMBER_IN_GROUP = 1320, + ERROR_MEMBER_NOT_IN_GROUP = 1321, + ERROR_LAST_ADMIN = 1322, + ERROR_WRONG_PASSWORD = 1323, + ERROR_ILL_FORMED_PASSWORD = 1324, + ERROR_PASSWORD_RESTRICTION = 1325, + ERROR_LOGON_FAILURE = 1326, + ERROR_ACCOUNT_RESTRICTION = 1327, + ERROR_INVALID_LOGON_HOURS = 1328, + ERROR_INVALID_WORKSTATION = 1329, + ERROR_PASSWORD_EXPIRED = 1330, + ERROR_ACCOUNT_DISABLED = 1331, + ERROR_NONE_MAPPED = 1332, + ERROR_TOO_MANY_LUIDS_REQUESTED = 1333, + ERROR_LUIDS_EXHAUSTED = 1334, + ERROR_INVALID_SUB_AUTHORITY = 1335, + ERROR_INVALID_ACL = 1336, + ERROR_INVALID_SID = 1337, + ERROR_INVALID_SECURITY_DESCR = 1338, + ERROR_BAD_INHERITANCE_ACL = 1340, + ERROR_SERVER_DISABLED = 1341, + ERROR_SERVER_NOT_DISABLED = 1342, + ERROR_INVALID_ID_AUTHORITY = 1343, + ERROR_ALLOTTED_SPACE_EXCEEDED = 1344, + ERROR_INVALID_GROUP_ATTRIBUTES = 1345, + ERROR_BAD_IMPERSONATION_LEVEL = 1346, + ERROR_CANT_OPEN_ANONYMOUS = 1347, + ERROR_BAD_VALIDATION_CLASS = 1348, + ERROR_BAD_TOKEN_TYPE = 1349, + ERROR_NO_SECURITY_ON_OBJECT = 1350, + ERROR_CANT_ACCESS_DOMAIN_INFO = 1351, + ERROR_INVALID_SERVER_STATE = 1352, + ERROR_INVALID_DOMAIN_STATE = 1353, + ERROR_INVALID_DOMAIN_ROLE = 1354, + ERROR_NO_SUCH_DOMAIN = 1355, + ERROR_DOMAIN_EXISTS = 1356, + ERROR_DOMAIN_LIMIT_EXCEEDED = 1357, + ERROR_INTERNAL_DB_CORRUPTION = 1358, + ERROR_INTERNAL_ERROR = 1359, + ERROR_GENERIC_NOT_MAPPED = 1360, + ERROR_BAD_DESCRIPTOR_FORMAT = 1361, + ERROR_NOT_LOGON_PROCESS = 1362, + ERROR_LOGON_SESSION_EXISTS = 1363, + ERROR_NO_SUCH_PACKAGE = 1364, + ERROR_BAD_LOGON_SESSION_STATE = 1365, + ERROR_LOGON_SESSION_COLLISION = 1366, + ERROR_INVALID_LOGON_TYPE = 1367, + ERROR_CANNOT_IMPERSONATE = 1368, + ERROR_RXACT_INVALID_STATE = 1369, + ERROR_RXACT_COMMIT_FAILURE = 1370, + ERROR_SPECIAL_ACCOUNT = 1371, + ERROR_SPECIAL_GROUP = 1372, + ERROR_SPECIAL_USER = 1373, + ERROR_MEMBERS_PRIMARY_GROUP = 1374, + ERROR_TOKEN_ALREADY_IN_USE = 1375, + ERROR_NO_SUCH_ALIAS = 1376, + ERROR_MEMBER_NOT_IN_ALIAS = 1377, + ERROR_MEMBER_IN_ALIAS = 1378, + ERROR_ALIAS_EXISTS = 1379, + ERROR_LOGON_NOT_GRANTED = 1380, + ERROR_TOO_MANY_SECRETS = 1381, + ERROR_SECRET_TOO_LONG = 1382, + ERROR_INTERNAL_DB_ERROR = 1383, + ERROR_TOO_MANY_CONTEXT_IDS = 1384, + ERROR_LOGON_TYPE_NOT_GRANTED = 1385, + ERROR_NT_CROSS_ENCRYPTION_REQUIRED = 1386, + ERROR_NO_SUCH_MEMBER = 1387, + ERROR_INVALID_MEMBER = 1388, + ERROR_TOO_MANY_SIDS = 1389, + ERROR_LM_CROSS_ENCRYPTION_REQUIRED = 1390, + ERROR_NO_INHERITANCE = 1391, + ERROR_FILE_CORRUPT = 1392, + ERROR_DISK_CORRUPT = 1393, + ERROR_NO_USER_SESSION_KEY = 1394, + ERROR_LICENSE_QUOTA_EXCEEDED = 1395, + ERROR_WRONG_TARGET_NAME = 1396, + ERROR_MUTUAL_AUTH_FAILED = 1397, + ERROR_TIME_SKEW = 1398, + ERROR_CURRENT_DOMAIN_NOT_ALLOWED = 1399, + ERROR_INVALID_WINDOW_HANDLE = 1400, + ERROR_INVALID_MENU_HANDLE = 1401, + ERROR_INVALID_CURSOR_HANDLE = 1402, + ERROR_INVALID_ACCEL_HANDLE = 1403, + ERROR_INVALID_HOOK_HANDLE = 1404, + ERROR_INVALID_DWP_HANDLE = 1405, + ERROR_TLW_WITH_WSCHILD = 1406, + ERROR_CANNOT_FIND_WND_CLASS = 1407, + ERROR_WINDOW_OF_OTHER_THREAD = 1408, + ERROR_HOTKEY_ALREADY_REGISTERED = 1409, + ERROR_CLASS_ALREADY_EXISTS = 1410, + ERROR_CLASS_DOES_NOT_EXIST = 1411, + ERROR_CLASS_HAS_WINDOWS = 1412, + ERROR_INVALID_INDEX = 1413, + ERROR_INVALID_ICON_HANDLE = 1414, + ERROR_PRIVATE_DIALOG_INDEX = 1415, + ERROR_LISTBOX_ID_NOT_FOUND = 1416, + ERROR_NO_WILDCARD_CHARACTERS = 1417, + ERROR_CLIPBOARD_NOT_OPEN = 1418, + ERROR_HOTKEY_NOT_REGISTERED = 1419, + ERROR_WINDOW_NOT_DIALOG = 1420, + ERROR_CONTROL_ID_NOT_FOUND = 1421, + ERROR_INVALID_COMBOBOX_MESSAGE = 1422, + ERROR_WINDOW_NOT_COMBOBOX = 1423, + ERROR_INVALID_EDIT_HEIGHT = 1424, + ERROR_DC_NOT_FOUND = 1425, + ERROR_INVALID_HOOK_FILTER = 1426, + ERROR_INVALID_FILTER_PROC = 1427, + ERROR_HOOK_NEEDS_HMOD = 1428, + ERROR_GLOBAL_ONLY_HOOK = 1429, + ERROR_JOURNAL_HOOK_SET = 1430, + ERROR_HOOK_NOT_INSTALLED = 1431, + ERROR_INVALID_LB_MESSAGE = 1432, + ERROR_SETCOUNT_ON_BAD_LB = 1433, + ERROR_LB_WITHOUT_TABSTOPS = 1434, + ERROR_DESTROY_OBJECT_OF_OTHER_THREAD = 1435, + ERROR_CHILD_WINDOW_MENU = 1436, + ERROR_NO_SYSTEM_MENU = 1437, + ERROR_INVALID_MSGBOX_STYLE = 1438, + ERROR_INVALID_SPI_VALUE = 1439, + ERROR_SCREEN_ALREADY_LOCKED = 1440, + ERROR_HWNDS_HAVE_DIFF_PARENT = 1441, + ERROR_NOT_CHILD_WINDOW = 1442, + ERROR_INVALID_GW_COMMAND = 1443, + ERROR_INVALID_THREAD_ID = 1444, + ERROR_NON_MDICHILD_WINDOW = 1445, + ERROR_POPUP_ALREADY_ACTIVE = 1446, + ERROR_NO_SCROLLBARS = 1447, + ERROR_INVALID_SCROLLBAR_RANGE = 1448, + ERROR_INVALID_SHOWWIN_COMMAND = 1449, + ERROR_NO_SYSTEM_RESOURCES = 1450, + ERROR_NONPAGED_SYSTEM_RESOURCES = 1451, + ERROR_PAGED_SYSTEM_RESOURCES = 1452, + ERROR_WORKING_SET_QUOTA = 1453, + ERROR_PAGEFILE_QUOTA = 1454, + ERROR_COMMITMENT_LIMIT = 1455, + ERROR_MENU_ITEM_NOT_FOUND = 1456, + ERROR_INVALID_KEYBOARD_HANDLE = 1457, + ERROR_HOOK_TYPE_NOT_ALLOWED = 1458, + ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION = 1459, + ERROR_TIMEOUT = 1460, + ERROR_INVALID_MONITOR_HANDLE = 1461, + ERROR_EVENTLOG_FILE_CORRUPT = 1500, + ERROR_EVENTLOG_CANT_START = 1501, + ERROR_LOG_FILE_FULL = 1502, + ERROR_EVENTLOG_FILE_CHANGED = 1503, + ERROR_INSTALL_SERVICE_FAILURE = 1601, + ERROR_INSTALL_USEREXIT = 1602, + ERROR_INSTALL_FAILURE = 1603, + ERROR_INSTALL_SUSPEND = 1604, + ERROR_UNKNOWN_PRODUCT = 1605, + ERROR_UNKNOWN_FEATURE = 1606, + ERROR_UNKNOWN_COMPONENT = 1607, + ERROR_UNKNOWN_PROPERTY = 1608, + ERROR_INVALID_HANDLE_STATE = 1609, + ERROR_BAD_CONFIGURATION = 1610, + ERROR_INDEX_ABSENT = 1611, + ERROR_INSTALL_SOURCE_ABSENT = 1612, + ERROR_INSTALL_PACKAGE_VERSION = 1613, + ERROR_PRODUCT_UNINSTALLED = 1614, + ERROR_BAD_QUERY_SYNTAX = 1615, + ERROR_INVALID_FIELD = 1616, + ERROR_DEVICE_REMOVED = 1617, + ERROR_INSTALL_ALREADY_RUNNING = 1618, + ERROR_INSTALL_PACKAGE_OPEN_FAILED = 1619, + ERROR_INSTALL_PACKAGE_INVALID = 1620, + ERROR_INSTALL_UI_FAILURE = 1621, + ERROR_INSTALL_LOG_FAILURE = 1622, + ERROR_INSTALL_LANGUAGE_UNSUPPORTED = 1623, + ERROR_INSTALL_TRANSFORM_FAILURE = 1624, + ERROR_INSTALL_PACKAGE_REJECTED = 1625, + ERROR_FUNCTION_NOT_CALLED = 1626, + ERROR_FUNCTION_FAILED = 1627, + ERROR_INVALID_TABLE = 1628, + ERROR_DATATYPE_MISMATCH = 1629, + ERROR_UNSUPPORTED_TYPE = 1630, + ERROR_CREATE_FAILED = 1631, + ERROR_INSTALL_TEMP_UNWRITABLE = 1632, + ERROR_INSTALL_PLATFORM_UNSUPPORTED = 1633, + ERROR_INSTALL_NOTUSED = 1634, + ERROR_PATCH_PACKAGE_OPEN_FAILED = 1635, + ERROR_PATCH_PACKAGE_INVALID = 1636, + ERROR_PATCH_PACKAGE_UNSUPPORTED = 1637, + ERROR_PRODUCT_VERSION = 1638, + ERROR_INVALID_COMMAND_LINE = 1639, + ERROR_INSTALL_REMOTE_DISALLOWED = 1640, + ERROR_SUCCESS_REBOOT_INITIATED = 1641, + ERROR_PATCH_TARGET_NOT_FOUND = 1642, + ERROR_PATCH_PACKAGE_REJECTED = 1643, + ERROR_INSTALL_TRANSFORM_REJECTED = 1644, + RPC_S_INVALID_STRING_BINDING = 1700, + RPC_S_WRONG_KIND_OF_BINDING = 1701, + RPC_S_INVALID_BINDING = 1702, + RPC_S_PROTSEQ_NOT_SUPPORTED = 1703, + RPC_S_INVALID_RPC_PROTSEQ = 1704, + RPC_S_INVALID_STRING_UUID = 1705, + RPC_S_INVALID_ENDPOINT_FORMAT = 1706, + RPC_S_INVALID_NET_ADDR = 1707, + RPC_S_NO_ENDPOINT_FOUND = 1708, + RPC_S_INVALID_TIMEOUT = 1709, + RPC_S_OBJECT_NOT_FOUND = 1710, + RPC_S_ALREADY_REGISTERED = 1711, + RPC_S_TYPE_ALREADY_REGISTERED = 1712, + RPC_S_ALREADY_LISTENING = 1713, + RPC_S_NO_PROTSEQS_REGISTERED = 1714, + RPC_S_NOT_LISTENING = 1715, + RPC_S_UNKNOWN_MGR_TYPE = 1716, + RPC_S_UNKNOWN_IF = 1717, + RPC_S_NO_BINDINGS = 1718, + RPC_S_NO_PROTSEQS = 1719, + RPC_S_CANT_CREATE_ENDPOINT = 1720, + RPC_S_OUT_OF_RESOURCES = 1721, + RPC_S_SERVER_UNAVAILABLE = 1722, + RPC_S_SERVER_TOO_BUSY = 1723, + RPC_S_INVALID_NETWORK_OPTIONS = 1724, + RPC_S_NO_CALL_ACTIVE = 1725, + RPC_S_CALL_FAILED = 1726, + RPC_S_CALL_FAILED_DNE = 1727, + RPC_S_PROTOCOL_ERROR = 1728, + RPC_S_UNSUPPORTED_TRANS_SYN = 1730, + RPC_S_UNSUPPORTED_TYPE = 1732, + RPC_S_INVALID_TAG = 1733, + RPC_S_INVALID_BOUND = 1734, + RPC_S_NO_ENTRY_NAME = 1735, + RPC_S_INVALID_NAME_SYNTAX = 1736, + RPC_S_UNSUPPORTED_NAME_SYNTAX = 1737, + RPC_S_UUID_NO_ADDRESS = 1739, + RPC_S_DUPLICATE_ENDPOINT = 1740, + RPC_S_UNKNOWN_AUTHN_TYPE = 1741, + RPC_S_MAX_CALLS_TOO_SMALL = 1742, + RPC_S_STRING_TOO_LONG = 1743, + RPC_S_PROTSEQ_NOT_FOUND = 1744, + RPC_S_PROCNUM_OUT_OF_RANGE = 1745, + RPC_S_BINDING_HAS_NO_AUTH = 1746, + RPC_S_UNKNOWN_AUTHN_SERVICE = 1747, + RPC_S_UNKNOWN_AUTHN_LEVEL = 1748, + RPC_S_INVALID_AUTH_IDENTITY = 1749, + RPC_S_UNKNOWN_AUTHZ_SERVICE = 1750, + EPT_S_INVALID_ENTRY = 1751, + EPT_S_CANT_PERFORM_OP = 1752, + EPT_S_NOT_REGISTERED = 1753, + RPC_S_NOTHING_TO_EXPORT = 1754, + RPC_S_INCOMPLETE_NAME = 1755, + RPC_S_INVALID_VERS_OPTION = 1756, + RPC_S_NO_MORE_MEMBERS = 1757, + RPC_S_NOT_ALL_OBJS_UNEXPORTED = 1758, + RPC_S_INTERFACE_NOT_FOUND = 1759, + RPC_S_ENTRY_ALREADY_EXISTS = 1760, + RPC_S_ENTRY_NOT_FOUND = 1761, + RPC_S_NAME_SERVICE_UNAVAILABLE = 1762, + RPC_S_INVALID_NAF_ID = 1763, + RPC_S_CANNOT_SUPPORT = 1764, + RPC_S_NO_CONTEXT_AVAILABLE = 1765, + RPC_S_INTERNAL_ERROR = 1766, + RPC_S_ZERO_DIVIDE = 1767, + RPC_S_ADDRESS_ERROR = 1768, + RPC_S_FP_DIV_ZERO = 1769, + RPC_S_FP_UNDERFLOW = 1770, + RPC_S_FP_OVERFLOW = 1771, + RPC_X_NO_MORE_ENTRIES = 1772, + RPC_X_SS_CHAR_TRANS_OPEN_FAIL = 1773, + RPC_X_SS_CHAR_TRANS_SHORT_FILE = 1774, + RPC_X_SS_IN_NULL_CONTEXT = 1775, + RPC_X_SS_CONTEXT_DAMAGED = 1777, + RPC_X_SS_HANDLES_MISMATCH = 1778, + RPC_X_SS_CANNOT_GET_CALL_HANDLE = 1779, + RPC_X_NULL_REF_POINTER = 1780, + RPC_X_ENUM_VALUE_OUT_OF_RANGE = 1781, + RPC_X_BYTE_COUNT_TOO_SMALL = 1782, + RPC_X_BAD_STUB_DATA = 1783, + ERROR_INVALID_USER_BUFFER = 1784, + ERROR_UNRECOGNIZED_MEDIA = 1785, + ERROR_NO_TRUST_LSA_SECRET = 1786, + ERROR_NO_TRUST_SAM_ACCOUNT = 1787, + ERROR_TRUSTED_DOMAIN_FAILURE = 1788, + ERROR_TRUSTED_RELATIONSHIP_FAILURE = 1789, + ERROR_TRUST_FAILURE = 1790, + RPC_S_CALL_IN_PROGRESS = 1791, + ERROR_NETLOGON_NOT_STARTED = 1792, + ERROR_ACCOUNT_EXPIRED = 1793, + ERROR_REDIRECTOR_HAS_OPEN_HANDLES = 1794, + ERROR_PRINTER_DRIVER_ALREADY_INSTALLED = 1795, + ERROR_UNKNOWN_PORT = 1796, + ERROR_UNKNOWN_PRINTER_DRIVER = 1797, + ERROR_UNKNOWN_PRINTPROCESSOR = 1798, + ERROR_INVALID_SEPARATOR_FILE = 1799, + ERROR_INVALID_PRIORITY = 1800, + ERROR_INVALID_PRINTER_NAME = 1801, + ERROR_PRINTER_ALREADY_EXISTS = 1802, + ERROR_INVALID_PRINTER_COMMAND = 1803, + ERROR_INVALID_DATATYPE = 1804, + ERROR_INVALID_ENVIRONMENT = 1805, + RPC_S_NO_MORE_BINDINGS = 1806, + ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT = 1807, + ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT = 1808, + ERROR_NOLOGON_SERVER_TRUST_ACCOUNT = 1809, + ERROR_DOMAIN_TRUST_INCONSISTENT = 1810, + ERROR_SERVER_HAS_OPEN_HANDLES = 1811, + ERROR_RESOURCE_DATA_NOT_FOUND = 1812, + ERROR_RESOURCE_TYPE_NOT_FOUND = 1813, + ERROR_RESOURCE_NAME_NOT_FOUND = 1814, + ERROR_RESOURCE_LANG_NOT_FOUND = 1815, + ERROR_NOT_ENOUGH_QUOTA = 1816, + RPC_S_NO_INTERFACES = 1817, + RPC_S_CALL_CANCELLED = 1818, + RPC_S_BINDING_INCOMPLETE = 1819, + RPC_S_COMM_FAILURE = 1820, + RPC_S_UNSUPPORTED_AUTHN_LEVEL = 1821, + RPC_S_NO_PRINC_NAME = 1822, + RPC_S_NOT_RPC_ERROR = 1823, + RPC_S_UUID_LOCAL_ONLY = 1824, + RPC_S_SEC_PKG_ERROR = 1825, + RPC_S_NOT_CANCELLED = 1826, + RPC_X_INVALID_ES_ACTION = 1827, + RPC_X_WRONG_ES_VERSION = 1828, + RPC_X_WRONG_STUB_VERSION = 1829, + RPC_X_INVALID_PIPE_OBJECT = 1830, + RPC_X_WRONG_PIPE_ORDER = 1831, + RPC_X_WRONG_PIPE_VERSION = 1832, + RPC_S_GROUP_MEMBER_NOT_FOUND = 1898, + EPT_S_CANT_CREATE = 1899, + RPC_S_INVALID_OBJECT = 1900, + ERROR_INVALID_TIME = 1901, + ERROR_INVALID_FORM_NAME = 1902, + ERROR_INVALID_FORM_SIZE = 1903, + ERROR_ALREADY_WAITING = 1904, + ERROR_PRINTER_DELETED = 1905, + ERROR_INVALID_PRINTER_STATE = 1906, + ERROR_PASSWORD_MUST_CHANGE = 1907, + ERROR_DOMAIN_CONTROLLER_NOT_FOUND = 1908, + ERROR_ACCOUNT_LOCKED_OUT = 1909, + OR_INVALID_OXID = 1910, + OR_INVALID_OID = 1911, + OR_INVALID_SET = 1912, + RPC_S_SEND_INCOMPLETE = 1913, + RPC_S_INVALID_ASYNC_HANDLE = 1914, + RPC_S_INVALID_ASYNC_CALL = 1915, + RPC_X_PIPE_CLOSED = 1916, + RPC_X_PIPE_DISCIPLINE_ERROR = 1917, + RPC_X_PIPE_EMPTY = 1918, + ERROR_NO_SITENAME = 1919, + ERROR_CANT_ACCESS_FILE = 1920, + ERROR_CANT_RESOLVE_FILENAME = 1921, + RPC_S_ENTRY_TYPE_MISMATCH = 1922, + RPC_S_NOT_ALL_OBJS_EXPORTED = 1923, + RPC_S_INTERFACE_NOT_EXPORTED = 1924, + RPC_S_PROFILE_NOT_ADDED = 1925, + RPC_S_PRF_ELT_NOT_ADDED = 1926, + RPC_S_PRF_ELT_NOT_REMOVED = 1927, + RPC_S_GRP_ELT_NOT_ADDED = 1928, + RPC_S_GRP_ELT_NOT_REMOVED = 1929, + ERROR_KM_DRIVER_BLOCKED = 1930, + ERROR_CONTEXT_EXPIRED = 1931, + ERROR_INVALID_PIXEL_FORMAT = 2000, + ERROR_BAD_DRIVER = 2001, + ERROR_INVALID_WINDOW_STYLE = 2002, + ERROR_METAFILE_NOT_SUPPORTED = 2003, + ERROR_TRANSFORM_NOT_SUPPORTED = 2004, + ERROR_CLIPPING_NOT_SUPPORTED = 2005, + ERROR_INVALID_CMM = 2010, + ERROR_INVALID_PROFILE = 2011, + ERROR_TAG_NOT_FOUND = 2012, + ERROR_TAG_NOT_PRESENT = 2013, + ERROR_DUPLICATE_TAG = 2014, + ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE = 2015, + ERROR_PROFILE_NOT_FOUND = 2016, + ERROR_INVALID_COLORSPACE = 2017, + ERROR_ICM_NOT_ENABLED = 2018, + ERROR_DELETING_ICM_XFORM = 2019, + ERROR_INVALID_TRANSFORM = 2020, + ERROR_COLORSPACE_MISMATCH = 2021, + ERROR_INVALID_COLORINDEX = 2022, + ERROR_CONNECTED_OTHER_PASSWORD = 2108, + ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT = 2109, + ERROR_BAD_USERNAME = 2202, + ERROR_NOT_CONNECTED = 2250, + ERROR_OPEN_FILES = 2401, + ERROR_ACTIVE_CONNECTIONS = 2402, + ERROR_DEVICE_IN_USE = 2404, + ERROR_UNKNOWN_PRINT_MONITOR = 3000, + ERROR_PRINTER_DRIVER_IN_USE = 3001, + ERROR_SPOOL_FILE_NOT_FOUND = 3002, + ERROR_SPL_NO_STARTDOC = 3003, + ERROR_SPL_NO_ADDJOB = 3004, + ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED = 3005, + ERROR_PRINT_MONITOR_ALREADY_INSTALLED = 3006, + ERROR_INVALID_PRINT_MONITOR = 3007, + ERROR_PRINT_MONITOR_IN_USE = 3008, + ERROR_PRINTER_HAS_JOBS_QUEUED = 3009, + ERROR_SUCCESS_REBOOT_REQUIRED = 3010, + ERROR_SUCCESS_RESTART_REQUIRED = 3011, + ERROR_PRINTER_NOT_FOUND = 3012, + ERROR_PRINTER_DRIVER_WARNED = 3013, + ERROR_PRINTER_DRIVER_BLOCKED = 3014, + ERROR_WINS_INTERNAL = 4000, + ERROR_CAN_NOT_DEL_LOCAL_WINS = 4001, + ERROR_STATIC_INIT = 4002, + ERROR_INC_BACKUP = 4003, + ERROR_FULL_BACKUP = 4004, + ERROR_REC_NON_EXISTENT = 4005, + ERROR_RPL_NOT_ALLOWED = 4006, + ERROR_DHCP_ADDRESS_CONFLICT = 4100, + ERROR_WMI_GUID_NOT_FOUND = 4200, + ERROR_WMI_INSTANCE_NOT_FOUND = 4201, + ERROR_WMI_ITEMID_NOT_FOUND = 4202, + ERROR_WMI_TRY_AGAIN = 4203, + ERROR_WMI_DP_NOT_FOUND = 4204, + ERROR_WMI_UNRESOLVED_INSTANCE_REF = 4205, + ERROR_WMI_ALREADY_ENABLED = 4206, + ERROR_WMI_GUID_DISCONNECTED = 4207, + ERROR_WMI_SERVER_UNAVAILABLE = 4208, + ERROR_WMI_DP_FAILED = 4209, + ERROR_WMI_INVALID_MOF = 4210, + ERROR_WMI_INVALID_REGINFO = 4211, + ERROR_WMI_ALREADY_DISABLED = 4212, + ERROR_WMI_READ_ONLY = 4213, + ERROR_WMI_SET_FAILURE = 4214, + ERROR_INVALID_MEDIA = 4300, + ERROR_INVALID_LIBRARY = 4301, + ERROR_INVALID_MEDIA_POOL = 4302, + ERROR_DRIVE_MEDIA_MISMATCH = 4303, + ERROR_MEDIA_OFFLINE = 4304, + ERROR_LIBRARY_OFFLINE = 4305, + ERROR_EMPTY = 4306, + ERROR_NOT_EMPTY = 4307, + ERROR_MEDIA_UNAVAILABLE = 4308, + ERROR_RESOURCE_DISABLED = 4309, + ERROR_INVALID_CLEANER = 4310, + ERROR_UNABLE_TO_CLEAN = 4311, + ERROR_OBJECT_NOT_FOUND = 4312, + ERROR_DATABASE_FAILURE = 4313, + ERROR_DATABASE_FULL = 4314, + ERROR_MEDIA_INCOMPATIBLE = 4315, + ERROR_RESOURCE_NOT_PRESENT = 4316, + ERROR_INVALID_OPERATION = 4317, + ERROR_MEDIA_NOT_AVAILABLE = 4318, + ERROR_DEVICE_NOT_AVAILABLE = 4319, + ERROR_REQUEST_REFUSED = 4320, + ERROR_INVALID_DRIVE_OBJECT = 4321, + ERROR_LIBRARY_FULL = 4322, + ERROR_MEDIUM_NOT_ACCESSIBLE = 4323, + ERROR_UNABLE_TO_LOAD_MEDIUM = 4324, + ERROR_UNABLE_TO_INVENTORY_DRIVE = 4325, + ERROR_UNABLE_TO_INVENTORY_SLOT = 4326, + ERROR_UNABLE_TO_INVENTORY_TRANSPORT = 4327, + ERROR_TRANSPORT_FULL = 4328, + ERROR_CONTROLLING_IEPORT = 4329, + ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA = 4330, + ERROR_CLEANER_SLOT_SET = 4331, + ERROR_CLEANER_SLOT_NOT_SET = 4332, + ERROR_CLEANER_CARTRIDGE_SPENT = 4333, + ERROR_UNEXPECTED_OMID = 4334, + ERROR_CANT_DELETE_LAST_ITEM = 4335, + ERROR_MESSAGE_EXCEEDS_MAX_SIZE = 4336, + ERROR_VOLUME_CONTAINS_SYS_FILES = 4337, + ERROR_INDIGENOUS_TYPE = 4338, + ERROR_NO_SUPPORTING_DRIVES = 4339, + ERROR_CLEANER_CARTRIDGE_INSTALLED = 4340, + ERROR_FILE_OFFLINE = 4350, + ERROR_REMOTE_STORAGE_NOT_ACTIVE = 4351, + ERROR_REMOTE_STORAGE_MEDIA_ERROR = 4352, + ERROR_NOT_A_REPARSE_POINT = 4390, + ERROR_REPARSE_ATTRIBUTE_CONFLICT = 4391, + ERROR_INVALID_REPARSE_DATA = 4392, + ERROR_REPARSE_TAG_INVALID = 4393, + ERROR_REPARSE_TAG_MISMATCH = 4394, + ERROR_VOLUME_NOT_SIS_ENABLED = 4500, + ERROR_DEPENDENT_RESOURCE_EXISTS = 5001, + ERROR_DEPENDENCY_NOT_FOUND = 5002, + ERROR_DEPENDENCY_ALREADY_EXISTS = 5003, + ERROR_RESOURCE_NOT_ONLINE = 5004, + ERROR_HOST_NODE_NOT_AVAILABLE = 5005, + ERROR_RESOURCE_NOT_AVAILABLE = 5006, + ERROR_RESOURCE_NOT_FOUND = 5007, + ERROR_SHUTDOWN_CLUSTER = 5008, + ERROR_CANT_EVICT_ACTIVE_NODE = 5009, + ERROR_OBJECT_ALREADY_EXISTS = 5010, + ERROR_OBJECT_IN_LIST = 5011, + ERROR_GROUP_NOT_AVAILABLE = 5012, + ERROR_GROUP_NOT_FOUND = 5013, + ERROR_GROUP_NOT_ONLINE = 5014, + ERROR_HOST_NODE_NOT_RESOURCE_OWNER = 5015, + ERROR_HOST_NODE_NOT_GROUP_OWNER = 5016, + ERROR_RESMON_CREATE_FAILED = 5017, + ERROR_RESMON_ONLINE_FAILED = 5018, + ERROR_RESOURCE_ONLINE = 5019, + ERROR_QUORUM_RESOURCE = 5020, + ERROR_NOT_QUORUM_CAPABLE = 5021, + ERROR_CLUSTER_SHUTTING_DOWN = 5022, + ERROR_INVALID_STATE = 5023, + ERROR_RESOURCE_PROPERTIES_STORED = 5024, + ERROR_NOT_QUORUM_CLASS = 5025, + ERROR_CORE_RESOURCE = 5026, + ERROR_QUORUM_RESOURCE_ONLINE_FAILED = 5027, + ERROR_QUORUMLOG_OPEN_FAILED = 5028, + ERROR_CLUSTERLOG_CORRUPT = 5029, + ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE = 5030, + ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE = 5031, + ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND = 5032, + ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE = 5033, + ERROR_QUORUM_OWNER_ALIVE = 5034, + ERROR_NETWORK_NOT_AVAILABLE = 5035, + ERROR_NODE_NOT_AVAILABLE = 5036, + ERROR_ALL_NODES_NOT_AVAILABLE = 5037, + ERROR_RESOURCE_FAILED = 5038, + ERROR_CLUSTER_INVALID_NODE = 5039, + ERROR_CLUSTER_NODE_EXISTS = 5040, + ERROR_CLUSTER_JOIN_IN_PROGRESS = 5041, + ERROR_CLUSTER_NODE_NOT_FOUND = 5042, + ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND = 5043, + ERROR_CLUSTER_NETWORK_EXISTS = 5044, + ERROR_CLUSTER_NETWORK_NOT_FOUND = 5045, + ERROR_CLUSTER_NETINTERFACE_EXISTS = 5046, + ERROR_CLUSTER_NETINTERFACE_NOT_FOUND = 5047, + ERROR_CLUSTER_INVALID_REQUEST = 5048, + ERROR_CLUSTER_INVALID_NETWORK_PROVIDER = 5049, + ERROR_CLUSTER_NODE_DOWN = 5050, + ERROR_CLUSTER_NODE_UNREACHABLE = 5051, + ERROR_CLUSTER_NODE_NOT_MEMBER = 5052, + ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS = 5053, + ERROR_CLUSTER_INVALID_NETWORK = 5054, + ERROR_CLUSTER_NODE_UP = 5056, + ERROR_CLUSTER_IPADDR_IN_USE = 5057, + ERROR_CLUSTER_NODE_NOT_PAUSED = 5058, + ERROR_CLUSTER_NO_SECURITY_CONTEXT = 5059, + ERROR_CLUSTER_NETWORK_NOT_INTERNAL = 5060, + ERROR_CLUSTER_NODE_ALREADY_UP = 5061, + ERROR_CLUSTER_NODE_ALREADY_DOWN = 5062, + ERROR_CLUSTER_NETWORK_ALREADY_ONLINE = 5063, + ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE = 5064, + ERROR_CLUSTER_NODE_ALREADY_MEMBER = 5065, + ERROR_CLUSTER_LAST_INTERNAL_NETWORK = 5066, + ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS = 5067, + ERROR_INVALID_OPERATION_ON_QUORUM = 5068, + ERROR_DEPENDENCY_NOT_ALLOWED = 5069, + ERROR_CLUSTER_NODE_PAUSED = 5070, + ERROR_NODE_CANT_HOST_RESOURCE = 5071, + ERROR_CLUSTER_NODE_NOT_READY = 5072, + ERROR_CLUSTER_NODE_SHUTTING_DOWN = 5073, + ERROR_CLUSTER_JOIN_ABORTED = 5074, + ERROR_CLUSTER_INCOMPATIBLE_VERSIONS = 5075, + ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED = 5076, + ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED = 5077, + ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND = 5078, + ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED = 5079, + ERROR_CLUSTER_RESNAME_NOT_FOUND = 5080, + ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED = 5081, + ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST = 5082, + ERROR_CLUSTER_DATABASE_SEQMISMATCH = 5083, + ERROR_RESMON_INVALID_STATE = 5084, + ERROR_CLUSTER_GUM_NOT_LOCKER = 5085, + ERROR_QUORUM_DISK_NOT_FOUND = 5086, + ERROR_DATABASE_BACKUP_CORRUPT = 5087, + ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT = 5088, + ERROR_RESOURCE_PROPERTY_UNCHANGEABLE = 5089, + ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE = 5890, + ERROR_CLUSTER_QUORUMLOG_NOT_FOUND = 5891, + ERROR_CLUSTER_MEMBERSHIP_HALT = 5892, + ERROR_CLUSTER_INSTANCE_ID_MISMATCH = 5893, + ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP = 5894, + ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH = 5895, + ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP = 5896, + ERROR_CLUSTER_PARAMETER_MISMATCH = 5897, + ERROR_NODE_CANNOT_BE_CLUSTERED = 5898, + ERROR_CLUSTER_WRONG_OS_VERSION = 5899, + ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME = 5900, + ERROR_ENCRYPTION_FAILED = 6000, + ERROR_DECRYPTION_FAILED = 6001, + ERROR_FILE_ENCRYPTED = 6002, + ERROR_NO_RECOVERY_POLICY = 6003, + ERROR_NO_EFS = 6004, + ERROR_WRONG_EFS = 6005, + ERROR_NO_USER_KEYS = 6006, + ERROR_FILE_NOT_ENCRYPTED = 6007, + ERROR_NOT_EXPORT_FORMAT = 6008, + ERROR_FILE_READ_ONLY = 6009, + ERROR_DIR_EFS_DISALLOWED = 6010, + ERROR_EFS_SERVER_NOT_TRUSTED = 6011, + ERROR_BAD_RECOVERY_POLICY = 6012, + ERROR_EFS_ALG_BLOB_TOO_BIG = 6013, + ERROR_VOLUME_NOT_SUPPORT_EFS = 6014, + ERROR_EFS_DISABLED = 6015, + ERROR_EFS_VERSION_NOT_SUPPORT = 6016, + ERROR_NO_BROWSER_SERVERS_FOUND = 6118, + SCHED_E_SERVICE_NOT_LOCALSYSTEM = 6200, + ERROR_CTX_WINSTATION_NAME_INVALID = 7001, + ERROR_CTX_INVALID_PD = 7002, + ERROR_CTX_PD_NOT_FOUND = 7003, + ERROR_CTX_WD_NOT_FOUND = 7004, + ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY = 7005, + ERROR_CTX_SERVICE_NAME_COLLISION = 7006, + ERROR_CTX_CLOSE_PENDING = 7007, + ERROR_CTX_NO_OUTBUF = 7008, + ERROR_CTX_MODEM_INF_NOT_FOUND = 7009, + ERROR_CTX_INVALID_MODEMNAME = 7010, + ERROR_CTX_MODEM_RESPONSE_ERROR = 7011, + ERROR_CTX_MODEM_RESPONSE_TIMEOUT = 7012, + ERROR_CTX_MODEM_RESPONSE_NO_CARRIER = 7013, + ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE = 7014, + ERROR_CTX_MODEM_RESPONSE_BUSY = 7015, + ERROR_CTX_MODEM_RESPONSE_VOICE = 7016, + ERROR_CTX_TD_ERROR = 7017, + ERROR_CTX_WINSTATION_NOT_FOUND = 7022, + ERROR_CTX_WINSTATION_ALREADY_EXISTS = 7023, + ERROR_CTX_WINSTATION_BUSY = 7024, + ERROR_CTX_BAD_VIDEO_MODE = 7025, + ERROR_CTX_GRAPHICS_INVALID = 7035, + ERROR_CTX_LOGON_DISABLED = 7037, + ERROR_CTX_NOT_CONSOLE = 7038, + ERROR_CTX_CLIENT_QUERY_TIMEOUT = 7040, + ERROR_CTX_CONSOLE_DISCONNECT = 7041, + ERROR_CTX_CONSOLE_CONNECT = 7042, + ERROR_CTX_SHADOW_DENIED = 7044, + ERROR_CTX_WINSTATION_ACCESS_DENIED = 7045, + ERROR_CTX_INVALID_WD = 7049, + ERROR_CTX_SHADOW_INVALID = 7050, + ERROR_CTX_SHADOW_DISABLED = 7051, + ERROR_CTX_CLIENT_LICENSE_IN_USE = 7052, + ERROR_CTX_CLIENT_LICENSE_NOT_SET = 7053, + ERROR_CTX_LICENSE_NOT_AVAILABLE = 7054, + ERROR_CTX_LICENSE_CLIENT_INVALID = 7055, + ERROR_CTX_LICENSE_EXPIRED = 7056, + ERROR_CTX_SHADOW_NOT_RUNNING = 7057, + ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE = 7058, + FRS_ERR_INVALID_API_SEQUENCE = 8001, + FRS_ERR_STARTING_SERVICE = 8002, + FRS_ERR_STOPPING_SERVICE = 8003, + FRS_ERR_INTERNAL_API = 8004, + FRS_ERR_INTERNAL = 8005, + FRS_ERR_SERVICE_COMM = 8006, + FRS_ERR_INSUFFICIENT_PRIV = 8007, + FRS_ERR_AUTHENTICATION = 8008, + FRS_ERR_PARENT_INSUFFICIENT_PRIV = 8009, + FRS_ERR_PARENT_AUTHENTICATION = 8010, + FRS_ERR_CHILD_TO_PARENT_COMM = 8011, + FRS_ERR_PARENT_TO_CHILD_COMM = 8012, + FRS_ERR_SYSVOL_POPULATE = 8013, + FRS_ERR_SYSVOL_POPULATE_TIMEOUT = 8014, + FRS_ERR_SYSVOL_IS_BUSY = 8015, + FRS_ERR_SYSVOL_DEMOTE = 8016, + FRS_ERR_INVALID_SERVICE_PARAMETER = 8017, + ERROR_DS_NOT_INSTALLED = 8200, + ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY = 8201, + ERROR_DS_NO_ATTRIBUTE_OR_VALUE = 8202, + ERROR_DS_INVALID_ATTRIBUTE_SYNTAX = 8203, + ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED = 8204, + ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS = 8205, + ERROR_DS_BUSY = 8206, + ERROR_DS_UNAVAILABLE = 8207, + ERROR_DS_NO_RIDS_ALLOCATED = 8208, + ERROR_DS_NO_MORE_RIDS = 8209, + ERROR_DS_INCORRECT_ROLE_OWNER = 8210, + ERROR_DS_RIDMGR_INIT_ERROR = 8211, + ERROR_DS_OBJ_CLASS_VIOLATION = 8212, + ERROR_DS_CANT_ON_NON_LEAF = 8213, + ERROR_DS_CANT_ON_RDN = 8214, + ERROR_DS_CANT_MOD_OBJ_CLASS = 8215, + ERROR_DS_CROSS_DOM_MOVE_ERROR = 8216, + ERROR_DS_GC_NOT_AVAILABLE = 8217, + ERROR_SHARED_POLICY = 8218, + ERROR_POLICY_OBJECT_NOT_FOUND = 8219, + ERROR_POLICY_ONLY_IN_DS = 8220, + ERROR_PROMOTION_ACTIVE = 8221, + ERROR_NO_PROMOTION_ACTIVE = 8222, + ERROR_DS_OPERATIONS_ERROR = 8224, + ERROR_DS_PROTOCOL_ERROR = 8225, + ERROR_DS_TIMELIMIT_EXCEEDED = 8226, + ERROR_DS_SIZELIMIT_EXCEEDED = 8227, + ERROR_DS_ADMIN_LIMIT_EXCEEDED = 8228, + ERROR_DS_COMPARE_FALSE = 8229, + ERROR_DS_COMPARE_TRUE = 8230, + ERROR_DS_AUTH_METHOD_NOT_SUPPORTED = 8231, + ERROR_DS_STRONG_AUTH_REQUIRED = 8232, + ERROR_DS_INAPPROPRIATE_AUTH = 8233, + ERROR_DS_AUTH_UNKNOWN = 8234, + ERROR_DS_REFERRAL = 8235, + ERROR_DS_UNAVAILABLE_CRIT_EXTENSION = 8236, + ERROR_DS_CONFIDENTIALITY_REQUIRED = 8237, + ERROR_DS_INAPPROPRIATE_MATCHING = 8238, + ERROR_DS_CONSTRAINT_VIOLATION = 8239, + ERROR_DS_NO_SUCH_OBJECT = 8240, + ERROR_DS_ALIAS_PROBLEM = 8241, + ERROR_DS_INVALID_DN_SYNTAX = 8242, + ERROR_DS_IS_LEAF = 8243, + ERROR_DS_ALIAS_DEREF_PROBLEM = 8244, + ERROR_DS_UNWILLING_TO_PERFORM = 8245, + ERROR_DS_LOOP_DETECT = 8246, + ERROR_DS_NAMING_VIOLATION = 8247, + ERROR_DS_OBJECT_RESULTS_TOO_LARGE = 8248, + ERROR_DS_AFFECTS_MULTIPLE_DSAS = 8249, + ERROR_DS_SERVER_DOWN = 8250, + ERROR_DS_LOCAL_ERROR = 8251, + ERROR_DS_ENCODING_ERROR = 8252, + ERROR_DS_DECODING_ERROR = 8253, + ERROR_DS_FILTER_UNKNOWN = 8254, + ERROR_DS_PARAM_ERROR = 8255, + ERROR_DS_NOT_SUPPORTED = 8256, + ERROR_DS_NO_RESULTS_RETURNED = 8257, + ERROR_DS_CONTROL_NOT_FOUND = 8258, + ERROR_DS_CLIENT_LOOP = 8259, + ERROR_DS_REFERRAL_LIMIT_EXCEEDED = 8260, + ERROR_DS_SORT_CONTROL_MISSING = 8261, + ERROR_DS_OFFSET_RANGE_ERROR = 8262, + ERROR_DS_ROOT_MUST_BE_NC = 8301, + ERROR_DS_ADD_REPLICA_INHIBITED = 8302, + ERROR_DS_ATT_NOT_DEF_IN_SCHEMA = 8303, + ERROR_DS_MAX_OBJ_SIZE_EXCEEDED = 8304, + ERROR_DS_OBJ_STRING_NAME_EXISTS = 8305, + ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA = 8306, + ERROR_DS_RDN_DOESNT_MATCH_SCHEMA = 8307, + ERROR_DS_NO_REQUESTED_ATTS_FOUND = 8308, + ERROR_DS_USER_BUFFER_TO_SMALL = 8309, + ERROR_DS_ATT_IS_NOT_ON_OBJ = 8310, + ERROR_DS_ILLEGAL_MOD_OPERATION = 8311, + ERROR_DS_OBJ_TOO_LARGE = 8312, + ERROR_DS_BAD_INSTANCE_TYPE = 8313, + ERROR_DS_MASTERDSA_REQUIRED = 8314, + ERROR_DS_OBJECT_CLASS_REQUIRED = 8315, + ERROR_DS_MISSING_REQUIRED_ATT = 8316, + ERROR_DS_ATT_NOT_DEF_FOR_CLASS = 8317, + ERROR_DS_ATT_ALREADY_EXISTS = 8318, + ERROR_DS_CANT_ADD_ATT_VALUES = 8320, + ERROR_DS_SINGLE_VALUE_CONSTRAINT = 8321, + ERROR_DS_RANGE_CONSTRAINT = 8322, + ERROR_DS_ATT_VAL_ALREADY_EXISTS = 8323, + ERROR_DS_CANT_REM_MISSING_ATT = 8324, + ERROR_DS_CANT_REM_MISSING_ATT_VAL = 8325, + ERROR_DS_ROOT_CANT_BE_SUBREF = 8326, + ERROR_DS_NO_CHAINING = 8327, + ERROR_DS_NO_CHAINED_EVAL = 8328, + ERROR_DS_NO_PARENT_OBJECT = 8329, + ERROR_DS_PARENT_IS_AN_ALIAS = 8330, + ERROR_DS_CANT_MIX_MASTER_AND_REPS = 8331, + ERROR_DS_CHILDREN_EXIST = 8332, + ERROR_DS_OBJ_NOT_FOUND = 8333, + ERROR_DS_ALIASED_OBJ_MISSING = 8334, + ERROR_DS_BAD_NAME_SYNTAX = 8335, + ERROR_DS_ALIAS_POINTS_TO_ALIAS = 8336, + ERROR_DS_CANT_DEREF_ALIAS = 8337, + ERROR_DS_OUT_OF_SCOPE = 8338, + ERROR_DS_OBJECT_BEING_REMOVED = 8339, + ERROR_DS_CANT_DELETE_DSA_OBJ = 8340, + ERROR_DS_GENERIC_ERROR = 8341, + ERROR_DS_DSA_MUST_BE_INT_MASTER = 8342, + ERROR_DS_CLASS_NOT_DSA = 8343, + ERROR_DS_INSUFF_ACCESS_RIGHTS = 8344, + ERROR_DS_ILLEGAL_SUPERIOR = 8345, + ERROR_DS_ATTRIBUTE_OWNED_BY_SAM = 8346, + ERROR_DS_NAME_TOO_MANY_PARTS = 8347, + ERROR_DS_NAME_TOO_LONG = 8348, + ERROR_DS_NAME_VALUE_TOO_LONG = 8349, + ERROR_DS_NAME_UNPARSEABLE = 8350, + ERROR_DS_NAME_TYPE_UNKNOWN = 8351, + ERROR_DS_NOT_AN_OBJECT = 8352, + ERROR_DS_SEC_DESC_TOO_SHORT = 8353, + ERROR_DS_SEC_DESC_INVALID = 8354, + ERROR_DS_NO_DELETED_NAME = 8355, + ERROR_DS_SUBREF_MUST_HAVE_PARENT = 8356, + ERROR_DS_NCNAME_MUST_BE_NC = 8357, + ERROR_DS_CANT_ADD_SYSTEM_ONLY = 8358, + ERROR_DS_CLASS_MUST_BE_CONCRETE = 8359, + ERROR_DS_INVALID_DMD = 8360, + ERROR_DS_OBJ_GUID_EXISTS = 8361, + ERROR_DS_NOT_ON_BACKLINK = 8362, + ERROR_DS_NO_CROSSREF_FOR_NC = 8363, + ERROR_DS_SHUTTING_DOWN = 8364, + ERROR_DS_UNKNOWN_OPERATION = 8365, + ERROR_DS_INVALID_ROLE_OWNER = 8366, + ERROR_DS_COULDNT_CONTACT_FSMO = 8367, + ERROR_DS_CROSS_NC_DN_RENAME = 8368, + ERROR_DS_CANT_MOD_SYSTEM_ONLY = 8369, + ERROR_DS_REPLICATOR_ONLY = 8370, + ERROR_DS_OBJ_CLASS_NOT_DEFINED = 8371, + ERROR_DS_OBJ_CLASS_NOT_SUBCLASS = 8372, + ERROR_DS_NAME_REFERENCE_INVALID = 8373, + ERROR_DS_CROSS_REF_EXISTS = 8374, + ERROR_DS_CANT_DEL_MASTER_CROSSREF = 8375, + ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD = 8376, + ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX = 8377, + ERROR_DS_DUP_RDN = 8378, + ERROR_DS_DUP_OID = 8379, + ERROR_DS_DUP_MAPI_ID = 8380, + ERROR_DS_DUP_SCHEMA_ID_GUID = 8381, + ERROR_DS_DUP_LDAP_DISPLAY_NAME = 8382, + ERROR_DS_SEMANTIC_ATT_TEST = 8383, + ERROR_DS_SYNTAX_MISMATCH = 8384, + ERROR_DS_EXISTS_IN_MUST_HAVE = 8385, + ERROR_DS_EXISTS_IN_MAY_HAVE = 8386, + ERROR_DS_NONEXISTENT_MAY_HAVE = 8387, + ERROR_DS_NONEXISTENT_MUST_HAVE = 8388, + ERROR_DS_AUX_CLS_TEST_FAIL = 8389, + ERROR_DS_NONEXISTENT_POSS_SUP = 8390, + ERROR_DS_SUB_CLS_TEST_FAIL = 8391, + ERROR_DS_BAD_RDN_ATT_ID_SYNTAX = 8392, + ERROR_DS_EXISTS_IN_AUX_CLS = 8393, + ERROR_DS_EXISTS_IN_SUB_CLS = 8394, + ERROR_DS_EXISTS_IN_POSS_SUP = 8395, + ERROR_DS_RECALCSCHEMA_FAILED = 8396, + ERROR_DS_TREE_DELETE_NOT_FINISHED = 8397, + ERROR_DS_CANT_DELETE = 8398, + ERROR_DS_ATT_SCHEMA_REQ_ID = 8399, + ERROR_DS_BAD_ATT_SCHEMA_SYNTAX = 8400, + ERROR_DS_CANT_CACHE_ATT = 8401, + ERROR_DS_CANT_CACHE_CLASS = 8402, + ERROR_DS_CANT_REMOVE_ATT_CACHE = 8403, + ERROR_DS_CANT_REMOVE_CLASS_CACHE = 8404, + ERROR_DS_CANT_RETRIEVE_DN = 8405, + ERROR_DS_MISSING_SUPREF = 8406, + ERROR_DS_CANT_RETRIEVE_INSTANCE = 8407, + ERROR_DS_CODE_INCONSISTENCY = 8408, + ERROR_DS_DATABASE_ERROR = 8409, + ERROR_DS_GOVERNSID_MISSING = 8410, + ERROR_DS_MISSING_EXPECTED_ATT = 8411, + ERROR_DS_NCNAME_MISSING_CR_REF = 8412, + ERROR_DS_SECURITY_CHECKING_ERROR = 8413, + ERROR_DS_SCHEMA_NOT_LOADED = 8414, + ERROR_DS_SCHEMA_ALLOC_FAILED = 8415, + ERROR_DS_ATT_SCHEMA_REQ_SYNTAX = 8416, + ERROR_DS_GCVERIFY_ERROR = 8417, + ERROR_DS_DRA_SCHEMA_MISMATCH = 8418, + ERROR_DS_CANT_FIND_DSA_OBJ = 8419, + ERROR_DS_CANT_FIND_EXPECTED_NC = 8420, + ERROR_DS_CANT_FIND_NC_IN_CACHE = 8421, + ERROR_DS_CANT_RETRIEVE_CHILD = 8422, + ERROR_DS_SECURITY_ILLEGAL_MODIFY = 8423, + ERROR_DS_CANT_REPLACE_HIDDEN_REC = 8424, + ERROR_DS_BAD_HIERARCHY_FILE = 8425, + ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED = 8426, + ERROR_DS_CONFIG_PARAM_MISSING = 8427, + ERROR_DS_COUNTING_AB_INDICES_FAILED = 8428, + ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED = 8429, + ERROR_DS_INTERNAL_FAILURE = 8430, + ERROR_DS_UNKNOWN_ERROR = 8431, + ERROR_DS_ROOT_REQUIRES_CLASS_TOP = 8432, + ERROR_DS_REFUSING_FSMO_ROLES = 8433, + ERROR_DS_MISSING_FSMO_SETTINGS = 8434, + ERROR_DS_UNABLE_TO_SURRENDER_ROLES = 8435, + ERROR_DS_DRA_GENERIC = 8436, + ERROR_DS_DRA_INVALID_PARAMETER = 8437, + ERROR_DS_DRA_BUSY = 8438, + ERROR_DS_DRA_BAD_DN = 8439, + ERROR_DS_DRA_BAD_NC = 8440, + ERROR_DS_DRA_DN_EXISTS = 8441, + ERROR_DS_DRA_INTERNAL_ERROR = 8442, + ERROR_DS_DRA_INCONSISTENT_DIT = 8443, + ERROR_DS_DRA_CONNECTION_FAILED = 8444, + ERROR_DS_DRA_BAD_INSTANCE_TYPE = 8445, + ERROR_DS_DRA_OUT_OF_MEM = 8446, + ERROR_DS_DRA_MAIL_PROBLEM = 8447, + ERROR_DS_DRA_REF_ALREADY_EXISTS = 8448, + ERROR_DS_DRA_REF_NOT_FOUND = 8449, + ERROR_DS_DRA_OBJ_IS_REP_SOURCE = 8450, + ERROR_DS_DRA_DB_ERROR = 8451, + ERROR_DS_DRA_NO_REPLICA = 8452, + ERROR_DS_DRA_ACCESS_DENIED = 8453, + ERROR_DS_DRA_NOT_SUPPORTED = 8454, + ERROR_DS_DRA_RPC_CANCELLED = 8455, + ERROR_DS_DRA_SOURCE_DISABLED = 8456, + ERROR_DS_DRA_SINK_DISABLED = 8457, + ERROR_DS_DRA_NAME_COLLISION = 8458, + ERROR_DS_DRA_SOURCE_REINSTALLED = 8459, + ERROR_DS_DRA_MISSING_PARENT = 8460, + ERROR_DS_DRA_PREEMPTED = 8461, + ERROR_DS_DRA_ABANDON_SYNC = 8462, + ERROR_DS_DRA_SHUTDOWN = 8463, + ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET = 8464, + ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA = 8465, + ERROR_DS_DRA_EXTN_CONNECTION_FAILED = 8466, + ERROR_DS_INSTALL_SCHEMA_MISMATCH = 8467, + ERROR_DS_DUP_LINK_ID = 8468, + ERROR_DS_NAME_ERROR_RESOLVING = 8469, + ERROR_DS_NAME_ERROR_NOT_FOUND = 8470, + ERROR_DS_NAME_ERROR_NOT_UNIQUE = 8471, + ERROR_DS_NAME_ERROR_NO_MAPPING = 8472, + ERROR_DS_NAME_ERROR_DOMAIN_ONLY = 8473, + ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING = 8474, + ERROR_DS_CONSTRUCTED_ATT_MOD = 8475, + ERROR_DS_WRONG_OM_OBJ_CLASS = 8476, + ERROR_DS_DRA_REPL_PENDING = 8477, + ERROR_DS_DS_REQUIRED = 8478, + ERROR_DS_INVALID_LDAP_DISPLAY_NAME = 8479, + ERROR_DS_NON_BASE_SEARCH = 8480, + ERROR_DS_CANT_RETRIEVE_ATTS = 8481, + ERROR_DS_BACKLINK_WITHOUT_LINK = 8482, + ERROR_DS_EPOCH_MISMATCH = 8483, + ERROR_DS_SRC_NAME_MISMATCH = 8484, + ERROR_DS_SRC_AND_DST_NC_IDENTICAL = 8485, + ERROR_DS_DST_NC_MISMATCH = 8486, + ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC = 8487, + ERROR_DS_SRC_GUID_MISMATCH = 8488, + ERROR_DS_CANT_MOVE_DELETED_OBJECT = 8489, + ERROR_DS_PDC_OPERATION_IN_PROGRESS = 8490, + ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD = 8491, + ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION = 8492, + ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS = 8493, + ERROR_DS_NC_MUST_HAVE_NC_PARENT = 8494, + ERROR_DS_DST_DOMAIN_NOT_NATIVE = 8496, + ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER = 8497, + ERROR_DS_CANT_MOVE_ACCOUNT_GROUP = 8498, + ERROR_DS_CANT_MOVE_RESOURCE_GROUP = 8499, + ERROR_DS_INVALID_SEARCH_FLAG = 8500, + ERROR_DS_NO_TREE_DELETE_ABOVE_NC = 8501, + ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE = 8502, + ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE = 8503, + ERROR_DS_SAM_INIT_FAILURE = 8504, + ERROR_DS_SENSITIVE_GROUP_VIOLATION = 8505, + ERROR_DS_CANT_MOD_PRIMARYGROUPID = 8506, + ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD = 8507, + ERROR_DS_NONSAFE_SCHEMA_CHANGE = 8508, + ERROR_DS_SCHEMA_UPDATE_DISALLOWED = 8509, + ERROR_DS_CANT_CREATE_UNDER_SCHEMA = 8510, + ERROR_DS_INSTALL_NO_SRC_SCH_VERSION = 8511, + ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE = 8512, + ERROR_DS_INVALID_GROUP_TYPE = 8513, + ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN = 8514, + ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN = 8515, + ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER = 8516, + ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER = 8517, + ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER = 8518, + ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER = 8519, + ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER = 8520, + ERROR_DS_HAVE_PRIMARY_MEMBERS = 8521, + ERROR_DS_STRING_SD_CONVERSION_FAILED = 8522, + ERROR_DS_NAMING_MASTER_GC = 8523, + ERROR_DS_LOOKUP_FAILURE = 8524, + ERROR_DS_COULDNT_UPDATE_SPNS = 8525, + ERROR_DS_CANT_RETRIEVE_SD = 8526, + ERROR_DS_KEY_NOT_UNIQUE = 8527, + ERROR_DS_WRONG_LINKED_ATT_SYNTAX = 8528, + ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD = 8529, + ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY = 8530, + ERROR_DS_CANT_START = 8531, + ERROR_DS_INIT_FAILURE = 8532, + ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION = 8533, + ERROR_DS_SOURCE_DOMAIN_IN_FOREST = 8534, + ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST = 8535, + ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED = 8536, + ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN = 8537, + ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER = 8538, + ERROR_DS_SRC_SID_EXISTS_IN_FOREST = 8539, + ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH = 8540, + ERROR_SAM_INIT_FAILURE = 8541, + ERROR_DS_DRA_SCHEMA_INFO_SHIP = 8542, + ERROR_DS_DRA_SCHEMA_CONFLICT = 8543, + ERROR_DS_DRA_EARLIER_SCHEMA_CONLICT = 8544, + ERROR_DS_DRA_OBJ_NC_MISMATCH = 8545, + ERROR_DS_NC_STILL_HAS_DSAS = 8546, + ERROR_DS_GC_REQUIRED = 8547, + ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY = 8548, + ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS = 8549, + ERROR_DS_CANT_ADD_TO_GC = 8550, + ERROR_DS_NO_CHECKPOINT_WITH_PDC = 8551, + ERROR_DS_SOURCE_AUDITING_NOT_ENABLED = 8552, + ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC = 8553, + ERROR_DS_INVALID_NAME_FOR_SPN = 8554, + ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS = 8555, + ERROR_DS_UNICODEPWD_NOT_IN_QUOTES = 8556, + ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED = 8557, + ERROR_DS_MUST_BE_RUN_ON_DST_DC = 8558, + ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER = 8559, + ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ = 8560, + ERROR_DS_INIT_FAILURE_CONSOLE = 8561, + ERROR_DS_SAM_INIT_FAILURE_CONSOLE = 8562, + ERROR_DS_FOREST_VERSION_TOO_HIGH = 8563, + ERROR_DS_DOMAIN_VERSION_TOO_HIGH = 8564, + ERROR_DS_FOREST_VERSION_TOO_LOW = 8565, + ERROR_DS_DOMAIN_VERSION_TOO_LOW = 8566, + ERROR_DS_INCOMPATIBLE_VERSION = 8567, + ERROR_DS_LOW_DSA_VERSION = 8568, + ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN = 8569, + ERROR_DS_NOT_SUPPORTED_SORT_ORDER = 8570, + ERROR_DS_NAME_NOT_UNIQUE = 8571, + ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4 = 8572, + ERROR_DS_OUT_OF_VERSION_STORE = 8573, + ERROR_DS_INCOMPATIBLE_CONTROLS_USED = 8574, + ERROR_DS_NO_REF_DOMAIN = 8575, + ERROR_DS_RESERVED_LINK_ID = 8576, + ERROR_DS_LINK_ID_NOT_AVAILABLE = 8577, + ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER = 8578, + ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE = 8579, + ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC = 8580, + ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG = 8581, + ERROR_DS_MODIFYDN_WRONG_GRANDPARENT = 8582, + ERROR_DS_NAME_ERROR_TRUST_REFERRAL = 8583, + ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER = 8584, + ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD = 8585, + ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE = 8586, + ERROR_DS_THREAD_LIMIT_EXCEEDED = 8587, + ERROR_DS_NOT_CLOSEST = 8588, + ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF = 8589, + ERROR_DS_SINGLE_USER_MODE_FAILED = 8590, + ERROR_DS_NTDSCRIPT_SYNTAX_ERROR = 8591, + ERROR_DS_NTDSCRIPT_PROCESS_ERROR = 8592, + ERROR_DS_DIFFERENT_REPL_EPOCHS = 8593, + ERROR_DS_DRS_EXTENSIONS_CHANGED = 8594, + ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR = 8595, + ERROR_DS_NO_MSDS_INTID = 8596, + ERROR_DS_DUP_MSDS_INTID = 8597, + ERROR_DS_EXISTS_IN_RDNATTID = 8598, + ERROR_DS_AUTHORIZATION_FAILED = 8599, + ERROR_DS_INVALID_SCRIPT = 8600, + ERROR_DS_REMOTE_CROSSREF_OP_FAILED = 8601, + DNS_ERROR_RCODE_FORMAT_ERROR = 9001, + DNS_ERROR_RCODE_SERVER_FAILURE = 9002, + DNS_ERROR_RCODE_NAME_ERROR = 9003, + DNS_ERROR_RCODE_NOT_IMPLEMENTED = 9004, + DNS_ERROR_RCODE_REFUSED = 9005, + DNS_ERROR_RCODE_YXDOMAIN = 9006, + DNS_ERROR_RCODE_YXRRSET = 9007, + DNS_ERROR_RCODE_NXRRSET = 9008, + DNS_ERROR_RCODE_NOTAUTH = 9009, + DNS_ERROR_RCODE_NOTZONE = 9010, + DNS_ERROR_RCODE_BADSIG = 9016, + DNS_ERROR_RCODE_BADKEY = 9017, + DNS_ERROR_RCODE_BADTIME = 9018, + DNS_INFO_NO_RECORDS = 9501, + DNS_ERROR_BAD_PACKET = 9502, + DNS_ERROR_NO_PACKET = 9503, + DNS_ERROR_RCODE = 9504, + DNS_ERROR_UNSECURE_PACKET = 9505, + DNS_ERROR_INVALID_TYPE = 9551, + DNS_ERROR_INVALID_IP_ADDRESS = 9552, + DNS_ERROR_INVALID_PROPERTY = 9553, + DNS_ERROR_TRY_AGAIN_LATER = 9554, + DNS_ERROR_NOT_UNIQUE = 9555, + DNS_ERROR_NON_RFC_NAME = 9556, + DNS_STATUS_FQDN = 9557, + DNS_STATUS_DOTTED_NAME = 9558, + DNS_STATUS_SINGLE_PART_NAME = 9559, + DNS_ERROR_INVALID_NAME_CHAR = 9560, + DNS_ERROR_NUMERIC_NAME = 9561, + DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER = 9562, + DNS_ERROR_ZONE_DOES_NOT_EXIST = 9601, + DNS_ERROR_NO_ZONE_INFO = 9602, + DNS_ERROR_INVALID_ZONE_OPERATION = 9603, + DNS_ERROR_ZONE_CONFIGURATION_ERROR = 9604, + DNS_ERROR_ZONE_HAS_NO_SOA_RECORD = 9605, + DNS_ERROR_ZONE_HAS_NO_NS_RECORDS = 9606, + DNS_ERROR_ZONE_LOCKED = 9607, + DNS_ERROR_ZONE_CREATION_FAILED = 9608, + DNS_ERROR_ZONE_ALREADY_EXISTS = 9609, + DNS_ERROR_AUTOZONE_ALREADY_EXISTS = 9610, + DNS_ERROR_INVALID_ZONE_TYPE = 9611, + DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP = 9612, + DNS_ERROR_ZONE_NOT_SECONDARY = 9613, + DNS_ERROR_NEED_SECONDARY_ADDRESSES = 9614, + DNS_ERROR_WINS_INIT_FAILED = 9615, + DNS_ERROR_NEED_WINS_SERVERS = 9616, + DNS_ERROR_NBSTAT_INIT_FAILED = 9617, + DNS_ERROR_SOA_DELETE_INVALID = 9618, + DNS_ERROR_FORWARDER_ALREADY_EXISTS = 9619, + DNS_ERROR_ZONE_REQUIRES_MASTER_IP = 9620, + DNS_ERROR_ZONE_IS_SHUTDOWN = 9621, + DNS_ERROR_PRIMARY_REQUIRES_DATAFILE = 9651, + DNS_ERROR_INVALID_DATAFILE_NAME = 9652, + DNS_ERROR_DATAFILE_OPEN_FAILURE = 9653, + DNS_ERROR_FILE_WRITEBACK_FAILED = 9654, + DNS_ERROR_DATAFILE_PARSING = 9655, + DNS_ERROR_RECORD_DOES_NOT_EXIST = 9701, + DNS_ERROR_RECORD_FORMAT = 9702, + DNS_ERROR_NODE_CREATION_FAILED = 9703, + DNS_ERROR_UNKNOWN_RECORD_TYPE = 9704, + DNS_ERROR_RECORD_TIMED_OUT = 9705, + DNS_ERROR_NAME_NOT_IN_ZONE = 9706, + DNS_ERROR_CNAME_LOOP = 9707, + DNS_ERROR_NODE_IS_CNAME = 9708, + DNS_ERROR_CNAME_COLLISION = 9709, + DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT = 9710, + DNS_ERROR_RECORD_ALREADY_EXISTS = 9711, + DNS_ERROR_SECONDARY_DATA = 9712, + DNS_ERROR_NO_CREATE_CACHE_DATA = 9713, + DNS_ERROR_NAME_DOES_NOT_EXIST = 9714, + DNS_WARNING_PTR_CREATE_FAILED = 9715, + DNS_WARNING_DOMAIN_UNDELETED = 9716, + DNS_ERROR_DS_UNAVAILABLE = 9717, + DNS_ERROR_DS_ZONE_ALREADY_EXISTS = 9718, + DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE = 9719, + DNS_INFO_AXFR_COMPLETE = 9751, + DNS_ERROR_AXFR = 9752, + DNS_INFO_ADDED_LOCAL_WINS = 9753, + DNS_STATUS_CONTINUE_NEEDED = 9801, + DNS_ERROR_NO_TCPIP = 9851, + DNS_ERROR_NO_DNS_SERVERS = 9852, + DNS_ERROR_DP_DOES_NOT_EXIST = 9901, + DNS_ERROR_DP_ALREADY_EXISTS = 9902, + DNS_ERROR_DP_NOT_ENLISTED = 9903, + DNS_ERROR_DP_ALREADY_ENLISTED = 9904, + WSAEINTR = 10004, + WSAEBADF = 10009, + WSAEACCES = 10013, + WSAEFAULT = 10014, + WSAEINVAL = 10022, + WSAEMFILE = 10024, + WSAEWOULDBLOCK = 10035, + WSAEINPROGRESS = 10036, + WSAEALREADY = 10037, + WSAENOTSOCK = 10038, + WSAEDESTADDRREQ = 10039, + WSAEMSGSIZE = 10040, + WSAEPROTOTYPE = 10041, + WSAENOPROTOOPT = 10042, + WSAEPROTONOSUPPORT = 10043, + WSAESOCKTNOSUPPORT = 10044, + WSAEOPNOTSUPP = 10045, + WSAEPFNOSUPPORT = 10046, + WSAEAFNOSUPPORT = 10047, + WSAEADDRINUSE = 10048, + WSAEADDRNOTAVAIL = 10049, + WSAENETDOWN = 10050, + WSAENETUNREACH = 10051, + WSAENETRESET = 10052, + WSAECONNABORTED = 10053, + WSAECONNRESET = 10054, + WSAENOBUFS = 10055, + WSAEISCONN = 10056, + WSAENOTCONN = 10057, + WSAESHUTDOWN = 10058, + WSAETOOMANYREFS = 10059, + WSAETIMEDOUT = 10060, + WSAECONNREFUSED = 10061, + WSAELOOP = 10062, + WSAENAMETOOLONG = 10063, + WSAEHOSTDOWN = 10064, + WSAEHOSTUNREACH = 10065, + WSAENOTEMPTY = 10066, + WSAEPROCLIM = 10067, + WSAEUSERS = 10068, + WSAEDQUOT = 10069, + WSAESTALE = 10070, + WSAEREMOTE = 10071, + WSASYSNOTREADY = 10091, + WSAVERNOTSUPPORTED = 10092, + WSANOTINITIALISED = 10093, + WSAEDISCON = 10101, + WSAENOMORE = 10102, + WSAECANCELLED = 10103, + WSAEINVALIDPROCTABLE = 10104, + WSAEINVALIDPROVIDER = 10105, + WSAEPROVIDERFAILEDINIT = 10106, + WSASYSCALLFAILURE = 10107, + WSASERVICE_NOT_FOUND = 10108, + WSATYPE_NOT_FOUND = 10109, + WSA_E_NO_MORE = 10110, + WSA_E_CANCELLED = 10111, + WSAEREFUSED = 10112, + WSAHOST_NOT_FOUND = 11001, + WSATRY_AGAIN = 11002, + WSANO_RECOVERY = 11003, + WSANO_DATA = 11004, + WSA_QOS_RECEIVERS = 11005, + WSA_QOS_SENDERS = 11006, + WSA_QOS_NO_SENDERS = 11007, + WSA_QOS_NO_RECEIVERS = 11008, + WSA_QOS_REQUEST_CONFIRMED = 11009, + WSA_QOS_ADMISSION_FAILURE = 11010, + WSA_QOS_POLICY_FAILURE = 11011, + WSA_QOS_BAD_STYLE = 11012, + WSA_QOS_BAD_OBJECT = 11013, + WSA_QOS_TRAFFIC_CTRL_ERROR = 11014, + WSA_QOS_GENERIC_ERROR = 11015, + WSA_QOS_ESERVICETYPE = 11016, + WSA_QOS_EFLOWSPEC = 11017, + WSA_QOS_EPROVSPECBUF = 11018, + WSA_QOS_EFILTERSTYLE = 11019, + WSA_QOS_EFILTERTYPE = 11020, + WSA_QOS_EFILTERCOUNT = 11021, + WSA_QOS_EOBJLENGTH = 11022, + WSA_QOS_EFLOWCOUNT = 11023, + WSA_QOS_EUNKNOWNPSOBJ = 11024, + WSA_QOS_EPOLICYOBJ = 11025, + WSA_QOS_EFLOWDESC = 11026, + WSA_QOS_EPSFLOWSPEC = 11027, + WSA_QOS_EPSFILTERSPEC = 11028, + WSA_QOS_ESDMODEOBJ = 11029, + WSA_QOS_ESHAPERATEOBJ = 11030, + WSA_QOS_RESERVED_PETYPE = 11031, + ERROR_IPSEC_QM_POLICY_EXISTS = 13000, + ERROR_IPSEC_QM_POLICY_NOT_FOUND = 13001, + ERROR_IPSEC_QM_POLICY_IN_USE = 13002, + ERROR_IPSEC_MM_POLICY_EXISTS = 13003, + ERROR_IPSEC_MM_POLICY_NOT_FOUND = 13004, + ERROR_IPSEC_MM_POLICY_IN_USE = 13005, + ERROR_IPSEC_MM_FILTER_EXISTS = 13006, + ERROR_IPSEC_MM_FILTER_NOT_FOUND = 13007, + ERROR_IPSEC_TRANSPORT_FILTER_EXISTS = 13008, + ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND = 13009, + ERROR_IPSEC_MM_AUTH_EXISTS = 13010, + ERROR_IPSEC_MM_AUTH_NOT_FOUND = 13011, + ERROR_IPSEC_MM_AUTH_IN_USE = 13012, + ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND = 13013, + ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND = 13014, + ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND = 13015, + ERROR_IPSEC_TUNNEL_FILTER_EXISTS = 13016, + ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND = 13017, + ERROR_IPSEC_MM_FILTER_PENDING_DELETION = 13018, + ERROR_IPSEC_TRANSPORT_FILTER_PENDING_DELETION = 13019, + ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION = 13020, + ERROR_IPSEC_MM_POLICY_PENDING_DELETION = 13021, + ERROR_IPSEC_MM_AUTH_PENDING_DELETION = 13022, + ERROR_IPSEC_QM_POLICY_PENDING_DELETION = 13023, + ERROR_IPSEC_IKE_AUTH_FAIL = 13801, + ERROR_IPSEC_IKE_ATTRIB_FAIL = 13802, + ERROR_IPSEC_IKE_NEGOTIATION_PENDING = 13803, + ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR = 13804, + ERROR_IPSEC_IKE_TIMED_OUT = 13805, + ERROR_IPSEC_IKE_NO_CERT = 13806, + ERROR_IPSEC_IKE_SA_DELETED = 13807, + ERROR_IPSEC_IKE_SA_REAPED = 13808, + ERROR_IPSEC_IKE_MM_ACQUIRE_DROP = 13809, + ERROR_IPSEC_IKE_QM_ACQUIRE_DROP = 13810, + ERROR_IPSEC_IKE_QUEUE_DROP_MM = 13811, + ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM = 13812, + ERROR_IPSEC_IKE_DROP_NO_RESPONSE = 13813, + ERROR_IPSEC_IKE_MM_DELAY_DROP = 13814, + ERROR_IPSEC_IKE_QM_DELAY_DROP = 13815, + ERROR_IPSEC_IKE_ERROR = 13816, + ERROR_IPSEC_IKE_CRL_FAILED = 13817, + ERROR_IPSEC_IKE_INVALID_KEY_USAGE = 13818, + ERROR_IPSEC_IKE_INVALID_CERT_TYPE = 13819, + ERROR_IPSEC_IKE_NO_PRIVATE_KEY = 13820, + ERROR_IPSEC_IKE_DH_FAIL = 13822, + ERROR_IPSEC_IKE_INVALID_HEADER = 13824, + ERROR_IPSEC_IKE_NO_POLICY = 13825, + ERROR_IPSEC_IKE_INVALID_SIGNATURE = 13826, + ERROR_IPSEC_IKE_KERBEROS_ERROR = 13827, + ERROR_IPSEC_IKE_NO_PUBLIC_KEY = 13828, + ERROR_IPSEC_IKE_PROCESS_ERR = 13829, + ERROR_IPSEC_IKE_PROCESS_ERR_SA = 13830, + ERROR_IPSEC_IKE_PROCESS_ERR_PROP = 13831, + ERROR_IPSEC_IKE_PROCESS_ERR_TRANS = 13832, + ERROR_IPSEC_IKE_PROCESS_ERR_KE = 13833, + ERROR_IPSEC_IKE_PROCESS_ERR_ID = 13834, + ERROR_IPSEC_IKE_PROCESS_ERR_CERT = 13835, + ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ = 13836, + ERROR_IPSEC_IKE_PROCESS_ERR_HASH = 13837, + ERROR_IPSEC_IKE_PROCESS_ERR_SIG = 13838, + ERROR_IPSEC_IKE_PROCESS_ERR_NONCE = 13839, + ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY = 13840, + ERROR_IPSEC_IKE_PROCESS_ERR_DELETE = 13841, + ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR = 13842, + ERROR_IPSEC_IKE_INVALID_PAYLOAD = 13843, + ERROR_IPSEC_IKE_LOAD_SOFT_SA = 13844, + ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN = 13845, + ERROR_IPSEC_IKE_INVALID_COOKIE = 13846, + ERROR_IPSEC_IKE_NO_PEER_CERT = 13847, + ERROR_IPSEC_IKE_PEER_CRL_FAILED = 13848, + ERROR_IPSEC_IKE_POLICY_CHANGE = 13849, + ERROR_IPSEC_IKE_NO_MM_POLICY = 13850, + ERROR_IPSEC_IKE_NOTCBPRIV = 13851, + ERROR_IPSEC_IKE_SECLOADFAIL = 13852, + ERROR_IPSEC_IKE_FAILSSPINIT = 13853, + ERROR_IPSEC_IKE_FAILQUERYSSP = 13854, + ERROR_IPSEC_IKE_SRVACQFAIL = 13855, + ERROR_IPSEC_IKE_SRVQUERYCRED = 13856, + ERROR_IPSEC_IKE_GETSPIFAIL = 13857, + ERROR_IPSEC_IKE_INVALID_FILTER = 13858, + ERROR_IPSEC_IKE_OUT_OF_MEMORY = 13859, + ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED = 13860, + ERROR_IPSEC_IKE_INVALID_POLICY = 13861, + ERROR_IPSEC_IKE_UNKNOWN_DOI = 13862, + ERROR_IPSEC_IKE_INVALID_SITUATION = 13863, + ERROR_IPSEC_IKE_DH_FAILURE = 13864, + ERROR_IPSEC_IKE_INVALID_GROUP = 13865, + ERROR_IPSEC_IKE_ENCRYPT = 13866, + ERROR_IPSEC_IKE_DECRYPT = 13867, + ERROR_IPSEC_IKE_POLICY_MATCH = 13868, + ERROR_IPSEC_IKE_UNSUPPORTED_ID = 13869, + ERROR_IPSEC_IKE_INVALID_HASH = 13870, + ERROR_IPSEC_IKE_INVALID_HASH_ALG = 13871, + ERROR_IPSEC_IKE_INVALID_HASH_SIZE = 13872, + ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG = 13873, + ERROR_IPSEC_IKE_INVALID_AUTH_ALG = 13874, + ERROR_IPSEC_IKE_INVALID_SIG = 13875, + ERROR_IPSEC_IKE_LOAD_FAILED = 13876, + ERROR_IPSEC_IKE_RPC_DELETE = 13877, + ERROR_IPSEC_IKE_BENIGN_REINIT = 13878, + ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY = 13879, + ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN = 13881, + ERROR_IPSEC_IKE_MM_LIMIT = 13882, + ERROR_IPSEC_IKE_NEGOTIATION_DISABLED = 13883, + ERROR_IPSEC_IKE_NEG_STATUS_END = 13884, + ERROR_SXS_SECTION_NOT_FOUND = 14000, + ERROR_SXS_CANT_GEN_ACTCTX = 14001, + ERROR_SXS_INVALID_ACTCTXDATA_FORMAT = 14002, + ERROR_SXS_ASSEMBLY_NOT_FOUND = 14003, + ERROR_SXS_MANIFEST_FORMAT_ERROR = 14004, + ERROR_SXS_MANIFEST_PARSE_ERROR = 14005, + ERROR_SXS_ACTIVATION_CONTEXT_DISABLED = 14006, + ERROR_SXS_KEY_NOT_FOUND = 14007, + ERROR_SXS_VERSION_CONFLICT = 14008, + ERROR_SXS_WRONG_SECTION_TYPE = 14009, + ERROR_SXS_THREAD_QUERIES_DISABLED = 14010, + ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET = 14011, + ERROR_SXS_UNKNOWN_ENCODING_GROUP = 14012, + ERROR_SXS_UNKNOWN_ENCODING = 14013, + ERROR_SXS_INVALID_XML_NAMESPACE_URI = 14014, + ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED = 14015, + ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED = 14016, + ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE = 14017, + ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE = 14018, + ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE = 14019, + ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT = 14020, + ERROR_SXS_DUPLICATE_DLL_NAME = 14021, + ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME = 14022, + ERROR_SXS_DUPLICATE_CLSID = 14023, + ERROR_SXS_DUPLICATE_IID = 14024, + ERROR_SXS_DUPLICATE_TLBID = 14025, + ERROR_SXS_DUPLICATE_PROGID = 14026, + ERROR_SXS_DUPLICATE_ASSEMBLY_NAME = 14027, + ERROR_SXS_FILE_HASH_MISMATCH = 14028, + ERROR_SXS_POLICY_PARSE_ERROR = 14029, + ERROR_SXS_XML_E_MISSINGQUOTE = 14030, + ERROR_SXS_XML_E_COMMENTSYNTAX = 14031, + ERROR_SXS_XML_E_BADSTARTNAMECHAR = 14032, + ERROR_SXS_XML_E_BADNAMECHAR = 14033, + ERROR_SXS_XML_E_BADCHARINSTRING = 14034, + ERROR_SXS_XML_E_XMLDECLSYNTAX = 14035, + ERROR_SXS_XML_E_BADCHARDATA = 14036, + ERROR_SXS_XML_E_MISSINGWHITESPACE = 14037, + ERROR_SXS_XML_E_EXPECTINGTAGEND = 14038, + ERROR_SXS_XML_E_MISSINGSEMICOLON = 14039, + ERROR_SXS_XML_E_UNBALANCEDPAREN = 14040, + ERROR_SXS_XML_E_INTERNALERROR = 14041, + ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE = 14042, + ERROR_SXS_XML_E_INCOMPLETE_ENCODING = 14043, + ERROR_SXS_XML_E_MISSING_PAREN = 14044, + ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE = 14045, + ERROR_SXS_XML_E_MULTIPLE_COLONS = 14046, + ERROR_SXS_XML_E_INVALID_DECIMAL = 14047, + ERROR_SXS_XML_E_INVALID_HEXIDECIMAL = 14048, + ERROR_SXS_XML_E_INVALID_UNICODE = 14049, + ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK = 14050, + ERROR_SXS_XML_E_UNEXPECTEDENDTAG = 14051, + ERROR_SXS_XML_E_UNCLOSEDTAG = 14052, + ERROR_SXS_XML_E_DUPLICATEATTRIBUTE = 14053, + ERROR_SXS_XML_E_MULTIPLEROOTS = 14054, + ERROR_SXS_XML_E_INVALIDATROOTLEVEL = 14055, + ERROR_SXS_XML_E_BADXMLDECL = 14056, + ERROR_SXS_XML_E_MISSINGROOT = 14057, + ERROR_SXS_XML_E_UNEXPECTEDEOF = 14058, + ERROR_SXS_XML_E_BADPEREFINSUBSET = 14059, + ERROR_SXS_XML_E_UNCLOSEDSTARTTAG = 14060, + ERROR_SXS_XML_E_UNCLOSEDENDTAG = 14061, + ERROR_SXS_XML_E_UNCLOSEDSTRING = 14062, + ERROR_SXS_XML_E_UNCLOSEDCOMMENT = 14063, + ERROR_SXS_XML_E_UNCLOSEDDECL = 14064, + ERROR_SXS_XML_E_UNCLOSEDCDATA = 14065, + ERROR_SXS_XML_E_RESERVEDNAMESPACE = 14066, + ERROR_SXS_XML_E_INVALIDENCODING = 14067, + ERROR_SXS_XML_E_INVALIDSWITCH = 14068, + ERROR_SXS_XML_E_BADXMLCASE = 14069, + ERROR_SXS_XML_E_INVALID_STANDALONE = 14070, + ERROR_SXS_XML_E_UNEXPECTED_STANDALONE = 14071, + ERROR_SXS_XML_E_INVALID_VERSION = 14072, + ERROR_SXS_XML_E_MISSINGEQUALS = 14073, + ERROR_SXS_PROTECTION_RECOVERY_FAILED = 14074, + ERROR_SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT = 14075, + ERROR_SXS_PROTECTION_CATALOG_NOT_VALID = 14076, + ERROR_SXS_UNTRANSLATABLE_HRESULT = 14077, + ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING = 14078, + ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE = 14079, + ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME = 14080 + } +} + \ No newline at end of file diff --git a/src/SystemLayer/NativeInterfaces.cs b/src/SystemLayer/NativeInterfaces.cs new file mode 100644 index 0000000..83d2d29 --- /dev/null +++ b/src/SystemLayer/NativeInterfaces.cs @@ -0,0 +1,916 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; + +namespace PaintDotNet.SystemLayer +{ + // Most of this code is from the "Vista Bridge" samples: http://msdn2.microsoft.com/en-us/library/ms756482.aspx + + internal static class NativeInterfaces + { + [ComImport] + [Guid(NativeConstants.IID_IOleWindow)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOleWindow + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetWindow(out IntPtr phwnd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ContextSensitiveHelp([MarshalAs(UnmanagedType.Bool)] bool fEnterMode); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileOperationProgressSink)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileOperationProgressSink + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void StartOperations(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FinishOperations(int hResult); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PreRenameItem( + uint dwFlags, + IShellItem psiItem, + string pszNewName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PostRenameItem( + uint dwFlags, + IShellItem psiItem, + string pszNewName, + int hrRename, + IShellItem psiNewlyCreated); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PreMoveItem( + uint dwFlags, + IShellItem psiItem, + IShellItem psiDestinationFolder, + string pszNewName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PostMoveItem( + uint dwFlags, + IShellItem psiItem, + IShellItem psiDestinationFolder, + string pszNewName, + int hrMove, + IShellItem psiNewlyCreated); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PreCopyItem( + uint dwFlags, + IShellItem psiItem, + IShellItem psiDestinationFolder, + string pszNewName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PostCopyItem( + uint dwFlags, + IShellItem psiItem, + IShellItem psiDestinationFOlder, + string pszNewName, + int hrCopy, + IShellItem psiNewlyCreated); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PreDeleteItem( + uint dwFlags, + IShellItem psiItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PostDeleteItem( + uint dwFlags, + IShellItem psiItem, + int hrDelete, + IShellItem psiNewlyCreated); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PreNewItem( + uint dwFlags, + IShellItem psiDestinationFolder, + string pszNewName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PostNewItem( + uint dwFlags, + IShellItem psiDestinationFolder, + string pszNewName, + string pszTemplateName, + uint dwFileAttributes, + int hrNew, + IShellItem psiNewItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void UpdateProgress( + uint iWorkTotal, + uint iWorkSoFar); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ResetTimer(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void PauseTimer(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ResumeTimer(); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileOperation)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileOperation + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Advise(IFileOperationProgressSink pfops, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Unadvise(uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOperationFlags(NativeConstants.FOF dwOperationFlags); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetProgressMessage([MarshalAs(UnmanagedType.LPWStr)] string pszMessage); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetProgressDialog(IntPtr popd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetProperties(IntPtr pproparray); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOwnerWindow(IntPtr hwndParent); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ApplyPropertiesToItem(IShellItem psiItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ApplyPropertiesToItems(IntPtr punkItems); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void RenameItem( + IShellItem psiItem, + [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, + IFileOperationProgressSink pfopsItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void RenameItems(IntPtr pUnkItems, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void MoveItem( + IShellItem psiItem, + IShellItem psiDestinationFolder, + [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, + IFileOperationProgressSink pfopsItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void MoveItems(IntPtr punkItems, IShellItem psiDestinationFolder); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CopyItem( + IShellItem psiItem, + IShellItem psiDestinationFolder, + [MarshalAs(UnmanagedType.LPWStr)] string pszCopyName, + IFileOperationProgressSink pfopsItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CopyItems(object punkItems, IShellItem psiDestinationFolder); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void DeleteItem(IShellItem psiItem, IFileOperationProgressSink pfopsItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void DeleteItems(object punkItems); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void NewItem( + IShellItem psiDestinationFolder, + uint dwFileAttributes, + [MarshalAs(UnmanagedType.LPWStr)] string pszName, + [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName, + IFileOperationProgressSink pfopsItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + int PerformOperations(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAnyOperationsAborted(out bool pfAnyOperationsAborted); + } + + [ComImport] + [Guid(NativeConstants.IID_ISequentialStream)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ISequentialStream + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Read(IntPtr pv, uint cb, out uint pcbRead); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Write(IntPtr pv, uint cb, out uint pcbWritten); + } + + [ComImport] + [Guid(NativeConstants.IID_IStream)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IStream + : ISequentialStream + { + // Defined on ISequentialStream - repeated here due to requirements of COM interop layer + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Read(IntPtr pv, uint cb, out uint pcbRead); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Write(IntPtr pv, uint cb, out uint pcbWritten); + + // IStream-specific interface members + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Seek(ulong dlibMove, uint dwOrigin, out ulong plibNewPosition); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetSize(ulong libNewSize); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void CopyTo(IStream pstm, ulong cb, out ulong pcbRead, out ulong pcbWritten); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Commit(NativeConstants.STGC grfCommitFlags); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Revert(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void LockRegion(ulong libOffset, ulong cb, uint dwLockType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void UnlockRegion(ulong libOffset, ulong cb, uint dwLockType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Stat(out NativeStructs.STATSTG pstatstg, NativeConstants.STATFLAG grfStatFlag); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Clone(out IStream ppstm); + } + + [ComImport] + [Guid(NativeConstants.IID_IModalWindow)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IModalWindow + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + int Show([In] IntPtr parent); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileDialog)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileDialog : IModalWindow + { + // Defined on IModalWindow - repeated here due to requirements of COM interop layer + // -------------------------------------------------------------------------------- + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + new int Show([In] IntPtr parent); + + // IFileDialog-Specific interface members + // -------------------------------------------------------------------------------- + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileTypes( + [In] uint cFileTypes, + [In] [MarshalAs(UnmanagedType.LPArray)] NativeStructs.COMDLG_FILTERSPEC[] rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOptions([In] NativeConstants.FOS fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetOptions(out NativeConstants.FOS pfos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig] + int GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, NativeConstants.FDAP fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Close([MarshalAs(UnmanagedType.Error)] int hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig] + int ClearClientData(); + + // Not supported: IShellItemFilter is not defined, converting to IntPtr + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileOpenDialog)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileOpenDialog : IFileDialog + { + // Defined on IModalWindow - repeated here due to requirements of COM interop layer + // -------------------------------------------------------------------------------- + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + new int Show([In] IntPtr parent); + + // Defined on IFileDialog - repeated here due to requirements of COM interop layer + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileTypes( + [In] uint cFileTypes, + [In] [MarshalAs(UnmanagedType.LPArray)] NativeStructs.COMDLG_FILTERSPEC[] rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetOptions([In] NativeConstants.FOS fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetOptions(out NativeConstants.FOS pfos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig] + new int GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, NativeConstants.FDAP fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Close([MarshalAs(UnmanagedType.Error)] int hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void ClearClientData(); + + // Not supported: IShellItemFilter is not defined, converting to IntPtr + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + + // Defined by IFileOpenDialog + // --------------------------------------------------------------------------------- + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileSaveDialog)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileSaveDialog : IFileDialog + { + // Defined on IModalWindow - repeated here due to requirements of COM interop layer + // -------------------------------------------------------------------------------- + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + new int Show([In] IntPtr parent); + + // Defined on IFileDialog - repeated here due to requirements of COM interop layer + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileTypes( + [In] uint cFileTypes, + [In] [MarshalAs(UnmanagedType.LPArray)] NativeStructs.COMDLG_FILTERSPEC[] rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetOptions([In] NativeConstants.FOS fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetOptions(out NativeConstants.FOS pfos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig] + new int GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, NativeConstants.FDAP fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void Close([MarshalAs(UnmanagedType.Error)] int hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void ClearClientData(); + + // Not supported: IShellItemFilter is not defined, converting to IntPtr + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + new void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + + // Defined by IFileSaveDialog interface + // ----------------------------------------------------------------------------------- + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetSaveAsItem([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + // Not currently supported: IPropertyStore + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetProperties([In, MarshalAs(UnmanagedType.Interface)] IntPtr pStore); + + // Not currently supported: IPropertyDescriptionList + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetCollectedProperties([In, MarshalAs(UnmanagedType.Interface)] IntPtr pList, [In] int fAppendDefault); + + // Not currently supported: IPropertyStore + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetProperties([MarshalAs(UnmanagedType.Interface)] out IntPtr ppStore); + + // Not currently supported: IPropertyStore, IFileOperationProgressSink + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void ApplyProperties([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In, MarshalAs(UnmanagedType.Interface)] IntPtr pStore, [In, ComAliasName("ShellObjects.wireHWND")] ref IntPtr hwnd, [In, MarshalAs(UnmanagedType.Interface)] IntPtr pSink); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileDialogEvents)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileDialogEvents + { + // NOTE: some of these callbacks are cancelable - returning S_FALSE means that + // the dialog should not proceed (e.g. with closing, changing folder); to + // support this, we need to use the PreserveSig attribute to enable us to return + // the proper HRESULT + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + int OnFileOk([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig] + int OnFolderChanging([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd, [In, MarshalAs(UnmanagedType.Interface)] IShellItem psiFolder); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnFolderChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnSelectionChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnShareViolation( + [In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd, + [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, + out NativeConstants.FDE_SHAREVIOLATION_RESPONSE pResponse); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnTypeChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnOverwrite( + [In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd, + [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, + out NativeConstants.FDE_OVERWRITE_RESPONSE pResponse); + } + + [ComImport] + [Guid(NativeConstants.IID_IShellItem)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItem + { + // Not supported: IBindCtx + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IStream ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetDisplayName([In] NativeConstants.SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] NativeConstants.SFGAO sfgaoMask, out NativeConstants.SFGAO psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } + + [ComImport] + [Guid(NativeConstants.IID_IShellItemArray)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IShellItemArray + { + // Not supported: IBindCtx + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPropertyDescriptionList([In] ref NativeStructs.PROPERTYKEY keyType, [In] ref Guid riid, out IntPtr ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAttributes([In] NativeConstants.SIATTRIBFLAGS dwAttribFlags, [In] NativeConstants.SFGAO sfgaoMask, out NativeConstants.SFGAO psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCount(out uint pdwNumItems); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + // Not supported: IEnumShellItems (will use GetCount and GetItemAt instead) + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems); + } + + [ComImport] + [Guid(NativeConstants.IID_IKnownFolder)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IKnownFolder + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetId(out Guid pkfid); + + // Not yet supported - adding to fill slot in vtable + void spacer1(); + //[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + //void GetCategory(out mbtagKF_CATEGORY pCategory); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetShellItem([In] uint dwFlags, ref Guid riid, out IShellItem ppv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetPath([In] uint dwFlags, [MarshalAs(UnmanagedType.LPWStr)] out string ppszPath); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetPath([In] uint dwFlags, [In, MarshalAs(UnmanagedType.LPWStr)] string pszPath); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetLocation([In] uint dwFlags, [Out, ComAliasName("ShellObjects.wirePIDL")] IntPtr ppidl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolderType(out Guid pftid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetRedirectionCapabilities(out uint pCapabilities); + + // Not yet supported - adding to fill slot in vtable + void spacer2(); + //[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + //void GetFolderDefinition(out tagKNOWNFOLDER_DEFINITION pKFD); + } + + [ComImport] + [Guid(NativeConstants.IID_IKnownFolderManager)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IKnownFolderManager + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FolderIdFromCsidl([In] int nCsidl, out Guid pfid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FolderIdToCsidl([In] ref Guid rfid, out int pnCsidl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolderIds([Out] IntPtr ppKFId, [In, Out] ref uint pCount); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolder([In] ref Guid rfid, [MarshalAs(UnmanagedType.Interface)] out IKnownFolder ppkf); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetFolderByName([In, MarshalAs(UnmanagedType.LPWStr)] string pszCanonicalName, [MarshalAs(UnmanagedType.Interface)] out IKnownFolder ppkf); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void RegisterFolder([In] ref Guid rfid, [In] ref NativeStructs.KNOWNFOLDER_DEFINITION pKFD); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void UnregisterFolder([In] ref Guid rfid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FindFolderFromPath([In, MarshalAs(UnmanagedType.LPWStr)] string pszPath, [In] NativeConstants.FFFP_MODE mode, [MarshalAs(UnmanagedType.Interface)] out IKnownFolder ppkf); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void FindFolderFromIDList([In] IntPtr pidl, [MarshalAs(UnmanagedType.Interface)] out IKnownFolder ppkf); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Redirect([In] ref Guid rfid, [In] IntPtr hwnd, [In] uint Flags, [In, MarshalAs(UnmanagedType.LPWStr)] string pszTargetPath, [In] uint cFolders, [In] ref Guid pExclusion, [MarshalAs(UnmanagedType.LPWStr)] out string ppszError); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileDialogCustomize)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileDialogCustomize + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EnableOpenDropDown([In] int dwIDCtl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddMenu([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddPushButton([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddComboBox([In] int dwIDCtl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddRadioButtonList([In] int dwIDCtl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddCheckButton([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel, [In] bool bChecked); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddEditBox([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddSeparator([In] int dwIDCtl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddText([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetControlLabel([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetControlState([In] int dwIDCtl, [Out] out NativeConstants.CDCONTROLSTATE pdwState); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetControlState([In] int dwIDCtl, [In] NativeConstants.CDCONTROLSTATE dwState); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetEditBoxText([In] int dwIDCtl, [Out] IntPtr ppszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetEditBoxText([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCheckButtonState([In] int dwIDCtl, [Out] out bool pbChecked); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetCheckButtonState([In] int dwIDCtl, [In] bool bChecked); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void AddControlItem([In] int dwIDCtl, [In] int dwIDItem, [In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void RemoveControlItem([In] int dwIDCtl, [In] int dwIDItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void RemoveAllControlItems([In] int dwIDCtl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetControlItemState([In] int dwIDCtl, [In] int dwIDItem, [Out] out NativeConstants.CDCONTROLSTATE pdwState); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetControlItemState([In] int dwIDCtl, [In] int dwIDItem, [In] NativeConstants.CDCONTROLSTATE dwState); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetSelectedControlItem([In] int dwIDCtl, [Out] out int pdwIDItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetSelectedControlItem([In] int dwIDCtl, [In] int dwIDItem); // Not valid for OpenDropDown + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void StartVisualGroup([In] int dwIDCtl, [In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void EndVisualGroup(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void MakeProminent([In] int dwIDCtl); + } + + [ComImport] + [Guid(NativeConstants.IID_IFileDialogControlEvents)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IFileDialogControlEvents + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnItemSelected([In, MarshalAs(UnmanagedType.Interface)] IFileDialogCustomize pfdc, [In] int dwIDCtl, [In] int dwIDItem); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnButtonClicked([In, MarshalAs(UnmanagedType.Interface)] IFileDialogCustomize pfdc, [In] int dwIDCtl); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnCheckButtonToggled([In, MarshalAs(UnmanagedType.Interface)] IFileDialogCustomize pfdc, [In] int dwIDCtl, [In] bool bChecked); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void OnControlActivating([In, MarshalAs(UnmanagedType.Interface)] IFileDialogCustomize pfdc, [In] int dwIDCtl); + } + + [ComImport] + [Guid(NativeConstants.IID_IPropertyStore)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IPropertyStore + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetCount([Out] out uint cProps); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetAt([In] uint iProp, out NativeStructs.PROPERTYKEY pkey); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void GetValue([In] ref NativeStructs.PROPERTYKEY key, out object pv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetValue([In] ref NativeStructs.PROPERTYKEY key, [In] ref object pv); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void Commit(); + } + + // --------------------------------------------------------- + // Coclass interfaces - designed to "look like" the object + // in the API, so that the 'new' operator can be used in a + // straightforward way. Behind the scenes, the C# compiler + // morphs all 'new CoClass()' calls to 'new CoClassWrapper()' + [ComImport] + [Guid(NativeConstants.IID_IFileOperation)] + [CoClass(typeof(FileOperationRCW))] + public interface NativeFileOperation : IFileOperation + { + } + + [ComImport] + [Guid(NativeConstants.IID_IFileOpenDialog)] + [CoClass(typeof(FileOpenDialogRCW))] + public interface NativeFileOpenDialog : IFileOpenDialog + { + } + + [ComImport] + [Guid(NativeConstants.IID_IFileSaveDialog)] + [CoClass(typeof(FileSaveDialogRCW))] + public interface NativeFileSaveDialog : IFileSaveDialog + { + } + + [ComImport] + [Guid(NativeConstants.IID_IKnownFolderManager)] + [CoClass(typeof(KnownFolderManagerRCW))] + public interface KnownFolderManager : IKnownFolderManager + { + } + + // --------------------------------------------------- + // .NET classes representing runtime callable wrappers + [ComImport] + [ClassInterface(ClassInterfaceType.None)] + [TypeLibType(TypeLibTypeFlags.FCanCreate)] + [Guid(NativeConstants.CLSID_FileOperation)] + public class FileOperationRCW + { + } + + [ComImport] + [ClassInterface(ClassInterfaceType.None)] + [TypeLibType(TypeLibTypeFlags.FCanCreate)] + [Guid(NativeConstants.CLSID_FileOpenDialog)] + public class FileOpenDialogRCW + { + } + + [ComImport] + [ClassInterface(ClassInterfaceType.None)] + [TypeLibType(TypeLibTypeFlags.FCanCreate)] + [Guid(NativeConstants.CLSID_FileSaveDialog)] + public class FileSaveDialogRCW + { + } + + [ComImport] + [ClassInterface(ClassInterfaceType.None)] + [TypeLibType(TypeLibTypeFlags.FCanCreate)] + [Guid(NativeConstants.CLSID_KnownFolderManager)] + public class KnownFolderManagerRCW + { + } + } +} diff --git a/src/SystemLayer/NativeMethods.cs b/src/SystemLayer/NativeMethods.cs new file mode 100644 index 0000000..cc8dc53 --- /dev/null +++ b/src/SystemLayer/NativeMethods.cs @@ -0,0 +1,209 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Text; + +namespace PaintDotNet.SystemLayer +{ + internal static class NativeMethods + { + internal static bool SUCCEEDED(int hr) + { + return hr >= 0; + } + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] + internal static extern void SHGetFolderPathW( + IntPtr hwndOwner, + int nFolder, + IntPtr hToken, + uint dwFlags, + StringBuilder lpszPath); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteFileW( + [MarshalAs(UnmanagedType.LPWStr)] string lpFileName); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool RemoveDirectoryW( + [MarshalAs(UnmanagedType.LPWStr)] string lpPathName); + + [DllImport("user32.dll", SetLastError = true)] + public static extern uint WaitForInputIdle( + IntPtr hProcess, + uint dwMilliseconds); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumWindows( + [MarshalAs(UnmanagedType.FunctionPtr)] NativeDelegates.EnumWindowsProc lpEnumFunc, + IntPtr lParam); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess( + uint dwDesiredAccess, + [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, + uint dwProcessId); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenProcessToken( + IntPtr ProcessHandle, + uint DesiredAccess, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DuplicateTokenEx( + IntPtr hExistingToken, + uint dwDesiredAccess, + IntPtr lpTokenAttributes, + NativeConstants.SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, + NativeConstants.TOKEN_TYPE TokenType, + out IntPtr phNewToken); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CreateProcessWithTokenW( + IntPtr hToken, + uint dwLogonFlags, + IntPtr lpApplicationName, + IntPtr lpCommandLine, + uint dwCreationFlags, + IntPtr lpEnvironment, + IntPtr lpCurrentDirectory, + IntPtr lpStartupInfo, + out NativeStructs.PROCESS_INFORMATION lpProcessInfo); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenClipboard(IntPtr hWndNewOwner); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseClipboard(); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr GetClipboardData(uint format); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsClipboardFormatAvailable(uint format); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)] + internal static extern void SHCreateItemFromParsingName( + [MarshalAs(UnmanagedType.LPWStr)] string pszPath, + IntPtr pbc, + ref Guid riid, + out IntPtr ppv); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool VerifyVersionInfo( + ref NativeStructs.OSVERSIONINFOEX lpVersionInfo, + uint dwTypeMask, + ulong dwlConditionMask); + + [DllImport("kernel32.dll")] + internal static extern ulong VerSetConditionMask( + ulong dwlConditionMask, + uint dwTypeBitMask, + byte dwConditionMask); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl( + IntPtr hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + uint nInBufferSize, + IntPtr lpOutBuffer, + uint nOutBufferSize, + ref uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ShellExecuteExW(ref NativeStructs.SHELLEXECUTEINFO lpExecInfo); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GlobalMemoryStatusEx(ref NativeStructs.MEMORYSTATUSEX lpBuffer); + + [DllImport("shell32.dll", SetLastError = false)] + internal static extern void SHAddToRecentDocs(uint uFlags, IntPtr pv); + + [DllImport("kernel32.dll", SetLastError = false)] + internal static extern void GetSystemInfo(ref NativeStructs.SYSTEM_INFO lpSystemInfo); + + [DllImport("kernel32.dll", SetLastError = false)] + internal static extern void GetNativeSystemInfo(ref NativeStructs.SYSTEM_INFO lpSystemInfo); + + [DllImport("Wintrust.dll", PreserveSig = true, SetLastError = false)] + internal extern static unsafe int WinVerifyTrust( + IntPtr hWnd, + ref Guid pgActionID, + ref NativeStructs.WINTRUST_DATA pWinTrustData + ); + + [DllImport("SetupApi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr SetupDiGetClassDevsW( + ref Guid ClassGuid, + [MarshalAs(UnmanagedType.LPWStr)] string Enumerator, + IntPtr hwndParent, + uint Flags); + + [DllImport("SetupApi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); + + [DllImport("SetupApi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetupDiEnumDeviceInfo( + IntPtr DeviceInfoSet, + uint MemberIndex, + ref NativeStructs.SP_DEVINFO_DATA DeviceInfoData); + + [DllImport("SetupApi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetupDiGetDeviceInstanceIdW( + IntPtr DeviceInfoSet, + ref NativeStructs.SP_DEVINFO_DATA DeviceInfoData, + IntPtr DeviceInstanceId, + uint DeviceInstanceIdSize, + out uint RequiredSize); + + internal static void ThrowOnWin32Error(string message) + { + int lastWin32Error = Marshal.GetLastWin32Error(); + ThrowOnWin32Error(message, lastWin32Error); + } + + internal static void ThrowOnWin32Error(string message, NativeErrors lastWin32Error) + { + ThrowOnWin32Error(message, (int)lastWin32Error); + } + + internal static void ThrowOnWin32Error(string message, int lastWin32Error) + { + if (lastWin32Error != NativeConstants.ERROR_SUCCESS) + { + string exMessageFormat = "{0} ({1}, {2})"; + string exMessage = string.Format(exMessageFormat, message, lastWin32Error, ((NativeErrors)lastWin32Error).ToString()); + + throw new Win32Exception(lastWin32Error, exMessage); + } + } + } +} diff --git a/src/SystemLayer/NativeStructs.cs b/src/SystemLayer/NativeStructs.cs new file mode 100644 index 0000000..053e6b8 --- /dev/null +++ b/src/SystemLayer/NativeStructs.cs @@ -0,0 +1,344 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer +{ + internal static class NativeStructs + { + [StructLayout(LayoutKind.Sequential)] + internal struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public uint dwProcessId; + public uint dwThreadId; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct STATSTG + { + public IntPtr pwcsName; + public NativeConstants.STGTY type; + public ulong cbSize; + public System.Runtime.InteropServices.ComTypes.FILETIME mtime; + public System.Runtime.InteropServices.ComTypes.FILETIME ctime; + public System.Runtime.InteropServices.ComTypes.FILETIME atime; + public uint grfMode; + public uint grfLocksSupported; + public Guid clsid; + public uint grfStateBits; + public uint reserved; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)] + internal struct KNOWNFOLDER_DEFINITION + { + public NativeConstants.KF_CATEGORY category; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszName; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszCreator; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszDescription; + public Guid fidParent; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszRelativePath; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszParsingName; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszToolTip; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszLocalizedName; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszIcon; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszSecurity; + public uint dwAttributes; + public NativeConstants.KF_DEFINITION_FLAGS kfdFlags; + public Guid ftidType; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4)] + internal struct PROPERTYKEY + { + public Guid fmtid; + public uint pid; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)] + internal struct COMDLG_FILTERSPEC + { + [MarshalAs(UnmanagedType.LPWStr)] + public string pszName; + [MarshalAs(UnmanagedType.LPWStr)] + public string pszSpec; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SYSTEM_INFO + { + public ushort wProcessorArchitecture; + public ushort wReserved; + public uint dwPageSize; + public IntPtr lpMinimumApplicationAddress; + public IntPtr lpMaximumApplicationAddress; + public UIntPtr dwActiveProcessorMask; + public uint dwNumberOfProcessors; + public uint dwProcessorType; + public uint dwAllocationGranularity; + public ushort wProcessorLevel; + public ushort wProcessorRevision; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct OSVERSIONINFOEX + { + public static int SizeOf + { + get + { + return Marshal.SizeOf(typeof(OSVERSIONINFOEX)); + } + } + + public uint dwOSVersionInfoSize; + public uint dwMajorVersion; + public uint dwMinorVersion; + public uint dwBuildNumber; + public uint dwPlatformId; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szCSDVersion; + + public ushort wServicePackMajor; + + public ushort wServicePackMinor; + public ushort wSuiteMask; + public byte wProductType; + public byte wReserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct COPYDATASTRUCT + { + internal UIntPtr dwData; + internal uint cbData; + internal IntPtr lpData; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct SHELLEXECUTEINFO + { + internal uint cbSize; + internal uint fMask; + internal IntPtr hwnd; + [MarshalAs(UnmanagedType.LPTStr)] internal string lpVerb; + [MarshalAs(UnmanagedType.LPTStr)] internal string lpFile; + [MarshalAs(UnmanagedType.LPTStr)] internal string lpParameters; + [MarshalAs(UnmanagedType.LPTStr)] internal string lpDirectory; + internal int nShow; + internal IntPtr hInstApp; + internal IntPtr lpIDList; + [MarshalAs(UnmanagedType.LPTStr)] internal string lpClass; + internal IntPtr hkeyClass; + internal uint dwHotKey; + internal IntPtr hIcon_or_hMonitor; + internal IntPtr hProcess; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MEMORYSTATUSEX + { + internal uint dwLength; + internal uint dwMemoryLoad; + internal ulong ullTotalPhys; + internal ulong ullAvailPhys; + internal ulong ullTotalPageFile; + internal ulong ullAvailPageFile; + internal ulong ullTotalVirtual; + internal ulong ullAvailVirtual; + internal ulong ullAvailExtendedVirtual; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct OVERLAPPED + { + internal UIntPtr Internal; + internal UIntPtr InternalHigh; + internal uint Offset; + internal uint OffsetHigh; + internal IntPtr hEvent; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct RGBQUAD + { + internal byte rgbBlue; + internal byte rgbGreen; + internal byte rgbRed; + internal byte rgbReserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BITMAPINFOHEADER + { + internal uint biSize; + internal int biWidth; + internal int biHeight; + internal ushort biPlanes; + internal ushort biBitCount; + internal uint biCompression; + internal uint biSizeImage; + internal int biXPelsPerMeter; + internal int biYPelsPerMeter; + internal uint biClrUsed; + internal uint biClrImportant; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct BITMAPINFO + { + internal BITMAPINFOHEADER bmiHeader; + internal RGBQUAD bmiColors; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct MEMORY_BASIC_INFORMATION + { + internal void *BaseAddress; + internal void *AllocationBase; + internal uint AllocationProtect; + internal UIntPtr RegionSize; + internal uint State; + internal uint Protect; + internal uint Type; + }; + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal class LOGFONT + { + internal int lfHeight = 0; + internal int lfWidth = 0; + internal int lfEscapement = 0; + internal int lfOrientation = 0; + internal int lfWeight = 0; + internal byte lfItalic = 0; + internal byte lfUnderline = 0; + internal byte lfStrikeOut = 0; + internal byte lfCharSet = 0; + internal byte lfOutPrecision = 0; + internal byte lfClipPrecision = 0; + internal byte lfQuality = 0; + internal byte lfPitchAndFamily = 0; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + internal string lfFaceName = string.Empty; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct LOGBRUSH + { + internal uint lbStyle; + internal uint lbColor; + internal int lbHatch; + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct RGNDATAHEADER + { + internal uint dwSize; + internal uint iType; + internal uint nCount; + internal uint nRgnSize; + internal RECT rcBound; + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct RGNDATA + { + internal RGNDATAHEADER rdh; + + internal unsafe static RECT *GetRectsPointer(RGNDATA *me) + { + return (RECT *)((byte *)me + sizeof(RGNDATAHEADER)); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct POINT + { + internal int x; + internal int y; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct RECT + { + internal int left; + internal int top; + internal int right; + internal int bottom; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct PropertyItem + { + internal int id; + internal uint length; + internal short type; + internal void *value; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct WINTRUST_DATA + { + internal uint cbStruct; + internal IntPtr pPolicyCallbackData; + internal IntPtr pSIPClientData; + internal uint dwUIChoice; + internal uint fdwRevocationChecks; + internal uint dwUnionChoice; + internal void *pInfo; // pFile, pCatalog, pBlob, pSgnr, or pCert + internal uint dwStateAction; + internal IntPtr hWVTStateData; + internal IntPtr pwszURLReference; + internal uint dwProvFlags; + internal uint dwUIContext; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal unsafe struct WINTRUST_FILE_INFO + { + internal uint cbStruct; + internal char *pcwszFilePath; + internal IntPtr hFile; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + { + internal bool fAutoDetect; + internal IntPtr lpszAutoConfigUrl; + internal IntPtr lpszProxy; + internal IntPtr lpszProxyBypass; + }; + + [StructLayout(LayoutKind.Sequential)] + internal struct SP_DEVINFO_DATA + { + public uint cbSize; + public Guid ClassGuid; + public uint DevInst; + public UIntPtr Reserved; + } + } +} diff --git a/src/SystemLayer/Network.cs b/src/SystemLayer/Network.cs new file mode 100644 index 0000000..91da6a0 --- /dev/null +++ b/src/SystemLayer/Network.cs @@ -0,0 +1,161 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer +{ + public static class Network + { + internal class ProxyInfo + { + private bool autoDetect; + private string autoConfigUrl; + private string proxy; + private string proxyBypass; + + /* + public bool AutoDetect + { + get + { + return this.autoDetect; + } + } + + public string AutoConfigUrl + { + get + { + return this.autoConfigUrl; + } + } + * */ + + public string Proxy + { + get + { + return this.proxy; + } + } + + /* + public string ProxyBypass + { + get + { + return this.proxyBypass; + } + } + * */ + + internal ProxyInfo(bool autoDetect, string autoConfigUrl, string proxy, string proxyBypass) + { + this.autoDetect = autoDetect; + this.autoConfigUrl = autoConfigUrl; + this.proxy = proxy; + this.proxyBypass = proxyBypass; + } + } + + internal static bool GetProxyDetails(out ProxyInfo proxyInfo) + { + NativeStructs.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig = new NativeStructs.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG(); + bool result = SafeNativeMethods.WinHttpGetIEProxyConfigForCurrentUser(ref proxyConfig); + + bool autoDetect; + string autoConfigUrl; + string proxy; + string proxyBypass; + + if (result) + { + autoDetect = proxyConfig.fAutoDetect; + autoConfigUrl = Marshal.PtrToStringUni(proxyConfig.lpszAutoConfigUrl); + proxy = Marshal.PtrToStringUni(proxyConfig.lpszProxy); + proxyBypass = Marshal.PtrToStringUni(proxyConfig.lpszProxyBypass); + + if (proxyConfig.lpszAutoConfigUrl != IntPtr.Zero) + { + SafeNativeMethods.GlobalFree(proxyConfig.lpszAutoConfigUrl); + proxyConfig.lpszAutoConfigUrl = IntPtr.Zero; + } + + if (proxyConfig.lpszProxy != IntPtr.Zero) + { + SafeNativeMethods.GlobalFree(proxyConfig.lpszProxy); + proxyConfig.lpszProxy = IntPtr.Zero; + } + + if (proxyConfig.lpszProxyBypass != IntPtr.Zero) + { + SafeNativeMethods.GlobalFree(proxyConfig.lpszProxyBypass); + proxyConfig.lpszProxyBypass = IntPtr.Zero; + } + } + else + { + autoDetect = false; + autoConfigUrl = null; + proxy = null; + proxyBypass = null; + } + + proxyInfo = new ProxyInfo(autoDetect, autoConfigUrl, proxy, proxyBypass); + return result; + } + + /// + /// Returns a list of proxies that should be usable to get access to Internet URI's. + /// + /// An array of WebProxy instances. A null value at any array location indicates to use no proxy. + public static WebProxy[] GetProxyList() + { + List proxies = new List(); + + // Get the IE-supplied proxy settings + ProxyInfo info; + bool result = GetProxyDetails(out info); + + if (result) + { + WebProxy proxy; + + try + { + proxy = new WebProxy(info.Proxy); + } + + catch (Exception) + { + proxy = null; + } + + if (proxy != null) + { + // We will add the proxy twice. First, with the default credentials for the logged-in user. Second, with no credentials. + WebProxy proxyWithCreds = new WebProxy(info.Proxy); + proxyWithCreds.Credentials = CredentialCache.DefaultCredentials; + proxies.Add(proxyWithCreds); + proxies.Add(proxy); + } + } + + // No proxy is the lowest priority + proxies.Add(null); + + return proxies.ToArray(); + } + } +} diff --git a/src/SystemLayer/NullGraphics.cs b/src/SystemLayer/NullGraphics.cs new file mode 100644 index 0000000..addc47f --- /dev/null +++ b/src/SystemLayer/NullGraphics.cs @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Sometimes you need a Graphics instance when you don't really have access to one. + /// Example situations include retrieving the bounds or scanlines of a Region. + /// So use this to create a 'null' Graphics instance that effectively eats all + /// rendering calls. + /// + public sealed class NullGraphics + : IDisposable + { + private IntPtr hdc = IntPtr.Zero; + private Graphics graphics = null; + private bool disposed = false; + + public Graphics Graphics + { + get + { + return graphics; + } + } + + public NullGraphics() + { + this.hdc = SafeNativeMethods.CreateCompatibleDC(IntPtr.Zero); + + if (this.hdc == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("CreateCompatibleDC returned NULL"); + } + + this.graphics = Graphics.FromHdc(this.hdc); + } + + ~NullGraphics() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + this.graphics.Dispose(); + this.graphics = null; + } + + SafeNativeMethods.DeleteDC(this.hdc); + disposed = true; + } + } + } +} diff --git a/src/SystemLayer/OS.cs b/src/SystemLayer/OS.cs new file mode 100644 index 0000000..890c62f --- /dev/null +++ b/src/SystemLayer/OS.cs @@ -0,0 +1,288 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using System; +using System.Globalization; + +namespace PaintDotNet.SystemLayer +{ + public static class OS + { + /// + /// Gets a flag indicating whether we are running on Windows Vista or later. + /// + /// + /// This is the preferred method for detecting this condition, and the most performant + /// (it avoids creating some temporary Version objects). + /// + public static bool IsVistaOrLater + { + get + { + return Environment.OSVersion.Version.Major >= 6; + } + } + + public static Version WindowsXP + { + get + { + return new Version(5, 1); + } + } + + public static Version WindowsServer2003 + { + get + { + return new Version(5, 2); + } + } + + public static Version WindowsVista + { + get + { + return new Version(6, 0); + } + } + + public static string Revision + { + get + { + NativeStructs.OSVERSIONINFOEX osviex = new NativeStructs.OSVERSIONINFOEX(); + osviex.dwOSVersionInfoSize = (uint)NativeStructs.OSVERSIONINFOEX.SizeOf; + bool result = SafeNativeMethods.GetVersionEx(ref osviex); + + if (result) + { + return osviex.szCSDVersion; + } + else + { + return "Unknown"; + } + } + } + + public static OSType Type + { + get + { + NativeStructs.OSVERSIONINFOEX osviex = new NativeStructs.OSVERSIONINFOEX(); + osviex.dwOSVersionInfoSize = (uint)NativeStructs.OSVERSIONINFOEX.SizeOf; + bool result = SafeNativeMethods.GetVersionEx(ref osviex); + OSType type; + + if (result) + { + if (Enum.IsDefined(typeof(OSType), (OSType)osviex.wProductType)) + { + type = (OSType)osviex.wProductType; + } + else + { + type = OSType.Unknown; + } + } + else + { + type = OSType.Unknown; + } + + return type; + } + } + + public static bool IsDotNetVersionInstalled(int major, int minor, int servicePack, bool allowClientSubset) + { + bool result = false; + + if (!result) + { + // .NET 2.0 should always have a build # of 50727 + result |= IsDotNet2VersionInstalled(major, minor, 50727, servicePack); + } + + if (!result) + { + result |= IsDotNet3VersionInstalled(major, minor, servicePack); + } + + if (!result && allowClientSubset) + { + result |= IsDotNet3ClientVersionInstalled(major, minor, servicePack); + } + + return result; + } + + private static bool IsDotNet2VersionInstalled(int major, int minor, int build, int minServicePack) + { + bool result = false; + + const string regKeyNameFormat = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v{0}.{1}.{2}"; + string regKeyName = string.Format(regKeyNameFormat, major.ToString(CultureInfo.InvariantCulture), + minor.ToString(CultureInfo.InvariantCulture), build.ToString(CultureInfo.InvariantCulture)); + + if (!result) + { + const string regValueName = "Install"; + result |= CheckForRegValueEqualsInt(regKeyName, regValueName, 1); + } + + if (result) + { + const string regValueName = "SP"; + int? spLevel = GetRegValueAsInt(regKeyName, regValueName); + result &= (spLevel.HasValue && spLevel.Value >= minServicePack); + } + + return result; + } + + private static bool IsDotNet3VersionInstalled(int major, int minor, int minServicePack) + { + bool result = false; + + const string regKeyNameFormat = "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v{0}.{1}"; + string regKeyName = string.Format(regKeyNameFormat, major, minor); + + if (!result) + { + const string regValueName = "Install"; + result |= CheckForRegValueEqualsInt(regKeyName, regValueName, 1); + } + + if (result) + { + const string regValueName = "SP"; + int? spLevel = GetRegValueAsInt(regKeyName, regValueName); + result &= (spLevel.HasValue && spLevel.Value >= minServicePack); + } + + return result; + } + + private static bool IsDotNet3ClientVersionInstalled(int major, int minor, int minServicePack) + { + bool result = false; + + const string regKeyNameFormat = "SOFTWARE\\Microsoft\\NET Framework Setup\\DotNetClient\\v{0}.{1}"; + string regKeyName = string.Format(regKeyNameFormat, major, minor); + + if (!result) + { + const string regValueName = "Install"; + result |= CheckForRegValueEqualsInt(regKeyName, regValueName, 1); + } + + if (result) + { + const string regValueName = "SP"; + int? spLevel = GetRegValueAsInt(regKeyName, regValueName); + result &= (spLevel.HasValue && spLevel.Value >= minServicePack); + } + + return result; + } + + private static int? GetRegValueAsInt(string regKeyName, string regValueName) + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(regKeyName, false)) + { + object value = null; + + if (key != null) + { + value = key.GetValue(regValueName); + } + + if (value != null && value is int) + { + return (int)value; + } + else + { + return null; + } + } + } + + private static bool CheckForRegValueEqualsInt(string regKeyName, string regValueName, int intValue) + { + int? value = GetRegValueAsInt(regKeyName, regValueName); + + if (value.HasValue) + { + return value.Value == intValue; + } + else + { + return false; + } + } + + public static bool CheckWindowsVersion(int major, int minor, short servicePack) + { + NativeStructs.OSVERSIONINFOEX osvi = new NativeStructs.OSVERSIONINFOEX(); + osvi.dwOSVersionInfoSize = (uint)NativeStructs.OSVERSIONINFOEX.SizeOf; + osvi.dwMajorVersion = (uint)major; + osvi.dwMinorVersion = (uint)minor; + osvi.wServicePackMajor = (ushort)servicePack; + + ulong mask = 0; + mask = NativeMethods.VerSetConditionMask(mask, NativeConstants.VER_MAJORVERSION, NativeConstants.VER_GREATER_EQUAL); + mask = NativeMethods.VerSetConditionMask(mask, NativeConstants.VER_MINORVERSION, NativeConstants.VER_GREATER_EQUAL); + mask = NativeMethods.VerSetConditionMask(mask, NativeConstants.VER_SERVICEPACKMAJOR, NativeConstants.VER_GREATER_EQUAL); + + bool result = NativeMethods.VerifyVersionInfo( + ref osvi, + NativeConstants.VER_MAJORVERSION | + NativeConstants.VER_MINORVERSION | + NativeConstants.VER_SERVICEPACKMAJOR, + mask); + + return result; + } + + // Requires: + // * Windows XP SP2 or later + // * Windows Server 2003 SP1 or later + // * Windows Vista + // * or later (must be NT-based) + public static bool CheckOSRequirement() + { + // Just say "no" to Windows 9x + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + return false; + } + + // Windows Vista or later? + bool winVista = OS.CheckWindowsVersion(6, 0, 0); + + // Windows 2003 or later? + bool win2k3 = OS.CheckWindowsVersion(5, 2, 0); + + // Windows 2003 SP1 or later? + bool win2k3SP1 = OS.CheckWindowsVersion(5, 2, 1); + + // Windows XP or later? + bool winXP = OS.CheckWindowsVersion(5, 1, 0); + + // Windows XP SP2 or later? + bool winXPSP2 = OS.CheckWindowsVersion(5, 1, 2); + + return winVista || (win2k3 && win2k3SP1) || (winXP && winXPSP2); + } + } +} diff --git a/src/SystemLayer/OSType.cs b/src/SystemLayer/OSType.cs new file mode 100644 index 0000000..85f8ec4 --- /dev/null +++ b/src/SystemLayer/OSType.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public enum OSType + { + Unknown = 0, + Workstation = (int)NativeConstants.VER_NT_WORKSTATION, + DomainController = (int)NativeConstants.VER_NT_DOMAIN_CONTROLLER, + Server = (int)NativeConstants.VER_NT_SERVER, + } +} diff --git a/src/SystemLayer/PdnGraphics.cs b/src/SystemLayer/PdnGraphics.cs new file mode 100644 index 0000000..625954b --- /dev/null +++ b/src/SystemLayer/PdnGraphics.cs @@ -0,0 +1,485 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer +{ + /// + /// These methods are used because we found some bugs in GDI+ / WinForms. Some + /// were the cause of major flickering with the transparent toolforms. + /// Other implementations of this class, or more generic implementations, may safely + /// thunk straight to equivelants in System.Drawing.Graphics. + /// + public static class PdnGraphics + { + public static GraphicsPath ClipPath(GraphicsPath subjectPath, CombineMode combineMode, GraphicsPath clipPath) + { + GpcWrapper.Polygon.Validate(combineMode); + + GpcWrapper.Polygon basePoly = new GpcWrapper.Polygon(subjectPath); + + GraphicsPath clipClone = (GraphicsPath)clipPath.Clone(); + clipClone.CloseAllFigures(); + GpcWrapper.Polygon clipPoly = new GpcWrapper.Polygon(clipClone); + clipClone.Dispose(); + + GpcWrapper.Polygon clippedPoly = GpcWrapper.Polygon.Clip(combineMode, basePoly, clipPoly); + + GraphicsPath returnPath = clippedPoly.ToGraphicsPath(); + returnPath.CloseAllFigures(); + return returnPath; + } + + public static void SetPropertyItems(Image image, PropertyItem[] items) + { + PropertyItem[] pis = image.PropertyItems; + + foreach (PropertyItem pi in pis) + { + image.RemovePropertyItem(pi.Id); + } + + foreach (PropertyItem pi in items) + { + image.SetPropertyItem(pi); + } + } + + /// + /// Creates a new, zero-filled PropertyItem. + /// + /// A PropertyItem that is zero-filled. + public static PropertyItem CreatePropertyItem() + { + PropertyItem2 pi2 = new PropertyItem2(0, 0, 0, new byte[0]); + return pi2.ToPropertyItem(); + } + + /// + /// Copies the given PropertyItem. + /// + /// The PropertyItem to clone. + /// A copy of the given PropertyItem. + public static PropertyItem ClonePropertyItem(PropertyItem pi) + { + byte[] valueClone; + + if (pi.Value == null) + { + valueClone = new byte[0]; + } + else + { + valueClone = (byte[])pi.Value.Clone(); + } + + PropertyItem2 pi2 = new PropertyItem2(pi.Id, pi.Len, pi.Type, valueClone); + return pi2.ToPropertyItem(); + } + + /// + /// Serializes a PropertyItem into a string blob. + /// + /// The PropertyItem to serialize. + /// A string that may be later deserialized using DeserializePropertyItem. + /// + /// Note to implementors: The format for the serialized data is intentionally opaque for programmatic users + /// of this class. However, since this data goes into .PDN files, it must be carefully maintained. See + /// the PropertyItem2 class for details. + /// + public static string SerializePropertyItem(PropertyItem pi) + { + PropertyItem2 pi2 = PropertyItem2.FromPropertyItem(pi); + return pi2.ToBlob(); + } + + /// + /// Deserializes a PropertyItem from a string previously returned from SerializePropertyItem. + /// + /// The string data to deserialize. + /// A PropertyItem instance. + /// + /// Note to implementors: The format for the serialized data is intentionally opaque for programmatic users + /// of this class. However, since this data goes into .PDN files, it must be carefully maintained. See + /// the PropertyItem2 class for details. + /// + public static PropertyItem DeserializePropertyItem(string piBlob) + { + PropertyItem2 pi2 = PropertyItem2.FromBlob(piBlob); + return pi2.ToPropertyItem(); + } + + /// + /// Draws a bitmap to a Graphics context. + /// + /// The Graphics context to draw the bitmap on to. + /// The clipping rectangle in destination coordinates. + /// The transformation matrix to apply. This is only used to transform the upper-left corner of dstRect. + /// The handle to the bitmap obtained from Memory.AllocateBitmap(). + /// The full width of the bitmap. + /// The full height of the bitmap. + /// The left edge of the source bitmap to draw from. + /// The top edge of the source bitmap to draw from. + public unsafe static void DrawBitmap( + Graphics dst, + Rectangle dstRect, + Matrix dstMatrix, + IntPtr srcBitmapHandle, + int srcOffsetX, + int srcOffsetY) + { + if (srcBitmapHandle == IntPtr.Zero) + { + throw new ArgumentNullException("srcBitmapHandle"); + } + + Point[] points = new Point[] { dstRect.Location }; + dstMatrix.TransformPoints(points); + dstRect.Location = points[0]; + + IntPtr hdc = IntPtr.Zero; + IntPtr hbitmap = IntPtr.Zero; + IntPtr chdc = IntPtr.Zero; + IntPtr old = IntPtr.Zero; + + try + { + hdc = dst.GetHdc(); + chdc = SafeNativeMethods.CreateCompatibleDC(hdc); + old = SafeNativeMethods.SelectObject(chdc, srcBitmapHandle); + SafeNativeMethods.BitBlt(hdc, dstRect.Left, dstRect.Top, dstRect.Width, + dstRect.Height, chdc, srcOffsetX, srcOffsetY, NativeConstants.SRCCOPY); + } + + finally + { + if (old != IntPtr.Zero) + { + SafeNativeMethods.SelectObject(chdc, old); + old = IntPtr.Zero; + } + + if (chdc != IntPtr.Zero) + { + SafeNativeMethods.DeleteDC(chdc); + chdc = IntPtr.Zero; + } + + if (hdc != IntPtr.Zero) + { + dst.ReleaseHdc(hdc); + hdc = IntPtr.Zero; + } + } + + GC.KeepAlive(dst); + } + + internal unsafe static void GetRegionScans(IntPtr hRgn, out Rectangle[] scans, out int area) + { + uint bytes = 0; + int countdown = screwUpMax; + int error = 0; + + // HACK: It seems that sometimes the GetRegionData will return ERROR_INVALID_HANDLE + // even though the handle (the HRGN) is fine. Maybe the function is not + // re-entrant? I'm not sure, but trying it again seems to fix it. + while (countdown > 0) + { + bytes = SafeNativeMethods.GetRegionData(hRgn, 0, (NativeStructs.RGNDATA *)IntPtr.Zero); + error = Marshal.GetLastWin32Error(); + + if (bytes == 0) + { + --countdown; + System.Threading.Thread.Sleep(5); + } + else + { + break; + } + } + + // But if we retry several times and it still messes up then we will finally give up. + if (bytes == 0) + { + throw new Win32Exception(error, "GetRegionData returned " + bytes.ToString() + ", GetLastError() = " + error.ToString()); + } + + byte *data; + + // Up to 512 bytes, allocate on the stack. Otherwise allocate from the heap. + if (bytes <= 512) + { + byte *data1 = stackalloc byte[(int)bytes]; + data = data1; + } + else + { + data = (byte *)Memory.Allocate(bytes).ToPointer(); + } + + try + { + NativeStructs.RGNDATA *pRgnData = (NativeStructs.RGNDATA *)data; + uint result = SafeNativeMethods.GetRegionData(hRgn, bytes, pRgnData); + + if (result != bytes) + { + throw new OutOfMemoryException("SafeNativeMethods.GetRegionData returned 0"); + } + + NativeStructs.RECT *pRects = NativeStructs.RGNDATA.GetRectsPointer(pRgnData); + scans = new Rectangle[pRgnData->rdh.nCount]; + area = 0; + + for (int i = 0; i < scans.Length; ++i) + { + scans[i] = Rectangle.FromLTRB(pRects[i].left, pRects[i].top, pRects[i].right, pRects[i].bottom); + area += scans[i].Width * scans[i].Height; + } + + pRects = null; + pRgnData = null; + } + + finally + { + if (bytes > 512) + { + Memory.Free(new IntPtr(data)); + } + } + } + + private const int screwUpMax = 100; + + /// + /// Retrieves an array of rectangles that approximates a region, and computes the + /// pixel area of it. This method is necessary to work around some bugs in .NET + /// and to increase performance for the way in which we typically use this data. + /// + /// The Region to retrieve data from. + /// An array of Rectangle to put the scans into. + /// An integer to write the computed area of the region into. + /// + /// Note to implementors: Simple implementations may simple call region.GetRegionScans() + /// and process the data for the 'out' variables. + public static void GetRegionScans(Region region, out Rectangle[] scans, out int area) + { + using (NullGraphics nullGraphics = new NullGraphics()) + { + IntPtr hRgn = IntPtr.Zero; + + try + { + hRgn = region.GetHrgn(nullGraphics.Graphics); + GetRegionScans(hRgn, out scans, out area); + } + + finally + { + if (hRgn != IntPtr.Zero) + { + SafeNativeMethods.DeleteObject(hRgn); + hRgn = IntPtr.Zero; + } + } + } + + GC.KeepAlive(region); + } + + /// + /// Draws a polygon. The last point is not joined to the beginning point. If there is an error while + /// trying to draw, it is discarded and ignored. + /// + /// The Graphics context to draw to. + /// The points to draw. Lines are drawn between every point N to point N+1. + /// The color to draw with. + /// + /// Note to implementors: This method is used to avoid drawing with GDI+, which avoids flickering + /// with our transparent toolforms. Implementations may thunk straight to g.DrawLines(). + /// + public static void DrawPolyLine(Graphics g, Color color, Point[] points) + { + try + { + DrawPolyLineImpl(g, color, points); + } + + catch (Exception ex) + { + Tracing.Ping("Exception while executing PdnGraphics.DrawPolyLine: " + ex.ToString()); + } + } + + private static void DrawPolyLineImpl(Graphics g, Color color, Point[] points) + { + if (points.Length < 1) + { + return; + } + + uint nativeColor = (uint)(color.R + (color.G << 8) + (color.B << 16)); + + IntPtr hdc = IntPtr.Zero; + IntPtr pen = IntPtr.Zero; + IntPtr oldObject = IntPtr.Zero; + + try + { + hdc = g.GetHdc(); + pen = SafeNativeMethods.CreatePen(NativeConstants.PS_SOLID, 1, nativeColor); + + if (pen == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("CreatePen returned NULL"); + } + + oldObject = SafeNativeMethods.SelectObject(hdc, pen); + + NativeStructs.POINT pt; + bool bResult = SafeNativeMethods.MoveToEx(hdc, points[0].X, points[0].Y, out pt); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("MoveToEx returned false"); + } + + for (int i = 1; i < points.Length; ++i) + { + bResult = SafeNativeMethods.LineTo(hdc, points[i].X, points[i].Y); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("LineTo returned false"); + } + } + } + + finally + { + if (oldObject != IntPtr.Zero) + { + SafeNativeMethods.SelectObject(hdc, oldObject); + oldObject = IntPtr.Zero; + } + + if (pen != IntPtr.Zero) + { + SafeNativeMethods.DeleteObject(pen); + pen = IntPtr.Zero; + } + + if (hdc != IntPtr.Zero) + { + g.ReleaseHdc(hdc); + hdc = IntPtr.Zero; + } + } + + GC.KeepAlive(g); + } + + /// + /// Draws several filled rectangles using the same color. If there is an error while trying to draw, + /// it is discarded and ignored. + /// + /// The Graphics context to draw to. + /// A list of rectangles to draw. + /// The color to fill the rectangles with. + /// + /// Note to implementors: This method is used to avoid drawing with GDI+, which avoids flickering + /// with our transparent toolforms. Implementations may thunk straight to g.FillRectangle(). + /// + public static void FillRectangles(Graphics g, Color color, Rectangle[] rects) + { + try + { + FillRectanglesImpl(g, color, rects); + } + + catch (Exception ex) + { + Tracing.Ping("Exception while executing PdnGraphics.FillRectangles: " + ex.ToString()); + } + } + + private static void FillRectanglesImpl(Graphics g, Color color, Rectangle[] rects) + { + uint nativeColor = (uint)(color.R + (color.G << 8) + (color.B << 16)); + + IntPtr hdc = IntPtr.Zero; + IntPtr brush = IntPtr.Zero; + IntPtr oldObject = IntPtr.Zero; + + try + { + hdc = g.GetHdc(); + brush = SafeNativeMethods.CreateSolidBrush(nativeColor); + + if (brush == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("CreateSolidBrush returned NULL"); + } + + oldObject = SafeNativeMethods.SelectObject(hdc, brush); + + foreach (Rectangle rect in rects) + { + NativeStructs.RECT nativeRect; + + nativeRect.left = rect.Left; + nativeRect.top = rect.Top; + nativeRect.right = rect.Right; + nativeRect.bottom = rect.Bottom; + + int result = SafeNativeMethods.FillRect(hdc, ref nativeRect, brush); + + if (result == 0) + { + NativeMethods.ThrowOnWin32Error("FillRect returned zero"); + } + } + } + + finally + { + if (oldObject != IntPtr.Zero) + { + SafeNativeMethods.SelectObject(hdc, oldObject); + oldObject = IntPtr.Zero; + } + + if (brush != IntPtr.Zero) + { + SafeNativeMethods.DeleteObject(brush); + brush = IntPtr.Zero; + } + + if (hdc != IntPtr.Zero) + { + g.ReleaseHdc(hdc); + hdc = IntPtr.Zero; + } + } + + GC.KeepAlive(g); + } + } +} + diff --git a/src/SystemLayer/Processor.cs b/src/SystemLayer/Processor.cs new file mode 100644 index 0000000..5b8e51f --- /dev/null +++ b/src/SystemLayer/Processor.cs @@ -0,0 +1,357 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Provides static methods and properties related to the CPU. + /// + public static class Processor + { + private static int logicalCpuCount; + private static string cpuName; + + static Processor() + { + logicalCpuCount = ConcreteLogicalCpuCount; + } + + private static ProcessorArchitecture Convert(ushort wProcessorArchitecture) + { + ProcessorArchitecture platform; + + switch (wProcessorArchitecture) + { + case NativeConstants.PROCESSOR_ARCHITECTURE_AMD64: + platform = ProcessorArchitecture.X64; + break; + + case NativeConstants.PROCESSOR_ARCHITECTURE_INTEL: + platform = ProcessorArchitecture.X86; + break; + + default: + case NativeConstants.PROCESSOR_ARCHITECTURE_UNKNOWN: + platform = ProcessorArchitecture.Unknown; + break; + } + + return platform; + } + + /// + /// Returns the processor architecture that the current process is using. + /// + /// + /// Note that if the current process is 32-bit, but the OS is 64-bit, this + /// property will still return X86 and not X64. + /// + public static ProcessorArchitecture Architecture + { + get + { + NativeStructs.SYSTEM_INFO sysInfo = new NativeStructs.SYSTEM_INFO(); + NativeMethods.GetSystemInfo(ref sysInfo); + ProcessorArchitecture architecture = Convert(sysInfo.wProcessorArchitecture); + return architecture; + } + } + + /// + /// Returns the processor architecture of the installed operating system. + /// + /// + /// Note that this may differ from the Architecture property if, for instance, + /// this is a 32-bit process on a 64-bit OS. + /// + public static ProcessorArchitecture NativeArchitecture + { + get + { + NativeStructs.SYSTEM_INFO sysInfo = new NativeStructs.SYSTEM_INFO(); + NativeMethods.GetNativeSystemInfo(ref sysInfo); + ProcessorArchitecture architecture = Convert(sysInfo.wProcessorArchitecture); + return architecture; + } + } + + private static string GetCpuName() + { + Guid processorClassGuid = new Guid("{50127DC3-0F36-415E-A6CC-4CB3BE910B65}"); + IntPtr hDiSet = IntPtr.Zero; + string cpuName = null; + + try + { + hDiSet = NativeMethods.SetupDiGetClassDevsW(ref processorClassGuid, null, IntPtr.Zero, NativeConstants.DIGCF_PRESENT); + + if (hDiSet == NativeConstants.INVALID_HANDLE_VALUE) + { + NativeMethods.ThrowOnWin32Error("SetupDiGetClassDevsW returned INVALID_HANDLE_VALUE"); + } + + bool bResult = false; + uint memberIndex = 0; + + while (true) + { + NativeStructs.SP_DEVINFO_DATA spDevinfoData = new NativeStructs.SP_DEVINFO_DATA(); + spDevinfoData.cbSize = (uint)Marshal.SizeOf(typeof(NativeStructs.SP_DEVINFO_DATA)); + + bResult = NativeMethods.SetupDiEnumDeviceInfo(hDiSet, memberIndex, ref spDevinfoData); + + if (!bResult) + { + int error = Marshal.GetLastWin32Error(); + + if (error == NativeConstants.ERROR_NO_MORE_ITEMS) + { + break; + } + else + { + throw new Win32Exception("SetupDiEnumDeviceInfo returned false, GetLastError() = " + error.ToString()); + } + } + + uint lengthReq = 0; + bResult = NativeMethods.SetupDiGetDeviceInstanceIdW(hDiSet, ref spDevinfoData, IntPtr.Zero, 0, out lengthReq); + + if (bResult) + { + NativeMethods.ThrowOnWin32Error("SetupDiGetDeviceInstanceIdW(1) returned true"); + } + + if (lengthReq == 0) + { + NativeMethods.ThrowOnWin32Error("SetupDiGetDeviceInstanceIdW(1) returned false, but also 0 for lengthReq"); + } + + IntPtr str = IntPtr.Zero; + string regPath = null; + + try + { + // Note: We cannot use Memory.Allocate() here because this property is + // usually retrieved during app shutdown, during which the heap may not + // be available. + str = Marshal.AllocHGlobal(checked((int)(sizeof(char) * (1 + lengthReq)))); + bResult = NativeMethods.SetupDiGetDeviceInstanceIdW(hDiSet, ref spDevinfoData, str, lengthReq, out lengthReq); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("SetupDiGetDeviceInstanceIdW(2) returned false"); + } + + regPath = Marshal.PtrToStringUni(str); + } + + finally + { + if (str != IntPtr.Zero) + { + Marshal.FreeHGlobal(str); + str = IntPtr.Zero; + } + } + + string keyName = @"SYSTEM\CurrentControlSet\Enum\" + regPath; + using (RegistryKey procKey = Registry.LocalMachine.OpenSubKey(keyName, false)) + { + const string friendlyName = "FriendlyName"; + + if (procKey != null) + { + object valueObj = procKey.GetValue(friendlyName); + string value = valueObj as string; + + if (value != null) + { + cpuName = value; + } + } + } + + if (cpuName != null) + { + break; + } + + ++memberIndex; + } + } + + finally + { + if (hDiSet != IntPtr.Zero) + { + NativeMethods.SetupDiDestroyDeviceInfoList(hDiSet); + hDiSet = IntPtr.Zero; + } + } + + return cpuName; + } + + /// + /// Returns the name of the CPU that is installed. If more than 1 CPU is installed, + /// then the name of the first one is retrieved. + /// + /// + /// This is the name that shows up in Windows Device Manager in the "Processors" node. + /// Note to implementors: This is only ever used for diagnostics (e.g., crash log). + /// + public static string CpuName + { + get + { + if (cpuName == null) + { + cpuName = GetCpuName(); + } + + return cpuName; + } + } + + /// + /// Gets the number of logical or "virtual" processors installed in the computer. + /// + /// + /// This value may not return the actual number of processors installed in the system. + /// It may be set to another number for testing and benchmarking purposes. It is + /// recommended that you use this property instead of ConcreteLogicalCpuCount for the + /// purposes of optimizing thread usage. + /// The maximum value for this property is 32 when running as a 32-bit process, or + /// 64 for a 64-bit process. Note that this implies the maximum is 32 for a 32-bit process + /// even when running on a 64-bit system. + /// + public static int LogicalCpuCount + { + get + { + return logicalCpuCount; + } + + set + { + if (value < 1 || value > (IntPtr.Size * 8)) + { + throw new ArgumentOutOfRangeException("value", value, "must be in the range [0, " + (IntPtr.Size * 8).ToString() + "]"); + } + + logicalCpuCount = value; + } + } + + /// + /// Gets the number of logical or "virtual" processors installed in the computer. + /// + /// + /// This property will always return the actual number of logical processors installed + /// in the system. Note that processors such as Intel Xeons and Pentium 4's with + /// HyperThreading will result in values that are twice the number of physical processor + /// packages that have been installed (i.e. 2 Xeons w/ HT => ConcreteLogicalCpuCount = 4). + /// + public static int ConcreteLogicalCpuCount + { + get + { + return Environment.ProcessorCount; + } + } + + /// + /// Gets the approximate speed of the processor, in megahurtz. + /// + /// + /// No accuracy is guaranteed, and precision is dependent on the operating system. + /// If there is an error determining the CPU speed, then 0 will be returned. + /// + public static int ApproximateSpeedMhz + { + get + { + const string keyName = @"HARDWARE\DESCRIPTION\System\CentralProcessor\0"; + const string valueName = @"~MHz"; + int mhz = 0; + + try + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName, false)) + { + if (key != null) + { + object value = key.GetValue(valueName); + mhz = (int)value; + } + } + } + + catch (Exception) + { + mhz = 0; + } + + return mhz; + } + } + + private static ProcessorFeature features = (ProcessorFeature)0; + + public static ProcessorFeature Features + { + get + { + if (features == (ProcessorFeature)0) + { + ProcessorFeature newFeatures = (ProcessorFeature)0; + + // DEP + if (SafeNativeMethods.IsProcessorFeaturePresent(NativeConstants.PF_NX_ENABLED)) + { + newFeatures |= ProcessorFeature.DEP; + } + + // SSE + if (SafeNativeMethods.IsProcessorFeaturePresent(NativeConstants.PF_XMMI_INSTRUCTIONS_AVAILABLE)) + { + newFeatures |= ProcessorFeature.SSE; + } + + // SSE2 + if (SafeNativeMethods.IsProcessorFeaturePresent(NativeConstants.PF_XMMI64_INSTRUCTIONS_AVAILABLE)) + { + newFeatures |= ProcessorFeature.SSE2; + } + + // SSE3 + if (SafeNativeMethods.IsProcessorFeaturePresent(NativeConstants.PF_SSE3_INSTRUCTIONS_AVAILABLE)) + { + newFeatures |= ProcessorFeature.SSE3; + } + + features = newFeatures; + } + + return features; + } + } + + public static bool IsFeaturePresent(ProcessorFeature feature) + { + return ((Features & feature) == feature); + } + } +} diff --git a/src/SystemLayer/ProcessorArchitecture.cs b/src/SystemLayer/ProcessorArchitecture.cs new file mode 100644 index 0000000..9aed6b5 --- /dev/null +++ b/src/SystemLayer/ProcessorArchitecture.cs @@ -0,0 +1,18 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +namespace PaintDotNet.SystemLayer +{ + public enum ProcessorArchitecture + { + X86, + X64, + Unknown + } +} diff --git a/src/SystemLayer/ProcessorFeature.cs b/src/SystemLayer/ProcessorFeature.cs new file mode 100644 index 0000000..e6b4e7d --- /dev/null +++ b/src/SystemLayer/ProcessorFeature.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + [Flags] + public enum ProcessorFeature + { + DEP = 1, + SSE = 2, + SSE2 = 4, + SSE3 = 8, + } +} diff --git a/src/SystemLayer/PropertyItem.png b/src/SystemLayer/PropertyItem.png new file mode 100644 index 0000000..e2473c6 Binary files /dev/null and b/src/SystemLayer/PropertyItem.png differ diff --git a/src/SystemLayer/PropertyItem2.cs b/src/SystemLayer/PropertyItem2.cs new file mode 100644 index 0000000..36ca91f --- /dev/null +++ b/src/SystemLayer/PropertyItem2.cs @@ -0,0 +1,189 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Re-implements System.Drawing.PropertyItem so that the data is serializable. + /// + [Serializable] + internal sealed class PropertyItem2 + { + private const string piElementName = "exif"; + private const string idPropertyName = "id"; + private const string lenPropertyName = "len"; + private const string typePropertyName = "type"; + private const string valuePropertyName = "value"; + + private int id; + private int len; + private short type; + private byte[] value; + + public int Id + { + get + { + return id; + } + } + + public int Len + { + get + { + return len; + } + } + + public short Type + { + get + { + return type; + } + } + + public byte[] Value + { + get + { + return (byte[])value.Clone(); + } + } + + public PropertyItem2(int id, int len, short type, byte[] value) + { + this.id = id; + this.len = len; + this.type = type; + + if (value == null) + { + this.value = new byte[0]; + } + else + { + this.value = (byte[])value.Clone(); + } + + if (len != this.value.Length) + { + Tracing.Ping("len != value.Length: id=" + id + ", type=" + type); + } + } + + public string ToBlob() + { + string blob = string.Format("<{0} {1}=\"{2}\" {3}=\"{4}\" {5}=\"{6}\" {7}=\"{8}\" />", + piElementName, + idPropertyName, this.id.ToString(CultureInfo.InvariantCulture), + lenPropertyName, this.len.ToString(CultureInfo.InvariantCulture), + typePropertyName, this.type.ToString(CultureInfo.InvariantCulture), + valuePropertyName, Convert.ToBase64String(this.value)); + + return blob; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + public PropertyItem ToPropertyItem() + { + PropertyItem pi = GetPropertyItem(); + + pi.Id = this.Id; + pi.Len = this.Len; + pi.Type = this.Type; + pi.Value = this.Value; + + return pi; + } + + public static PropertyItem2 FromPropertyItem(PropertyItem pi) + { + return new PropertyItem2(pi.Id, pi.Len, pi.Type, pi.Value); + } + + private static string GetProperty(string blob, string propertyName) + { + string findMe = propertyName + "=\""; + int startIndex = blob.IndexOf(findMe) + findMe.Length; + int endIndex = blob.IndexOf("\"", startIndex); + string propertyValue = blob.Substring(startIndex, endIndex - startIndex); + return propertyValue; + } + + public static PropertyItem2 FromBlob(string blob) + { + PropertyItem2 pi2; + + if (blob.Length > 0 && blob[0] == '<') + { + string idStr = GetProperty(blob, idPropertyName); + string lenStr = GetProperty(blob, lenPropertyName); + string typeStr = GetProperty(blob, typePropertyName); + string valueStr = GetProperty(blob, valuePropertyName); + + int id = int.Parse(idStr, CultureInfo.InvariantCulture); + int len = int.Parse(lenStr, CultureInfo.InvariantCulture); + short type = short.Parse(typeStr, CultureInfo.InvariantCulture); + byte[] value = Convert.FromBase64String(valueStr); + + pi2 = new PropertyItem2(id, len, type, value); + } + else + { + // Old way of serializing: .NET serialized! + byte[] bytes = Convert.FromBase64String(blob); + MemoryStream ms = new MemoryStream(bytes); + BinaryFormatter bf = new BinaryFormatter(); + SerializationFallbackBinder sfb = new SerializationFallbackBinder(); + sfb.AddAssembly(Assembly.GetExecutingAssembly()); + bf.Binder = sfb; + pi2 = (PropertyItem2)bf.Deserialize(ms); + } + + return pi2; + } + + // System.Drawing.Imaging.PropertyItem does not have a public constructor + // So, as per the documentation, we have to "steal" one. + // Quite ridiculous. + // This depends on PropertyItem.png being an embedded resource in this assembly. + private static Image propertyItemImage; + + [MethodImpl(MethodImplOptions.Synchronized)] + private static PropertyItem GetPropertyItem() + { + if (propertyItemImage == null) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("PaintDotNet.SystemLayer.PropertyItem.png"); + propertyItemImage = Image.FromStream(stream); + } + + PropertyItem pi = propertyItemImage.PropertyItems[0]; + pi.Id = 0; + pi.Len = 0; + pi.Type = 0; + pi.Value = new byte[0]; + + return pi; + } + } +} diff --git a/src/SystemLayer/RealParentWndProcDelegate.cs b/src/SystemLayer/RealParentWndProcDelegate.cs new file mode 100644 index 0000000..ed0bb55 --- /dev/null +++ b/src/SystemLayer/RealParentWndProcDelegate.cs @@ -0,0 +1,16 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public delegate void RealParentWndProcDelegate(ref Message m); +} diff --git a/src/SystemLayer/SafeNativeMethods.cs b/src/SystemLayer/SafeNativeMethods.cs new file mode 100644 index 0000000..08d32da --- /dev/null +++ b/src/SystemLayer/SafeNativeMethods.cs @@ -0,0 +1,412 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32.SafeHandles; +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Security; + +namespace PaintDotNet.SystemLayer +{ + [SuppressUnmanagedCodeSecurity] + internal static class SafeNativeMethods + { + [DllImport("kernel32.dll", SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool IsProcessorFeaturePresent(uint ProcessorFeature); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DrawMenuBar(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = false)] + internal static extern IntPtr GetSystemMenu( + IntPtr hWnd, + [MarshalAs(UnmanagedType.Bool)] bool bRevert); + + [DllImport("user32.dll", SetLastError = false)] + internal static extern int EnableMenuItem( + IntPtr hMenu, + uint uIDEnableItem, + uint uEnable); + + [DllImport("user32.dll", SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool FlashWindow( + IntPtr hWnd, + [MarshalAs(UnmanagedType.Bool)] bool bInvert); + + [DllImport("dwmapi.dll")] + internal unsafe static extern int DwmGetWindowAttribute( + IntPtr hwnd, + uint dwAttribute, + void* pvAttribute, + uint cbAttribute); + + [DllImport("kernel32.dll", SetLastError = false)] + internal static extern IntPtr GetCurrentThread(); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetThreadPriority( + IntPtr hThread, + int nPriority); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr CreateFileMappingW( + IntPtr hFile, + IntPtr lpFileMappingAttributes, + uint flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + [MarshalAs(UnmanagedType.LPTStr)] string lpName); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr MapViewOfFile( + IntPtr hFileMappingObject, + uint dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + UIntPtr dwNumberOfBytesToMap); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ShowScrollBar( + IntPtr hWnd, + int wBar, + [MarshalAs(UnmanagedType.Bool)] bool bShow); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GetVersionEx(ref NativeStructs.OSVERSIONINFOEX lpVersionInfo); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GetLayeredWindowAttributes( + IntPtr hwnd, + out uint pcrKey, + out byte pbAlpha, + out uint pdwFlags); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "2")] + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetLayeredWindowAttributes( + IntPtr hwnd, + uint crKey, + byte bAlpha, + uint dwFlags); + + [DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr CreateFontW( + int nHeight, + int nWidth, + int nEscapement, + int nOrientation, + int fnWeight, + uint fdwItalic, + uint fdwUnderline, + uint fdwStrikeOut, + uint fdwCharSet, + uint fdwOutputPrecision, + uint fdwClipPrecision, + uint fdwQuality, + uint fdwPitchAndFamily, + string lpszFace); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int DrawTextW( + IntPtr hdc, + string lpString, + int nCount, + ref NativeStructs.RECT lpRect, + uint uFormat); + + [DllImport("gdi32.dll", SetLastError = true)] + internal static extern IntPtr CreateDIBSection( + IntPtr hdc, + ref NativeStructs.BITMAPINFO pbmi, + uint iUsage, + out IntPtr ppvBits, + IntPtr hSection, + uint dwOffset); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr CreateFileW( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal unsafe static extern bool WriteFile( + IntPtr hFile, + void *lpBuffer, + uint nNumberOfBytesToWrite, + out uint lpNumberOfBytesWritten, + IntPtr lpOverlapped); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal unsafe static extern bool ReadFile( + SafeFileHandle sfhFile, + void *lpBuffer, + uint nNumberOfBytesToRead, + out uint lpNumberOfBytesRead, + IntPtr lpOverlapped); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetHandleInformation( + IntPtr hObject, + uint dwMask, + uint dwFlags); + + [DllImport("user32.dll", SetLastError = false)] + internal static extern int GetUpdateRgn( + IntPtr hWnd, + IntPtr hRgn, + [MarshalAs(UnmanagedType.Bool)] bool bErase); + + [DllImport("user32.dll", SetLastError = false)] + internal static extern uint GetWindowThreadProcessId( + IntPtr hWnd, + out uint lpdwProcessId); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr FindWindowW( + [MarshalAs(UnmanagedType.LPWStr)] string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] string lpWindowName); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr FindWindowExW( + IntPtr hwndParent, + IntPtr hwndChildAfter, + [MarshalAs(UnmanagedType.LPWStr)] string lpszClass, + [MarshalAs(UnmanagedType.LPWStr)] string lpszWindow); + + [DllImport("user32.dll", SetLastError = false)] + internal static extern IntPtr SendMessageW( + IntPtr hWnd, + uint msg, + IntPtr wParam, + IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal extern static bool PostMessageW( + IntPtr handle, + uint msg, + IntPtr wParam, + IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern uint GetWindowLongW( + IntPtr hWnd, + int nIndex); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern uint SetWindowLongW( + IntPtr hWnd, + int nIndex, + uint dwNewLong); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool QueryPerformanceCounter(out ulong lpPerformanceCount); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool QueryPerformanceFrequency(out ulong lpFrequency); + + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + internal static extern unsafe void memcpy(void* dst, void* src, UIntPtr length); + + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + internal static extern unsafe void memset(void* dst, int c, UIntPtr length); + + [DllImport("User32.dll", SetLastError = false)] + internal static extern int GetSystemMetrics(int nIndex); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern uint WaitForSingleObject( + IntPtr hHandle, + uint dwMilliseconds); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern uint WaitForMultipleObjects( + uint nCount, + IntPtr[] lpHandles, + [MarshalAs(UnmanagedType.Bool)] bool bWaitAll, + uint dwMilliseconds); + + internal static uint WaitForMultipleObjects(IntPtr[] lpHandles, bool bWaitAll, uint dwMilliseconds) + { + return WaitForMultipleObjects((uint)lpHandles.Length, lpHandles, bWaitAll, dwMilliseconds); + } + + [DllImport("wtsapi32.dll", SetLastError = true)] + internal static extern uint WTSRegisterSessionNotification(IntPtr hWnd, uint dwFlags); + + [DllImport("wtsapi32.dll", SetLastError = true)] + internal static extern uint WTSUnRegisterSessionNotification(IntPtr hWnd); + + [DllImport("Gdi32.dll", SetLastError = true)] + internal unsafe static extern uint GetRegionData( + IntPtr hRgn, + uint dwCount, + NativeStructs.RGNDATA *lpRgnData); + + [DllImport("Gdi32.dll", SetLastError = true)] + internal unsafe static extern IntPtr CreateRectRgn( + int nLeftRect, + int nTopRect, + int nRightRect, + int nBottomRect); + + [DllImport("Gdi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal extern static bool MoveToEx( + IntPtr hdc, + int X, + int Y, + out NativeStructs.POINT lpPoint); + + [DllImport("Gdi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal extern static bool LineTo( + IntPtr hdc, + int nXEnd, + int nYEnd); + + [DllImport("User32.dll", SetLastError = true)] + internal extern static int FillRect( + IntPtr hDC, + ref NativeStructs.RECT lprc, + IntPtr hbr); + + [DllImport("Gdi32.dll", SetLastError = true)] + internal extern static IntPtr CreatePen( + int fnPenStyle, + int nWidth, + uint crColor); + + [DllImport("Gdi32.dll", SetLastError = true)] + internal extern static IntPtr CreateSolidBrush(uint crColor); + + [DllImport("Gdi32.dll", SetLastError = false)] + internal extern static IntPtr SelectObject( + IntPtr hdc, + IntPtr hgdiobj); + + [DllImport("Gdi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal extern static bool DeleteObject(IntPtr hObject); + + [DllImport("Gdi32.dll", SetLastError = true)] + internal extern static uint DeleteDC(IntPtr hdc); + + [DllImport("Gdi32.Dll", SetLastError = true)] + internal extern static IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("Gdi32.Dll", SetLastError = true)] + internal extern static uint BitBlt( + IntPtr hdcDest, + int nXDest, + int nYDest, + int nWidth, + int nHeight, + IntPtr hdcSrc, + int nXSrc, + int nYSrc, + uint dwRop); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + UIntPtr dwSize, + uint flAllocationType, + uint flProtect); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool VirtualFree( + IntPtr lpAddress, + UIntPtr dwSize, + uint dwFreeType); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool VirtualProtect( + IntPtr lpAddress, + UIntPtr dwSize, + uint flNewProtect, + out uint lpflOldProtect); + + [DllImport("Kernel32.dll", SetLastError = false)] + internal static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, UIntPtr dwBytes); + + [DllImport("Kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool HeapFree(IntPtr hHeap, uint dwFlags, IntPtr lpMem); + + [DllImport("Kernel32.dll", SetLastError = false)] + internal static extern UIntPtr HeapSize(IntPtr hHeap, uint dwFlags, IntPtr lpMem); + + [DllImport("Kernel32.dll", SetLastError = true)] + internal static extern IntPtr HeapCreate( + uint flOptions, + [MarshalAs(UnmanagedType.SysUInt)] IntPtr dwInitialSize, + [MarshalAs(UnmanagedType.SysUInt)] IntPtr dwMaximumSize + ); + + [DllImport("Kernel32.dll", SetLastError = true)] + internal static extern uint HeapDestroy(IntPtr hHeap); + + [DllImport("Kernel32.Dll", SetLastError = true)] + internal unsafe static extern uint HeapSetInformation( + IntPtr HeapHandle, + int HeapInformationClass, + void *HeapInformation, + uint HeapInformationLength + ); + + [DllImport("winhttp.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool WinHttpGetIEProxyConfigForCurrentUser(ref NativeStructs.WINHTTP_CURRENT_USER_IE_PROXY_CONFIG pProxyConfig); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern IntPtr GlobalFree(IntPtr hMem); + + [DllImport("user32.dll", SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = false)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + } +} diff --git a/src/SystemLayer/ScanResult.cs b/src/SystemLayer/ScanResult.cs new file mode 100644 index 0000000..bd1c361 --- /dev/null +++ b/src/SystemLayer/ScanResult.cs @@ -0,0 +1,34 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Defines the possible results when scanning. + /// + public enum ScanResult + { + /// + /// The operation completed successfully. + /// + Success = 1, + + /// + /// The user cancelled the operation. + /// + UserCancelled = 2, + + /// + /// The device was busy or otherwise inaccessible. + /// + DeviceBusy = 3 + } +} diff --git a/src/SystemLayer/ScanningAndPrinting.cs b/src/SystemLayer/ScanningAndPrinting.cs new file mode 100644 index 0000000..9aa4d55 --- /dev/null +++ b/src/SystemLayer/ScanningAndPrinting.cs @@ -0,0 +1,104 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Provides methods and properties related to scanning and printing. + /// + public static class ScanningAndPrinting + { + private static IWiaInterface wiaInterface = null; + + private static IWiaInterface WiaInterface + { + get + { + if (wiaInterface == null) + { + // Windows Server 2003 x64 and Windows XP x64 do not have a 64-bit version of wiaaut.dll available + if (Environment.OSVersion.Version.Major == 5 && + Environment.OSVersion.Version.Minor == 2 && + Processor.Architecture == ProcessorArchitecture.X64) + { + wiaInterface = new WiaInterfaceOutOfProc(); + } + else + { + wiaInterface = new WiaInterfaceInProc(); + } + } + + return wiaInterface; + } + } + + public static bool IsComponentAvailable + { + get + { + return WiaInterface.IsComponentAvailable; + } + } + + public static bool CanPrint + { + get + { + return WiaInterface.CanPrint; + } + } + + public static bool CanScan + { + get + { + return WiaInterface.CanScan; + } + } + + public static void Print(Control owner, string fileName) + { + if (fileName == null) + { + throw new ArgumentNullException("fileName"); + } + + if (!CanPrint) + { + throw new InvalidOperationException("Printing is not available"); + } + + string fileNameExt = Path.GetExtension(fileName); + if (string.Compare(fileNameExt, ".bmp", true) != 0) + { + throw new ArgumentException("fileName must have a .bmp extension"); + } + + WiaInterface.Print(owner, fileName); + } + + public static ScanResult Scan(Control owner, string fileName) + { + if (!CanScan) + { + throw new InvalidOperationException("Scanning is not available"); + } + + ScanResult result = WiaInterface.Scan(owner, fileName); + return result; + } + } +} diff --git a/src/SystemLayer/ScrollPanel.cs b/src/SystemLayer/ScrollPanel.cs new file mode 100644 index 0000000..acd8d1a --- /dev/null +++ b/src/SystemLayer/ScrollPanel.cs @@ -0,0 +1,83 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + // TODO: remove? <-- depends on rewriting LayerControl and DocumentView so that it doesn't use a Panel, and that is post-3.0 stuff + /// + /// This is the same as System.Windows.Forms.Panel except for three things: + /// 1. It exposes a Scroll event. + /// 2. It allows you to disable SetFocus. + /// 3. It has a much simplified interface for AutoScrollPosition, exposed via the ScrollPosition property. + /// + public class ScrollPanel + : Panel + { + private bool ignoreSetFocus = false; + + /// + /// Gets or sets whether the control ignores WM_SETFOCUS. + /// + public bool IgnoreSetFocus + { + get + { + return ignoreSetFocus; + } + + set + { + ignoreSetFocus = value; + } + } + + /// + /// Gets or sets the scrollbar position. + /// + [Browsable(false)] + public Point ScrollPosition + { + get + { + return new Point(-AutoScrollPosition.X, -AutoScrollPosition.Y); + } + + set + { + AutoScrollPosition = value; + } + } + + protected override void WndProc(ref Message m) + { + switch (m.Msg) + { + case NativeConstants.WM_SETFOCUS: + if (IgnoreSetFocus) + { + return; + } + else + { + goto default; + } + + default: + base.WndProc(ref m); + break; + } + } + } +} diff --git a/src/SystemLayer/Security.cs b/src/SystemLayer/Security.cs new file mode 100644 index 0000000..d6d0213 --- /dev/null +++ b/src/SystemLayer/Security.cs @@ -0,0 +1,208 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using System; +using System.Security.Principal; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Security related static methods and properties. + /// + public static class Security + { + private static bool isAdmin = GetIsAdministrator(); + + private static bool GetIsAdministrator() + { + AppDomain domain = Thread.GetDomain(); + domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); + WindowsPrincipal principal = (WindowsPrincipal)Thread.CurrentPrincipal; + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + /// + /// Gets a flag indicating whether the user has administrator-level privileges. + /// + /// + /// This is used to control access to actions that require the user to be an administrator. + /// An example is checking for and installing updates, actions which are not normally able + /// to be performed by normal or "limited" users. A user must also be an administrator in + /// order to write to any Settings.SystemWide entries. + /// + public static bool IsAdministrator + { + get + { + return isAdmin; + } + } + + /// + /// Gets a flag indicating whether the current user is able to elevate to obtain + /// administrator-level privileges. + /// + /// + /// This flag has no meaning if IsAdministrator returns true. + /// This flag indicates whether a new process may be spawned which has administrator + /// privilege. It does not indicate the ability to elevate the current process to + /// administrator privilege. For Windows this indicates that the user is running + /// Vista and has UAC enabled. This property should be used instead of checking + /// the OS version anytime this check must be performed. + /// Note to implementors: This may be written to simply return false. + /// + public static bool CanElevateToAdministrator + { + get + { + if (OS.IsVistaOrLater && !Security.IsAdministrator) + { + return IsUacEnabled; + } + else + { + return false; + } + } + } + + private static bool IsUacEnabled + { + get + { + bool returnVal = false; + const string keyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"; + const string valueName = "EnableLUA"; + + try + { + if (Environment.OSVersion.Version >= OS.WindowsVista) + { + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName, false)) + { + if (key != null) + { + RegistryValueKind valueKind = key.GetValueKind(valueName); + + if (valueKind == RegistryValueKind.DWord) + { + int value = unchecked((int)key.GetValue(valueName)); + returnVal = (value == 1); + } + } + } + } + } + + catch (Exception ex) + { + Tracing.Ping(ex.ToString()); + returnVal = false; + } + + return returnVal; } + } + + /// + /// If IsAdministrator is true, this returns true if we can launch a process with limited privilege. + /// + /// + /// Here's the truth table for this: + /// Windows XP + Admin User -> false + /// Windows XP + Standard User -> true + /// Windows Vista + Admin User + UAC Enabled -> true + /// Windows Vista + Admin User + UAC Disabled -> false + /// Windows Vista + Standard User -> true + /// + public static bool CanLaunchNonAdminProcess + { + get + { + if (!Security.IsAdministrator) + { + return true; + } + else if (OS.IsVistaOrLater) + { + return Security.IsUacEnabled; + } + else + { + return false; + } + } + } + + /// + /// Verifies that a file has a valid digital signature. + /// + /// The parent/owner window for any UI that may be shown. + /// The path to the file to be validate. + /// Whether or not to show a UI in the case that the signature can not be found or validated. + /// Whether or not to show a UI in the case that the signature is successfully found and validated. + /// true if the file has a digital signature that validates up to a trusted root, or false otherwise + public static bool VerifySignedFile(IWin32Window owner, string fileName, bool showNegativeUI, bool showPositiveUI) + { + unsafe + { + fixed (char *szFileName = fileName) + { + Guid pgActionID = NativeConstants.WINTRUST_ACTION_GENERIC_VERIFY_V2; + + NativeStructs.WINTRUST_FILE_INFO fileInfo = new NativeStructs.WINTRUST_FILE_INFO(); + fileInfo.cbStruct = (uint)sizeof(NativeStructs.WINTRUST_FILE_INFO); + fileInfo.pcwszFilePath = szFileName; + + NativeStructs.WINTRUST_DATA wintrustData = new NativeStructs.WINTRUST_DATA(); + wintrustData.cbStruct = (uint)sizeof(NativeStructs.WINTRUST_DATA); + + if (!showNegativeUI && !showPositiveUI) + { + wintrustData.dwUIChoice = NativeConstants.WTD_UI_NONE; + } + else if (!showNegativeUI && showPositiveUI) + { + wintrustData.dwUIChoice = NativeConstants.WTD_UI_NOBAD; + } + else if (showNegativeUI && !showPositiveUI) + { + wintrustData.dwUIChoice = NativeConstants.WTD_UI_NOGOOD; + } + else // if (showNegativeUI && showPositiveUI) + { + wintrustData.dwUIChoice = NativeConstants.WTD_UI_ALL; + } + + wintrustData.fdwRevocationChecks = NativeConstants.WTD_REVOKE_WHOLECHAIN; + wintrustData.dwUnionChoice = NativeConstants.WTD_CHOICE_FILE; + wintrustData.pInfo = (void *)&fileInfo; + + IntPtr handle; + + if (owner == null) + { + handle = IntPtr.Zero; + } + else + { + handle = owner.Handle; + } + + int result = NativeMethods.WinVerifyTrust(handle, ref pgActionID, ref wintrustData); + + GC.KeepAlive(owner); + return result >= 0; + } + } + } + } +} diff --git a/src/SystemLayer/SerializationFallbackBinder.cs b/src/SystemLayer/SerializationFallbackBinder.cs new file mode 100644 index 0000000..d1068fc --- /dev/null +++ b/src/SystemLayer/SerializationFallbackBinder.cs @@ -0,0 +1,82 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; + +namespace PaintDotNet.SystemLayer +{ + /// + /// This is an implementation of SerializationBinder that tries to find a match + /// for a type even if a direct match doesn't exist. This gets around versioning + /// mismatches, and allows you to move data types between assemblies. + /// + /// + /// This class is in SystemLayer because there is code in this assembly that must + /// make use of it. This class does not otherwise need to be here, and can be + /// ignored by implementors. + /// + public sealed class SerializationFallbackBinder + : SerializationBinder + { + private List assemblies; + + public SerializationFallbackBinder() + { + this.assemblies = new List(); + } + + public void AddAssembly(Assembly assembly) + { + this.assemblies.Add(assembly); + } + + private Type TryBindToType(Assembly assembly, string typeName) + { + Type type = assembly.GetType(typeName, false, true); + return type; + } + + public override Type BindToType(string assemblyName, string typeName) + { + Type type = null; + + foreach (Assembly tryAssembly in this.assemblies) + { + type = TryBindToType(tryAssembly, typeName); + + if (type != null) + { + break; + } + } + + if (type == null) + { + string fullTypeName = typeName + ", " + assemblyName; + + try + { + type = System.Type.GetType(fullTypeName, false, true); + } + + catch (FileLoadException) + { + type = null; + } + } + + return type; + } + } +} diff --git a/src/SystemLayer/Settings.cs b/src/SystemLayer/Settings.cs new file mode 100644 index 0000000..ea4fe22 --- /dev/null +++ b/src/SystemLayer/Settings.cs @@ -0,0 +1,380 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Security; +using System.Security.AccessControl; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Stores non-volatile name/value settings. These persist between sessions of the application. + /// + /// + /// On Windows, this class uses the registry. + /// + public sealed class Settings + : ISimpleCollection + { + private const string hkcuKey = @"SOFTWARE\Paint.NET"; + + public static readonly Settings SystemWide = new Settings(Microsoft.Win32.Registry.LocalMachine); + public static readonly Settings CurrentUser = new Settings(Microsoft.Win32.Registry.CurrentUser); + + private const string pointXSuffix = ".X"; + private const string pointYSuffix = ".Y"; + + private RegistryKey rootKey; + + private Settings(RegistryKey rootKey) + { + this.rootKey = rootKey; + } + + private RegistryKey CreateSettingsKey(bool writable) + { + RegistryKey softwareKey = null; + + try + { + softwareKey = this.rootKey.OpenSubKey(hkcuKey, writable); + } + + catch (Exception) + { + softwareKey = null; + } + + if (softwareKey == null) + { + try + { + softwareKey = rootKey.CreateSubKey(hkcuKey); + } + + catch (Exception) + { + throw; + } + } + + return softwareKey; + } + + public bool TryDelete(string key) + { + try + { + Delete(key); + return true; + } + + catch (Exception) + { + return false; + } + } + + /// + /// Deletes a settings key. + /// + /// The key to delete. + public void Delete(string key) + { + using (RegistryKey pdnKey = CreateSettingsKey(true)) + { + pdnKey.DeleteValue(key, false); + } + } + + /// + /// Deletes several settings keys. + /// + /// The keys to delete. + public void Delete(string[] keys) + { + using (RegistryKey pdnKey = CreateSettingsKey(true)) + { + foreach (string key in keys) + { + pdnKey.DeleteValue(key, false); + } + } + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + public object GetObject(string key) + { + using (RegistryKey pdnKey = CreateSettingsKey(false)) + { + return pdnKey.GetValue(key); + } + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The default value to use if the key doesn't exist. + /// The value of the key, or defaultValue if it didn't exist. + public object GetObject(string key, object defaultValue) + { + try + { + using (RegistryKey pdnKey = CreateSettingsKey(false)) + { + return pdnKey.GetValue(key, defaultValue); + } + } + + catch (Exception) + { + return defaultValue; + } + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + public void SetObject(string key, object value) + { + using (RegistryKey pdnKey = CreateSettingsKey(true)) + { + pdnKey.SetValue(key, value); + } + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + public string GetString(string key) + { + return (string)GetObject(key); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The default value to use if the key doesn't exist. + /// The value of the key, or defaultValue if it didn't exist. + public string GetString(string key, string defaultValue) + { + return (string)GetObject(key, defaultValue); + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + public void SetString(string key, string value) + { + SetObject(key, value); + } + + /// + /// Saves the given strings. + /// + public void SetStrings(NameValueCollection nvc) + { + foreach (string key in nvc.Keys) + { + string value = nvc[key]; + SetString("Test\\" + key, value); + } + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + public bool GetBoolean(string key) + { + return bool.Parse(GetString(key)); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The default value to use if the key doesn't exist. + /// The value of the key, or defaultValue if it didn't exist. + public bool GetBoolean(string key, bool defaultValue) + { + return bool.Parse(GetString(key, defaultValue.ToString())); + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + public void SetBoolean(string key, bool value) + { + SetString(key, value.ToString()); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + public Point GetPoint(string key) + { + int x = GetInt32(key + pointXSuffix); + int y = GetInt32(key + pointYSuffix); + return new Point(x, y); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The default value to use if the key doesn't exist. + /// The value of the key, or defaultValue if it didn't exist. + public Point GetPoint(string key, Point defaultValue) + { + int x = GetInt32(key + pointXSuffix, defaultValue.X); + int y = GetInt32(key + pointYSuffix, defaultValue.Y); + return new Point(x, y); + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + public void SetPoint(string key, Point value) + { + SetInt32(key + pointXSuffix, value.X); + SetInt32(key + pointYSuffix, value.Y); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + public Int32 GetInt32(string key) + { + return Int32.Parse(GetString(key)); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The default value to use if the key doesn't exist. + /// The value of the key, or defaultValue if it didn't exist. + public Int32 GetInt32(string key, Int32 defaultValue) + { + return Int32.Parse(GetString(key, defaultValue.ToString())); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The default value to use if the key doesn't exist. + /// The value of the key, or defaultValue if it didn't exist. + public float GetSingle(string key, float defaultValue) + { + return Single.Parse(GetString(key, defaultValue.ToString())); + } + + /// + /// Retrieves the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + public float GetSingle(string key) + { + return Single.Parse(GetString(key)); + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + public void SetInt32(string key, int value) + { + SetString(key, value.ToString()); + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + public void SetSingle(string key, float value) + { + SetString(key, value.ToString()); + } + + /// + /// Gets the value of a settings key. + /// + /// The name of the key to retrieve. + /// The value of the key. + /// This method treats the key value as a stream of base64 encoded bytes that represent a PNG image. + public Image GetImage(string key) + { + string imageB64 = GetString(key); + byte[] pngBytes = Convert.FromBase64String(imageB64); + MemoryStream ms = new MemoryStream(pngBytes); + Image image = Image.FromStream(ms); + ms.Close(); + return image; + } + + /// + /// Sets the value of a settings key. + /// + /// The name of the key to set. + /// The new value of the key. + /// This method saves the key value as a stream of base64 encoded bytes that represent a PNG image. + public void SetImage(string key, Image value) + { + MemoryStream ms = new MemoryStream(); + value.Save(ms, ImageFormat.Png); + byte[] buffer = ms.GetBuffer(); + string base64 = Convert.ToBase64String(buffer); + SetString(key, base64); + ms.Close(); + } + + public string Get(string key) + { + return GetString(key); + } + + public void Set(string key, string value) + { + SetString(key, value); + } + } +} diff --git a/src/SystemLayer/Shell.cs b/src/SystemLayer/Shell.cs new file mode 100644 index 0000000..b4fa88f --- /dev/null +++ b/src/SystemLayer/Shell.cs @@ -0,0 +1,840 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + public static class Shell + { + /// + /// Repairs the installation of Paint.NET by replacing any files that have gone missing. + /// This method should only be called after it has been determined that the files are missing, + /// and not as a way to determine which files are missing. + /// This is used, for instance, if the resource files, such as PaintDotNet.Strings.3.resources, + /// cannot be found. This is actually a top support issue, and by automatically repairing + /// this problem we save a lot of people a lot of trouble. + /// + /// + /// Friendly names for the files that are missing. These will not be used as part of the + /// repair process but rather as part of any UI presented to the user, or in an exception that + /// will be thrown in the case of an error. + /// + /// + /// true if everything was successful, false if the user cancelled or does not have administrator + /// privilege (and cannot elevate). An exception is thrown for errors. + /// + /// + /// Note to implementors: This may be implemented as a no-op. Just return true in this case. + /// + public static bool ReplaceMissingFiles(string[] missingFiles) + { + // Generate a friendly, comma separated list of the missing file names + StringBuilder missingFilesSB = new StringBuilder(); + + for (int i = 0; i < missingFiles.Length; ++i) + { + missingFilesSB.Append(missingFiles[i]); + + if (i != missingFiles.Length - 1) + { + missingFilesSB.Append(", "); + } + } + + try + { + // If they are not an admin and have no possibility of elevating, such as for a standard User + // in XP, then give them an error. Unfortunately we do not know if we can even load text + // resources at this point, and so must provide an English-only error message. + if (!Security.IsAdministrator && !Security.CanElevateToAdministrator) + { + MessageBox.Show( + null, + "Paint.NET has detected that some important installation files are missing. Repairing " + + "this requires administrator privilege. Please run the 'PdnRepair.exe' program in the installation " + + "directory after logging in with a user that has administrator privilege." + Environment.NewLine + + Environment.NewLine + + "The missing files are: " + missingFilesSB.ToString(), + "Paint.NET", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return false; + } + + const int hMargin = 8; + const int vMargin = 8; + Form form = new Form(); + form.Text = "Paint.NET"; + form.ClientSize = new Size(400, 10); + form.StartPosition = FormStartPosition.CenterScreen; + + Label infoLabel = new Label(); + form.Controls.Add(infoLabel); + infoLabel.Text = + "Paint.NET has detected that some important installation files are missing. If you click " + + "the Repair button it will attempt to repair this and then continue loading." + Environment.NewLine + + Environment.NewLine + + "The missing files are: " + missingFilesSB.ToString(); + +#if DEBUG + infoLabel.Text += Environment.NewLine + Environment.NewLine + + "*** Since this is a DEBUG build, you should probably add /skipRepairAttempt to the command-line."; +#endif + + infoLabel.Location = new Point(hMargin, vMargin); + infoLabel.Width = form.ClientSize.Width - hMargin * 2; + infoLabel.Height = infoLabel.GetPreferredSize(new Size(infoLabel.Width, 1)).Height; + + Button repairButton = new Button(); + form.Controls.Add(repairButton); + repairButton.Text = "&Repair"; + + Exception exception = null; + + repairButton.Click += + delegate(object sender, EventArgs e) + { + form.DialogResult = DialogResult.Yes; + repairButton.Enabled = false; + + try + { + Shell.Execute(form, "PdnRepair.exe", "/noPause", ExecutePrivilege.AsInvokerOrAsManifest, ExecuteWaitType.WaitForExit); + } + + catch (Exception ex) + { + exception = ex; + } + }; + + repairButton.AutoSize = true; + repairButton.PerformLayout(); + repairButton.Width += 20; + repairButton.Location = new Point((form.ClientSize.Width - repairButton.Width) / 2, infoLabel.Bottom + vMargin * 2); + repairButton.FlatStyle = FlatStyle.System; + UI.EnableShield(repairButton, true); + + form.FormBorderStyle = FormBorderStyle.FixedDialog; + form.MinimizeBox = false; + form.MaximizeBox = false; + form.ShowInTaskbar = true; + form.Icon = null; + form.ClientSize = new Size(form.ClientRectangle.Width, repairButton.Bottom + vMargin); + + DialogResult result = form.ShowDialog(null); + form.Dispose(); + form = null; + + if (result == DialogResult.Yes) + { + return true; + } + else if (exception == null) + { + return false; + } + else + { + throw new Exception("Error while attempting to repair", exception); + } + } + + catch (Exception ex) + { + throw new Exception("Could not repair installation after it was determined that the following files are missing: " + + missingFilesSB.ToString(), ex); + } + } + + /// + /// Opens the requested directory in the shell's file/folder browser. + /// + /// The window that is currently in the foreground. + /// The folder to open. + /// + /// This UI is presented modelessly, in another process, and in the foreground. + /// Error handling and messaging (error dialogs) will be handled by the shell, + /// and these errors will not be communicated to the caller of this method. + /// + public static void BrowseFolder(IWin32Window parent, string folderPath) + { + NativeStructs.SHELLEXECUTEINFO sei = new NativeStructs.SHELLEXECUTEINFO(); + + sei.cbSize = (uint)Marshal.SizeOf(typeof(NativeStructs.SHELLEXECUTEINFO)); + sei.fMask = NativeConstants.SEE_MASK_NO_CONSOLE; + sei.lpVerb = "open"; + sei.lpFile = folderPath; + sei.nShow = NativeConstants.SW_SHOWNORMAL; + sei.hwnd = parent.Handle; + + bool bResult = NativeMethods.ShellExecuteExW(ref sei); + + if (bResult) + { + if (sei.hProcess != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(sei.hProcess); + sei.hProcess = IntPtr.Zero; + } + } + else + { + NativeMethods.ThrowOnWin32Error("ShellExecuteW returned FALSE"); + } + + GC.KeepAlive(parent); + } + +#if false + [Obsolete("Do not use this method.", true)] + public static void Execute( + IWin32Window parent, + string exePath, + string args, + bool requireAdmin) + { + Execute(parent, exePath, args, requireAdmin ? ExecutePrivilege.RequireAdmin : ExecutePrivilege.AsInvokerOrAsManifest, ExecuteWaitType.ReturnImmediately); + } +#endif + + private const string updateExeFileName = "UpdateMonitor.exe"; + + private delegate int ExecuteHandOff(IWin32Window parent, string exePath, string args, out IntPtr hProcess); + + /// + /// Uses the shell to execute the command. This method must only be used by Paint.NET + /// and not by plugins. + /// + /// + /// The window that is currently in the foreground. This may be null if requireAdmin + /// is false and the executable that exePath refers to is not marked (e.g. via a + /// manifest) as requiring administrator privilege. + /// + /// + /// The path to the executable to launch. + /// + /// + /// The command-line arguments for the executable. + /// + /// + /// The privileges to execute the new process with. + /// If the executable is already marked as requiring administrator privilege + /// (e.g. via a "requiresAdministrator" UAC manifest), this parameter should be + /// set to AsInvokerOrAsManifest. + /// + /// + /// If administrator privilege is required, a consent UI may be displayed asking the + /// user to approve the action. A parent window must be provided in this case so that + /// the consent UI will know where to position itself. Administrator privilege is + /// required if execPrivilege is set to RequireAdmin, or if the executable being launched + /// has a manifest declaring that it requires this privilege and if the operating + /// system recognizes the manifest. + /// + /// + /// execPrivilege was RequireAdmin, but parent was null. + /// + /// + /// execPrivilege was RequireAdmin, but the user does not have this privilege, nor do they + /// have the ability to acquire or elevate to obtain this privilege. + /// + /// + /// There was an error launching the program. + /// + public static void Execute( + IWin32Window parent, + string exePath, + string args, + ExecutePrivilege execPrivilege, + ExecuteWaitType execWaitType) + { + if (exePath == null) + { + throw new ArgumentNullException("exePath"); + } + + if (execPrivilege == ExecutePrivilege.RequireAdmin && parent == null) + { + throw new ArgumentException("If requireAdmin is true, a parent window must be provided"); + } + + // If this action requires admin privilege, but the user does not have this + // privilege and is not capable of acquiring this privilege, then we will + // throw an exception. + if (execPrivilege == ExecutePrivilege.RequireAdmin && + !Security.IsAdministrator && + !Security.CanElevateToAdministrator) + { + throw new SecurityException("Executable requires administrator privilege, but user is not an administrator and cannot elevate"); + } + + ExecuteHandOff executeHandOff = null; + switch (execPrivilege) + { + case ExecutePrivilege.AsInvokerOrAsManifest: + executeHandOff = new ExecuteHandOff(ExecAsInvokerOrAsManifest); + break; + + case ExecutePrivilege.RequireAdmin: + executeHandOff = new ExecuteHandOff(ExecRequireAdmin); + break; + + case ExecutePrivilege.RequireNonAdminIfPossible: + if (Security.CanLaunchNonAdminProcess) + { + executeHandOff = new ExecuteHandOff(ExecRequireNonAdmin); + } + else + { + executeHandOff = new ExecuteHandOff(ExecAsInvokerOrAsManifest); + } + break; + + default: + throw new InvalidEnumArgumentException("ExecutePrivilege"); + } + + string updateMonitorExePath = null; + if (execWaitType == ExecuteWaitType.RelaunchPdnOnExit) + { + RelaunchPdnHelperPart1(out updateMonitorExePath); + } + + IntPtr hProcess = IntPtr.Zero; + int nResult = executeHandOff(parent, exePath, args, out hProcess); + + if (nResult == NativeConstants.ERROR_SUCCESS) + { + if (execWaitType == ExecuteWaitType.WaitForExit) + { + SafeNativeMethods.WaitForSingleObject(hProcess, NativeConstants.INFINITE); + } + else if (execWaitType == ExecuteWaitType.RelaunchPdnOnExit) + { + bool bResult2 = SafeNativeMethods.SetHandleInformation( + hProcess, + NativeConstants.HANDLE_FLAG_INHERIT, + NativeConstants.HANDLE_FLAG_INHERIT); + + RelaunchPdnHelperPart2(updateMonitorExePath, hProcess); + + // Ensure that we don't close the process handle right away in the next few lines of code. + // It must be inherited by the child process. Yes, this is technically a leak but we are + // planning to terminate in just a moment anyway. + hProcess = IntPtr.Zero; + } + else if (execWaitType == ExecuteWaitType.ReturnImmediately) + { + } + + if (hProcess != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(hProcess); + hProcess = IntPtr.Zero; + } + } + else + { + if (nResult == NativeConstants.ERROR_CANCELLED || + nResult == NativeConstants.ERROR_TIMEOUT) + { + // no problem + } + else + { + NativeMethods.ThrowOnWin32Error("ExecuteHandoff failed", nResult); + } + + if (updateMonitorExePath != null) + { + try + { + File.Delete(updateMonitorExePath); + } + + catch (Exception) + { + } + + updateMonitorExePath = null; + } + } + + GC.KeepAlive(parent); + } + + private static int ExecAsInvokerOrAsManifest(IWin32Window parent, string exePath, string args, out IntPtr hProcess) + { + return ExecShellExecuteEx(parent, exePath, args, null, out hProcess); + } + + private static int ExecRequireAdmin(IWin32Window parent, string exePath, string args, out IntPtr hProcess) + { + const string runAs = "runas"; + string verb; + + if (Security.IsAdministrator) + { + verb = null; + } + else + { + verb = runAs; + } + + return ExecShellExecuteEx(parent, exePath, args, verb, out hProcess); + } + + private static int ExecRequireNonAdmin(IWin32Window parent, string exePath, string args, out IntPtr hProcess) + { + int nError = NativeConstants.ERROR_SUCCESS; + string commandLine = "\"" + exePath + "\"" + (args == null ? "" : (" " + args)); + + string dir; + + try + { + dir = Path.GetDirectoryName(exePath); + } + + catch (Exception) + { + dir = null; + } + + IntPtr hWndShell = IntPtr.Zero; + IntPtr hShellProcess = IntPtr.Zero; + IntPtr hShellProcessToken = IntPtr.Zero; + IntPtr hTokenCopy = IntPtr.Zero; + IntPtr bstrExePath = IntPtr.Zero; + IntPtr bstrCommandLine = IntPtr.Zero; + IntPtr bstrDir = IntPtr.Zero; + NativeStructs.PROCESS_INFORMATION procInfo = new NativeStructs.PROCESS_INFORMATION(); + + try + { + hWndShell = SafeNativeMethods.FindWindowW("Progman", null); + if (hWndShell == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("FindWindowW() returned NULL"); + } + + uint dwPID; + uint dwThreadId = SafeNativeMethods.GetWindowThreadProcessId(hWndShell, out dwPID); + if (0 == dwPID) + { + NativeMethods.ThrowOnWin32Error("GetWindowThreadProcessId returned 0", NativeErrors.ERROR_FILE_NOT_FOUND); + } + + hShellProcess = NativeMethods.OpenProcess(NativeConstants.PROCESS_QUERY_INFORMATION, false, dwPID); + if (IntPtr.Zero == hShellProcess) + { + NativeMethods.ThrowOnWin32Error("OpenProcess() returned NULL"); + } + + bool optResult = NativeMethods.OpenProcessToken( + hShellProcess, + NativeConstants.TOKEN_ASSIGN_PRIMARY | NativeConstants.TOKEN_DUPLICATE | NativeConstants.TOKEN_QUERY, + out hShellProcessToken); + + if (!optResult) + { + NativeMethods.ThrowOnWin32Error("OpenProcessToken() returned FALSE"); + } + + bool dteResult = NativeMethods.DuplicateTokenEx( + hShellProcessToken, + NativeConstants.MAXIMUM_ALLOWED, + IntPtr.Zero, + NativeConstants.SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, + NativeConstants.TOKEN_TYPE.TokenPrimary, + out hTokenCopy); + + if (!dteResult) + { + NativeMethods.ThrowOnWin32Error("DuplicateTokenEx() returned FALSE"); + } + + bstrExePath = Marshal.StringToBSTR(exePath); + bstrCommandLine = Marshal.StringToBSTR(commandLine); + bstrDir = Marshal.StringToBSTR(dir); + + bool cpwtResult = NativeMethods.CreateProcessWithTokenW( + hTokenCopy, + 0, + bstrExePath, + bstrCommandLine, + 0, + IntPtr.Zero, + bstrDir, + IntPtr.Zero, + out procInfo); + + if (cpwtResult) + { + hProcess = procInfo.hProcess; + procInfo.hProcess = IntPtr.Zero; + nError = NativeConstants.ERROR_SUCCESS; + } + else + { + hProcess = IntPtr.Zero; + nError = Marshal.GetLastWin32Error(); + } + } + + catch (Win32Exception ex) + { + Tracing.Ping(ex.ToString()); + nError = ex.ErrorCode; + hProcess = IntPtr.Zero; + } + + finally + { + if (bstrExePath != IntPtr.Zero) + { + Marshal.FreeBSTR(bstrExePath); + bstrExePath = IntPtr.Zero; + } + + if (bstrCommandLine != IntPtr.Zero) + { + Marshal.FreeBSTR(bstrCommandLine); + bstrCommandLine = IntPtr.Zero; + } + + if (bstrDir != IntPtr.Zero) + { + Marshal.FreeBSTR(bstrDir); + bstrDir = IntPtr.Zero; + } + + if (hShellProcess != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(hShellProcess); + hShellProcess = IntPtr.Zero; + } + + if (hShellProcessToken != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(hShellProcessToken); + hShellProcessToken = IntPtr.Zero; + } + + if (hTokenCopy != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(hTokenCopy); + hTokenCopy = IntPtr.Zero; + } + + if (procInfo.hThread != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(procInfo.hThread); + procInfo.hThread = IntPtr.Zero; + } + + if (procInfo.hProcess != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(procInfo.hProcess); + procInfo.hProcess = IntPtr.Zero; + } + } + + return nError; + } + + private static int ExecShellExecuteEx(IWin32Window parent, string exePath, string args, string verb, out IntPtr hProcess) + { + string dir; + + try + { + dir = Path.GetDirectoryName(exePath); + } + + catch (Exception) + { + dir = null; + } + + NativeStructs.SHELLEXECUTEINFO sei = new NativeStructs.SHELLEXECUTEINFO(); + sei.cbSize = (uint)Marshal.SizeOf(typeof(NativeStructs.SHELLEXECUTEINFO)); + + sei.fMask = + NativeConstants.SEE_MASK_NOCLOSEPROCESS | + NativeConstants.SEE_MASK_NO_CONSOLE | + NativeConstants.SEE_MASK_FLAG_DDEWAIT; + + sei.lpVerb = verb; + sei.lpDirectory = dir; + sei.lpFile = exePath; + sei.lpParameters = args; + + sei.nShow = NativeConstants.SW_SHOWNORMAL; + + if (parent != null) + { + sei.hwnd = parent.Handle; + } + + bool bResult = NativeMethods.ShellExecuteExW(ref sei); + hProcess = sei.hProcess; + sei.hProcess = IntPtr.Zero; + + int nResult = NativeConstants.ERROR_SUCCESS; + + if (!bResult) + { + nResult = Marshal.GetLastWin32Error(); + } + + return nResult; + } + + private static void RelaunchPdnHelperPart1(out string updateMonitorExePath) + { + string srcDir = Application.StartupPath; + string srcPath = Path.Combine(srcDir, updateExeFileName); + string srcPath2 = srcPath + ".config"; + + string dstDir = Environment.ExpandEnvironmentVariables(@"%TEMP%\PdnSetup"); + string dstPath = Path.Combine(dstDir, updateExeFileName); + string dstPath2 = dstPath + ".config"; + + if (!Directory.Exists(dstDir)) + { + Directory.CreateDirectory(dstDir); + } + + File.Copy(srcPath, dstPath, true); + File.Copy(srcPath2, dstPath2, true); + updateMonitorExePath = dstPath; + } + + private static void RelaunchPdnHelperPart2(string updateMonitorExePath, IntPtr hProcess) + { + string args = hProcess.ToInt64().ToString(CultureInfo.InstalledUICulture); + ProcessStartInfo psi = new ProcessStartInfo(updateMonitorExePath, args); + psi.UseShellExecute = false; + psi.WindowStyle = ProcessWindowStyle.Hidden; + Process process = Process.Start(psi); + process.Dispose(); + } + + /// + /// Asynchronously restarts Paint.NET. + /// + /// + /// This method does not restart Paint.NET immediately. Instead, it waits + /// for Paint.NET to terminate and then restarts it. It does not perform + /// the termination or shutdown. + /// + public static void RestartApplication() + { + string srcDir = Application.StartupPath; + string updateExePath = Path.Combine(srcDir, updateExeFileName); + + Process thisProcess = Process.GetCurrentProcess(); + IntPtr hProcess = thisProcess.Handle; + + bool bResult = SafeNativeMethods.SetHandleInformation( + hProcess, + NativeConstants.HANDLE_FLAG_INHERIT, + NativeConstants.HANDLE_FLAG_INHERIT); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("SetHandleInformation() returned false"); + } + + RelaunchPdnHelperPart2(updateExePath, hProcess); + } + + /// + /// Launches the default browser and opens the given URL. + /// + /// The URL to show. The maximum length is 512 characters. + /// + /// This method will not present an error dialog if the URL could not be launched. + /// Note: This method must only be used by Paint.NET, and not any plugins. It may + /// change or be removed in future versions. + /// + public static bool LaunchUrl(IWin32Window owner, string url) + { + if (url.Length > 512) + { + throw new ArgumentOutOfRangeException("url.Length must be <= 512"); + } + + bool success = false; + string quotedUrl = "\"" + url + "\""; + ExecutePrivilege executePrivilege; + + if (!Security.IsAdministrator || (Security.IsAdministrator && !Security.CanLaunchNonAdminProcess)) + { + executePrivilege = ExecutePrivilege.AsInvokerOrAsManifest; + } + else + { + executePrivilege = ExecutePrivilege.RequireNonAdminIfPossible; + } + + // Method 1. Just launch the url, and hope that the shell figures out the association correctly. + // This method will not work with ExecutePrivilege.RequireNonAdmin though. + if (!success && executePrivilege != ExecutePrivilege.RequireNonAdminIfPossible) + { + try + { + Execute(owner, quotedUrl, null, executePrivilege, ExecuteWaitType.ReturnImmediately); + success = true; + } + + catch (Exception ex) + { + Tracing.Ping("Exception while using method 1 to launch url, " + quotedUrl + ", :" + ex.ToString()); + success = false; + } + } + + // Method 2. Launch the url through explorer + if (!success) + { + const string shellFileLoc = @"%WINDIR%\explorer.exe"; + string shellExePath = "(n/a)"; + + try + { + shellExePath = Environment.ExpandEnvironmentVariables(shellFileLoc); + Execute(owner, shellExePath, quotedUrl, executePrivilege, ExecuteWaitType.ReturnImmediately); + success = true; + } + + catch (Exception ex) + { + Tracing.Ping("Exception while using method 2 to launch url through '" + shellExePath + "', " + quotedUrl + ", : " + ex.ToString()); + success = false; + } + } + + return success; + } + + [Obsolete("Use PdnInfo.OpenUrl() instead. Shell.LaunchUrl() must only be used by Paint.NET code, not by plugins.", true)] + public static bool OpenUrl(IWin32Window owner, string url) + { + return LaunchUrl(owner, url); + } + + public static void AddToRecentDocumentsList(string fileName) + { + // Apparently SHAddToRecentDocs can block for a very long period of time when certain + // conditions are met: so we just stick it on "the backburner." + ThreadPool.QueueUserWorkItem(new WaitCallback(AddToRecentDocumentsListImpl), fileName); + } + + private static void AddToRecentDocumentsListImpl(object fileNameObj) + { + string fileName = (string)fileNameObj; + IntPtr bstrFileName = IntPtr.Zero; + + try + { + bstrFileName = Marshal.StringToBSTR(fileName); + NativeMethods.SHAddToRecentDocs(NativeConstants.SHARD_PATHW, bstrFileName); + } + + finally + { + if (bstrFileName != IntPtr.Zero) + { + Marshal.FreeBSTR(bstrFileName); + bstrFileName = IntPtr.Zero; + } + } + } + + // TODO: convert to extension method in the 4.0 codebase, which can use .NET 3.5 + private static T2 Map(T1 mapFrom, Pair[] mappings) + { + foreach (Pair mapping in mappings) + { + if (mapping.First.Equals(mapFrom)) + { + return mapping.Second; + } + } + + throw new KeyNotFoundException(); + } + + private static string GetCSIDLPath(int csidl, bool tryCreateIfAbsent) + { + // First, try calling SHGetFolderPathW with the "CSIDL_FLAG_CREATE" flag. However, if it + // returns an error then ignore it. We've had some crash logs with "access denied" coming + // from this function. + int csidlWithFlags = csidl | (tryCreateIfAbsent ? NativeConstants.CSIDL_FLAG_CREATE : 0); + StringBuilder sbWithFlags = new StringBuilder(NativeConstants.MAX_PATH); + Do.TryBool(() => NativeMethods.SHGetFolderPathW(IntPtr.Zero, csidlWithFlags, IntPtr.Zero, NativeConstants.SHGFP_TYPE_CURRENT, sbWithFlags)); + + StringBuilder sb = new StringBuilder(NativeConstants.MAX_PATH); + NativeMethods.SHGetFolderPathW(IntPtr.Zero, csidl, IntPtr.Zero, NativeConstants.SHGFP_TYPE_CURRENT, sb); + + // If we get back something like 'Z:' then we need to put a backslash on it. + // Otherwise other path-related functions will freak out. + if (sb.Length == 2 && sb[1] == ':') + { + sb.Append(Path.DirectorySeparatorChar); + } + + string path = sb.ToString(); + + return path; + } + + private static readonly Pair[] pathMappings = new Pair[] + { + Pair.Create(VirtualFolderName.SystemProgramFiles, NativeConstants.CSIDL_PROGRAM_FILES), + Pair.Create(VirtualFolderName.UserDesktop, NativeConstants.CSIDL_DESKTOP_DIRECTORY), + Pair.Create(VirtualFolderName.UserDocuments, NativeConstants.CSIDL_PERSONAL), + Pair.Create(VirtualFolderName.UserLocalAppData, NativeConstants.CSIDL_LOCAL_APPDATA), + Pair.Create(VirtualFolderName.UserPictures, NativeConstants.CSIDL_MYPICTURES), + Pair.Create(VirtualFolderName.UserRoamingAppData, NativeConstants.CSIDL_APPDATA) + }; + + public static string GetVirtualPath(VirtualFolderName folderName, bool tryCreateIfAbsent) + { + try + { + int csidl = Map(folderName, pathMappings); + string path = GetCSIDLPath(csidl, tryCreateIfAbsent); + return path; + } + + catch (KeyNotFoundException) + { + throw new InvalidEnumArgumentException("folderName", (int)folderName, typeof(VirtualFolderName)); + } + } + } +} diff --git a/src/SystemLayer/SingleInstaceManager.cs b/src/SystemLayer/SingleInstaceManager.cs new file mode 100644 index 0000000..d635dc6 --- /dev/null +++ b/src/SystemLayer/SingleInstaceManager.cs @@ -0,0 +1,351 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Provides a way to manage and communicate between instances of an application + /// in the same user session. + /// + public sealed class SingleInstanceManager + : IDisposable + { + private const int mappingSize = 8; // sizeof(int64) + private string mappingName; + private Form window = null; + private IntPtr hWnd = IntPtr.Zero; + private IntPtr hFileMapping; + private List pendingInstanceMessages = new List(); + private bool isFirstInstance; + + public bool IsFirstInstance + { + get + { + return this.isFirstInstance; + } + } + + public bool AreMessagesPending + { + get + { + lock (this.pendingInstanceMessages) + { + return (this.pendingInstanceMessages.Count > 0); + } + } + } + + public void SetWindow(Form newWindow) + { + if (this.window != null) + { + UnregisterWindow(); + } + + RegisterWindow(newWindow); + } + + private void UnregisterWindow() + { + if (this.window != null) + { + this.window.HandleCreated -= new EventHandler(Window_HandleCreated); + this.window.HandleDestroyed -= new EventHandler(Window_HandleDestroyed); + this.window.Disposed -= new EventHandler(Window_Disposed); + WriteHandleValueToMappedFile(IntPtr.Zero); + this.hWnd = IntPtr.Zero; + this.window = null; + } + } + + private void RegisterWindow(Form newWindow) + { + this.window = newWindow; + + if (this.window != null) + { + this.window.HandleCreated += new EventHandler(Window_HandleCreated); + this.window.HandleDestroyed += new EventHandler(Window_HandleDestroyed); + this.window.Disposed += new EventHandler(Window_Disposed); + + if (this.window.IsHandleCreated) + { + this.hWnd = this.window.Handle; + WriteHandleValueToMappedFile(this.hWnd); + } + } + + GC.KeepAlive(newWindow); + } + + private void Window_Disposed(object sender, EventArgs e) + { + UnregisterWindow(); + } + + private void Window_HandleDestroyed(object sender, EventArgs e) + { + UnregisterWindow(); + } + + private void Window_HandleCreated(object sender, EventArgs e) + { + this.hWnd = this.window.Handle; + WriteHandleValueToMappedFile(this.hWnd); + GC.KeepAlive(this.window); + } + + public string[] GetPendingInstanceMessages() + { + string[] messages; + + lock (this.pendingInstanceMessages) + { + messages = this.pendingInstanceMessages.ToArray(); + this.pendingInstanceMessages.Clear(); + } + + return messages; + } + + public event EventHandler InstanceMessageReceived; + private void OnInstanceMessageReceived() + { + if (InstanceMessageReceived != null) + { + InstanceMessageReceived(this, EventArgs.Empty); + } + } + + public void SendInstanceMessage(string text) + { + SendInstanceMessage(text, 1); + } + + public void SendInstanceMessage(string text, int timeoutSeconds) + { + IntPtr ourHwnd = IntPtr.Zero; + DateTime now = DateTime.Now; + DateTime timeoutTime = DateTime.Now + new TimeSpan(0, 0, 0, timeoutSeconds); + + while (ourHwnd == IntPtr.Zero && now < timeoutTime) + { + ourHwnd = ReadHandleFromFromMappedFile(); + now = DateTime.Now; + + if (ourHwnd == IntPtr.Zero) + { + System.Threading.Thread.Sleep(100); + } + } + + if (ourHwnd != IntPtr.Zero) + { + NativeStructs.COPYDATASTRUCT copyDataStruct = new NativeStructs.COPYDATASTRUCT(); + IntPtr szText = IntPtr.Zero; + + try + { + unsafe + { + szText = Marshal.StringToCoTaskMemUni(text); + copyDataStruct.dwData = UIntPtr.Zero; + copyDataStruct.lpData = szText; + copyDataStruct.cbData = (uint)(2 * (1 + text.Length)); + IntPtr lParam = new IntPtr((void*)©DataStruct); + + SafeNativeMethods.SendMessageW(ourHwnd, NativeConstants.WM_COPYDATA, this.hWnd, lParam); + } + } + + finally + { + if (szText != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(szText); + szText = IntPtr.Zero; + } + } + } + } + + public void FocusFirstInstance() + { + IntPtr ourHwnd = this.ReadHandleFromFromMappedFile(); + + if (ourHwnd != IntPtr.Zero) + { + if (SafeNativeMethods.IsIconic(ourHwnd)) + { + SafeNativeMethods.ShowWindow(ourHwnd, NativeConstants.SW_RESTORE); + } + + SafeNativeMethods.SetForegroundWindow(ourHwnd); + } + } + + public void FilterMessage(ref Message m) + { + if (m.Msg == NativeConstants.WM_COPYDATA) + { + unsafe + { + NativeStructs.COPYDATASTRUCT* pCopyDataStruct = (NativeStructs.COPYDATASTRUCT*)m.LParam.ToPointer(); + string message = Marshal.PtrToStringUni(pCopyDataStruct->lpData); + + lock (this.pendingInstanceMessages) + { + this.pendingInstanceMessages.Add(message); + } + + OnInstanceMessageReceived(); + } + } + } + + public SingleInstanceManager(string moniker) + { + int error = NativeConstants.ERROR_SUCCESS; + + if (moniker.IndexOf('\\') != -1) + { + throw new ArgumentException("moniker must not have a backslash character"); + } + + this.mappingName = "Local\\" + moniker; + + this.hFileMapping = SafeNativeMethods.CreateFileMappingW( + NativeConstants.INVALID_HANDLE_VALUE, + IntPtr.Zero, + NativeConstants.PAGE_READWRITE | NativeConstants.SEC_COMMIT, + 0, + mappingSize, + mappingName); + + error = Marshal.GetLastWin32Error(); + + if (this.hFileMapping == IntPtr.Zero) + { + throw new Win32Exception(error, "CreateFileMappingW() returned NULL (" + error.ToString() + ")"); + } + + this.isFirstInstance = (error != NativeConstants.ERROR_ALREADY_EXISTS); + } + + private void WriteHandleValueToMappedFile(IntPtr hValue) + { + int error = NativeConstants.ERROR_SUCCESS; + bool bResult = true; + + IntPtr lpData = SafeNativeMethods.MapViewOfFile( + this.hFileMapping, + NativeConstants.FILE_MAP_WRITE, + 0, + 0, + new UIntPtr((uint)mappingSize)); + + error = Marshal.GetLastWin32Error(); + + if (lpData == IntPtr.Zero) + { + throw new Win32Exception(error, "MapViewOfFile() returned NULL (" + error + ")"); + } + + long int64 = hValue.ToInt64(); + byte[] int64Bytes = new byte[(int)mappingSize]; + + for (int i = 0; i < mappingSize; ++i) + { + int64Bytes[i] = (byte)((int64 >> (i * 8)) & 0xff); + } + + Marshal.Copy(int64Bytes, 0, lpData, mappingSize); + + bResult = SafeNativeMethods.UnmapViewOfFile(lpData); + error = Marshal.GetLastWin32Error(); + + if (!bResult) + { + throw new Win32Exception(error, "UnmapViewOfFile() returned FALSE (" + error + ")"); + } + } + + private IntPtr ReadHandleFromFromMappedFile() + { + int error = NativeConstants.ERROR_SUCCESS; + + IntPtr lpData = SafeNativeMethods.MapViewOfFile( + this.hFileMapping, + NativeConstants.FILE_MAP_READ, + 0, + 0, + new UIntPtr((uint)mappingSize)); + + error = Marshal.GetLastWin32Error(); + + if (lpData == IntPtr.Zero) + { + throw new Win32Exception(error, "MapViewOfFile() returned NULL (" + error + ")"); + } + + byte[] int64Bytes = new byte[(int)mappingSize]; + Marshal.Copy(lpData, int64Bytes, 0, mappingSize); + + long int64 = 0; + for (int i = 0; i < mappingSize; ++i) + { + int64 += (long)(int64Bytes[i] << (i * 8)); + } + + bool bResult = SafeNativeMethods.UnmapViewOfFile(lpData); + error = Marshal.GetLastWin32Error(); + + if (!bResult) + { + throw new Win32Exception(error, "UnmapViewOfFile() returned FALSE (" + error + ")"); + } + + IntPtr hValue = new IntPtr(int64); + return hValue; + } + + ~SingleInstanceManager() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (disposing) + { + UnregisterWindow(); + } + + if (this.hFileMapping != IntPtr.Zero) + { + SafeNativeMethods.CloseHandle(this.hFileMapping); + this.hFileMapping = IntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/src/SystemLayer/SystemLayer.csproj b/src/SystemLayer/SystemLayer.csproj new file mode 100644 index 0000000..ef7e6fc --- /dev/null +++ b/src/SystemLayer/SystemLayer.csproj @@ -0,0 +1,254 @@ + + + Local + 9.0.21022 + 2.0 + {80572820-93A5-4278-A513-D902BEA2639C} + Debug + AnyCPU + + + + + PaintDotNet.SystemLayer + + + JScript + Grid + IE50 + false + Library + PaintDotNet.SystemLayer + OnBuildSuccess + + + + + + + 2.0 + + + bin\Debug\ + true + 281280512 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + true + + + bin\Release\ + true + 281280512 + false + + + TRACE + + + true + 4096 + false + + + true + false + false + true + 4 + pdbonly + prompt + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + + False + ..\Interop.WIA\Interop.WIA.dll + + + System + + + + System.Drawing + + + System.Windows.Forms + + + + + + Code + + + + + + + + + + + + + + + + + Code + + + Code + + + Code + + + + Component + + + + + + + + + + + + Code + + + Code + + + Code + + + Component + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Component + + + Code + + + Code + + + Code + + + + + + Code + + + Component + + + Code + + + Code + + + Code + + + + + + + + Code + + + + + + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} + StylusReader + + + + + @rem register the WIA 2.0 library +rem regsvr32 /s "$(SolutionDir)\WIAAutSDK\wiaaut.dll" + + @rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/SystemLayer/ThreadBackground.cs b/src/SystemLayer/ThreadBackground.cs new file mode 100644 index 0000000..a109388 --- /dev/null +++ b/src/SystemLayer/ThreadBackground.cs @@ -0,0 +1,108 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.Threading; + +namespace PaintDotNet.SystemLayer +{ + // Current implementation deficiency: The interface is such that an implied push/pop of + // flags is presented. However, once you 'push' a background mode, it is not 'popped' + // until all ThreadBackground objects have been disposed on the current thread. + + public sealed class ThreadBackground + : IDisposable + { + [ThreadStatic] + private static int count = 0; + + [ThreadStatic] + private ThreadBackgroundFlags activeFlags = ThreadBackgroundFlags.None; + + private Thread currentThread; + private ThreadPriority oldThreadPriority; + private ThreadBackgroundFlags flags; + + public ThreadBackground(ThreadBackgroundFlags flags) + { + this.flags = flags; + this.currentThread = Thread.CurrentThread; + this.oldThreadPriority = this.currentThread.Priority; + + if ((flags & ThreadBackgroundFlags.Cpu) == ThreadBackgroundFlags.Cpu && + (activeFlags & ThreadBackgroundFlags.Cpu) != ThreadBackgroundFlags.Cpu) + { + this.currentThread.Priority = ThreadPriority.BelowNormal; + activeFlags |= ThreadBackgroundFlags.Cpu; + } + + if (Environment.OSVersion.Version >= OS.WindowsVista && + (flags & ThreadBackgroundFlags.IO) == ThreadBackgroundFlags.IO && + (activeFlags & ThreadBackgroundFlags.IO) != ThreadBackgroundFlags.IO) + { + IntPtr hThread = SafeNativeMethods.GetCurrentThread(); + bool bResult = SafeNativeMethods.SetThreadPriority(hThread, NativeConstants.THREAD_MODE_BACKGROUND_BEGIN); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("SetThreadPriority(THREAD_MODE_BACKGROUND_BEGIN) returned FALSE"); + } + } + + activeFlags |= flags; + + ++count; + } + + ~ThreadBackground() + { + Debug.Assert(false, "ThreadBackgroundMode() object must be manually Disposed()"); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + --count; + + if (Thread.CurrentThread.ManagedThreadId != this.currentThread.ManagedThreadId) + { + throw new InvalidOperationException("Dispose() was called on a thread other than the one that this object was created on"); + } + + if (count == 0) + { + if ((activeFlags & ThreadBackgroundFlags.Cpu) == ThreadBackgroundFlags.Cpu) + { + this.currentThread.Priority = this.oldThreadPriority; + activeFlags &= ~ThreadBackgroundFlags.Cpu; + } + + if (Environment.OSVersion.Version >= OS.WindowsVista && + (activeFlags & ThreadBackgroundFlags.IO) == ThreadBackgroundFlags.IO) + { + IntPtr hThread = SafeNativeMethods.GetCurrentThread(); + bool bResult = SafeNativeMethods.SetThreadPriority(hThread, NativeConstants.THREAD_MODE_BACKGROUND_END); + + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("SetThreadPriority(THREAD_MODE_BACKGROUND_END) returned FALSE"); + } + + activeFlags &= ~ThreadBackgroundFlags.IO; + } + } + } + } +} diff --git a/src/SystemLayer/ThreadBackgroundFlags.cs b/src/SystemLayer/ThreadBackgroundFlags.cs new file mode 100644 index 0000000..f137c1a --- /dev/null +++ b/src/SystemLayer/ThreadBackgroundFlags.cs @@ -0,0 +1,23 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + [Flags] + public enum ThreadBackgroundFlags + { + None = 0, + Cpu = 1, + IO = 2, + + All = Cpu | IO + } +} diff --git a/src/SystemLayer/Timing.cs b/src/SystemLayer/Timing.cs new file mode 100644 index 0000000..87d3a01 --- /dev/null +++ b/src/SystemLayer/Timing.cs @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Methods for keeping track of time in a high precision manner. + /// + /// + /// This class provides precision and accuracy of 1 millisecond. + /// + public sealed class Timing + { + private ulong countsPerMs; + private double countsPerMsDouble; + private ulong birthTick; + + /// + /// The number of milliseconds that elapsed between system startup + /// and creation of this instance of Timing. + /// + public ulong BirthTick + { + get + { + return birthTick; + } + } + + /// + /// Returns the number of milliseconds that have elapsed since + /// system startup. + /// + public ulong GetTickCount() + { + ulong tick; + SafeNativeMethods.QueryPerformanceCounter(out tick); + return tick / countsPerMs; + } + + /// + /// Returns the number of milliseconds that have elapsed since + /// system startup. + /// + public double GetTickCountDouble() + { + ulong tick; + SafeNativeMethods.QueryPerformanceCounter(out tick); + return (double)tick / countsPerMsDouble; + } + + /// + /// Constructs an instance of the Timing class. + /// + public Timing() + { + ulong frequency; + + if (!SafeNativeMethods.QueryPerformanceFrequency(out frequency)) + { + NativeMethods.ThrowOnWin32Error("QueryPerformanceFrequency returned false"); + } + + countsPerMs = frequency / 1000; + countsPerMsDouble = (double)frequency / 1000.0; + birthTick = GetTickCount(); + } + } +} diff --git a/src/SystemLayer/ToolStripEx.cs b/src/SystemLayer/ToolStripEx.cs new file mode 100644 index 0000000..7871641 --- /dev/null +++ b/src/SystemLayer/ToolStripEx.cs @@ -0,0 +1,235 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// This class adds on to the functionality provided in System.Windows.Forms.ToolStrip. + /// + /// + /// The first aggravating thing I found out about ToolStrip is that it does not "click through." + /// If the form that is hosting a ToolStrip is not active and you click on a button in the tool + /// strip, it sets focus to the form but does NOT click the button. This makes sense in many + /// situations, but definitely not for Paint.NET. + /// + public class ToolStripEx + : ToolStrip + { + private bool clickThrough = true; + private bool managedFocus = true; + private static int lockFocusCount = 0; + + public ToolStripEx() + { + ToolStripProfessionalRenderer tspr = this.Renderer as ToolStripProfessionalRenderer; + + if (tspr != null) + { + tspr.ColorTable.UseSystemColors = true; + tspr.RoundedEdges = false; + } + + this.ImageScalingSize = new System.Drawing.Size(UI.ScaleWidth(16), UI.ScaleHeight(16)); + } + + protected override bool ProcessCmdKey(ref Message m, Keys keyData) + { + bool processed = false; + Form form = this.FindForm(); + FormEx formEx = FormEx.FindFormEx(form); + + if (formEx != null) + { + processed = formEx.RelayProcessCmdKey(keyData); + } + + return processed; + } + + /// + /// Gets or sets whether the ToolStripEx honors item clicks when its containing form does + /// not have input focus. + /// + /// + /// Default value is true, which is the opposite of the behavior provided by the base + /// ToolStrip class. + /// + public bool ClickThrough + { + get + { + return this.clickThrough; + } + + set + { + this.clickThrough = value; + } + } + + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + + if (this.clickThrough) + { + UI.ClickThroughWndProc(ref m); + } + } + + /// + /// This event is raised when this toolstrip instance wishes to relinquish focus. + /// + public event EventHandler RelinquishFocus; + + private void OnRelinquishFocus() + { + if (RelinquishFocus != null) + { + RelinquishFocus(this, EventArgs.Empty); + } + } + + /// + /// Gets or sets whether the toolstrip manages focus. + /// + /// + /// If this is true, the toolstrip will capture focus when the mouse enters its client area. It will then + /// relinquish focus (via the RelinquishFocus event) when the mouse leaves. It will not capture or + /// attempt to relinquish focus if MenuStripEx.IsAnyMenuActive returns true. + /// + public bool ManagedFocus + { + get + { + return this.managedFocus; + } + + set + { + this.managedFocus = value; + } + } + + protected override void OnItemAdded(ToolStripItemEventArgs e) + { + ToolStripComboBox tscb = e.Item as ToolStripComboBox; + ToolStripTextBox tstb = e.Item as ToolStripTextBox; + + if (tscb != null) + { + tscb.DropDownClosed += ComboBox_DropDownClosed; + tscb.Enter += ComboBox_Enter; + tscb.Leave += ComboBox_Leave; + } + else if (tstb != null) + { + tstb.Enter += TextBox_Enter; + tstb.Leave += TextBox_Enter; + } + else + { + e.Item.MouseEnter += OnItemMouseEnter; + } + + base.OnItemAdded(e); + } + + private static void PushLockFocus() + { + Interlocked.Increment(ref lockFocusCount); + } + + private static void PopLockFocus() + { + Interlocked.Decrement(ref lockFocusCount); + } + + private static bool IsFocusLocked + { + get + { + return lockFocusCount > 0; + } + } + + private void ComboBox_Enter(object sender, EventArgs e) + { + PushLockFocus(); + } + + private void ComboBox_Leave(object sender, EventArgs e) + { + PopLockFocus(); + } + + private void TextBox_Enter(object sender, EventArgs e) + { + PushLockFocus(); + } + + private void TextBox_Leave(object sender, EventArgs e) + { + PopLockFocus(); + } + + private void ComboBox_DropDownClosed(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + protected override void OnItemRemoved(ToolStripItemEventArgs e) + { + ToolStripComboBox tscb = e.Item as ToolStripComboBox; + ToolStripTextBox tstb = e.Item as ToolStripTextBox; + + if (tscb != null) + { + tscb.DropDownClosed -= ComboBox_DropDownClosed; + tscb.Enter -= ComboBox_Enter; + tscb.Leave -= ComboBox_Leave; + } + else if (tstb != null) + { + tstb.Enter -= TextBox_Enter; + tstb.Leave -= TextBox_Enter; + } + else + { + e.Item.MouseEnter -= OnItemMouseEnter; + } + + base.OnItemRemoved(e); + } + + private void OnItemMouseEnter(object sender, EventArgs e) + { + if (this.managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive && !IsFocusLocked) + { + Tracing.Ping("stealing focus"); + this.Focus(); + } + } + + protected override void OnMouseLeave(EventArgs e) + { + if (this.managedFocus && !MenuStripEx.IsAnyMenuActive && UI.IsOurAppActive && !IsFocusLocked) + { + Tracing.Ping("relinquishing focus"); + OnRelinquishFocus(); + } + + base.OnMouseLeave(e); + } + } +} diff --git a/src/SystemLayer/Tracing.cs b/src/SystemLayer/Tracing.cs new file mode 100644 index 0000000..fdb1334 --- /dev/null +++ b/src/SystemLayer/Tracing.cs @@ -0,0 +1,186 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Methods for manual profiling and tracing. + /// + /// + /// This class does not rely on any system-specific functionality, but is placed + /// in the SystemLayer assembly (as opposed to Core) so that classes here may + /// also used it. + /// + public static class Tracing + { + private static void WriteLine(string msg) + { + Console.WriteLine(msg); + } + + private static Set features = new Set(); + + public static void LogFeature(string featureName) + { + if (string.IsNullOrEmpty(featureName)) + { + return; + } + + Ping("Logging feature: " + featureName); + + lock (features) + { + if (!features.Contains(featureName)) + { + features.Add(featureName); + } + } + } + + public static IEnumerable GetLoggedFeatures() + { + lock (features) + { + return features.ToArray(); + } + } + +#if DEBUG + private static Timing timing = new Timing(); + private static Stack tracePoints = new Stack(); + + private class TracePoint + { + private string message; + private ulong timestamp; + + public string Message + { + get + { + return message; + } + } + + public ulong Timestamp + { + get + { + return timestamp; + } + } + + public TracePoint(string message, ulong timestamp) + { + this.message = message; + this.timestamp = timestamp; + } + } +#endif + + [Conditional("DEBUG")] + public static void Enter() + { +#if DEBUG + StackTrace trace = new StackTrace(); + StackFrame parentFrame = trace.GetFrame(1); + MethodBase parentMethod = parentFrame.GetMethod(); + ulong now = timing.GetTickCount(); + string msg = new string(' ', 4 * tracePoints.Count) + parentMethod.DeclaringType.Name + "." + parentMethod.Name; + WriteLine((now - timing.BirthTick).ToString() + ": " + msg); + TracePoint tracePoint = new TracePoint(msg, now); + tracePoints.Push(tracePoint); +#endif + } + + [Conditional("DEBUG")] + public static void Enter(string message) + { +#if DEBUG + StackTrace trace = new StackTrace(); + StackFrame parentFrame = trace.GetFrame(1); + MethodBase parentMethod = parentFrame.GetMethod(); + ulong now = timing.GetTickCount(); + string msg = new string(' ', 4 * tracePoints.Count) + parentMethod.DeclaringType.Name + "." + + parentMethod.Name + ": " + message; + WriteLine((now - timing.BirthTick).ToString() + ": " + msg); + TracePoint tracePoint = new TracePoint(msg, now); + tracePoints.Push(tracePoint); +#endif + } + + [Conditional("DEBUG")] + public static void Leave() + { +#if DEBUG + TracePoint tracePoint = (TracePoint)tracePoints.Pop(); + ulong now = timing.GetTickCount(); + WriteLine((now - timing.BirthTick).ToString() + ": " + tracePoint.Message + " (" + + (now - tracePoint.Timestamp).ToString() + "ms)"); +#endif + } + + [Conditional("DEBUG")] + public static void Ping(string message, int callerCount) + { +#if DEBUG + StackTrace trace = new StackTrace(); + string callerString = ""; + for (int i = 0; i < Math.Min(trace.FrameCount - 1, callerCount); ++i) + { + StackFrame frame = trace.GetFrame(1 + i); + MethodBase method = frame.GetMethod(); + callerString += method.DeclaringType.Name + "." + method.Name; + if (i != callerCount - 1) + { + callerString += " <- "; + } + } + ulong now = timing.GetTickCount(); + WriteLine((now - timing.BirthTick).ToString() + ": " + new string(' ', 4 * tracePoints.Count) + + callerString + (message != null ? (": " + message) : "")); +#endif + } + + [Conditional("DEBUG")] + public static void Ping(string message) + { +#if DEBUG + StackTrace trace = new StackTrace(); + StackFrame parentFrame = trace.GetFrame(1); + MethodBase parentMethod = parentFrame.GetMethod(); + ulong now = timing.GetTickCount(); + WriteLine((now - timing.BirthTick).ToString() + ": " + new string(' ', 4 * tracePoints.Count) + + parentMethod.DeclaringType.Name + "." + parentMethod.Name + (message != null ? (": " + message) : "")); +#endif + } + + [Conditional("DEBUG")] + public static void Ping() + { +#if DEBUG + StackTrace trace = new StackTrace(); + StackFrame parentFrame = trace.GetFrame(1); + MethodBase parentMethod = parentFrame.GetMethod(); + ulong now = timing.GetTickCount(); + WriteLine((now - timing.BirthTick).ToString() + ": " + new string(' ', 4 * tracePoints.Count) + + parentMethod.DeclaringType.Name + "." + parentMethod.Name); +#endif + } + + } +} diff --git a/src/SystemLayer/UI.cs b/src/SystemLayer/UI.cs new file mode 100644 index 0000000..9898a74 --- /dev/null +++ b/src/SystemLayer/UI.cs @@ -0,0 +1,694 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Contains static methods related to the user interface. + /// + public static class UI + { + private static bool initScales = false; + private static float xScale; + private static float yScale; + + public static void FlashForm(Form form) + { + IntPtr hWnd = form.Handle; + SafeNativeMethods.FlashWindow(hWnd, false); + SafeNativeMethods.FlashWindow(hWnd, false); + GC.KeepAlive(form); + } + + /// + /// In some circumstances, the window manager will draw the window larger than it reports + /// its size to be. You can use this function to retrieve the size of this extra border + /// padding. + /// + /// + /// + /// An integer greater than or equal to zero that describes the size of the border padding + /// which is not reported via the window's Size or Bounds property. + /// + /// + /// Note to implementors: This method may simply return 0. It is provided for use in Windows + /// Vista when DWM+Aero is enabled in which case sizable FloatingToolForm windows do not + /// visibly dock to the correct locations. + /// + public static int GetExtendedFrameBounds(Form window) + { + int returnVal; + + if (OS.IsVistaOrLater) + { + unsafe + { + int* rcVal = stackalloc int[4]; + + int hr = SafeNativeMethods.DwmGetWindowAttribute( + window.Handle, + NativeConstants.DWMWA_EXTENDED_FRAME_BOUNDS, + (void*)rcVal, + 4 * (uint)sizeof(int)); + + if (hr >= 0) + { + returnVal = -rcVal[0]; + } + else + { + returnVal = 0; + } + } + } + else + { + returnVal = 0; + } + + GC.KeepAlive(window); + return Math.Max(0, returnVal); + } + + private static void InitScaleFactors(Control c) + { + if (c == null) + { + xScale = 1.0f; + yScale = 1.0f; + } + else + { + using (Graphics g = c.CreateGraphics()) + { + xScale = g.DpiX / 96.0f; + yScale = g.DpiY / 96.0f; + } + } + + initScales = true; + } + + public static void InitScaling(Control c) + { + if (!initScales) + { + InitScaleFactors(c); + } + } + + public static float ScaleWidth(float width) + { + return (float)Math.Round(width * GetXScaleFactor()); + } + + public static int ScaleWidth(int width) + { + return (int)Math.Round((float)width * GetXScaleFactor()); + } + + public static int ScaleHeight(int height) + { + return (int)Math.Round((float)height * GetYScaleFactor()); + } + + public static float ScaleHeight(float height) + { + return (float)Math.Round(height * GetYScaleFactor()); + } + + public static Size ScaleSize(Size size) + { + return new Size(ScaleWidth(size.Width), ScaleHeight(size.Height)); + } + + public static Point ScalePoint(Point pt) + { + return new Point(ScaleWidth(pt.X), ScaleHeight(pt.Y)); + } + + public static float GetXScaleFactor() + { + if (!initScales) + { + throw new InvalidOperationException("Must call InitScaling() first"); + } + + return xScale; + } + + public static float GetYScaleFactor() + { + if (!initScales) + { + throw new InvalidOperationException("Must call InitScaling() first"); + } + + return yScale; + } + + public static void DrawCommandButton( + Graphics g, + PushButtonState state, + Rectangle rect, + Color backColor, + Control childControl) + { + VisualStyleElement element = null; + int alpha = 255; + + if (OS.IsVistaOrLater) + { + const string className = "BUTTON"; + const int partID = NativeConstants.BP_COMMANDLINK; + int stateID; + + switch (state) + { + case PushButtonState.Default: + stateID = NativeConstants.CMDLS_DEFAULTED; + break; + + case PushButtonState.Disabled: + stateID = NativeConstants.CMDLS_DISABLED; + break; + + case PushButtonState.Hot: + stateID = NativeConstants.CMDLS_HOT; + break; + + case PushButtonState.Normal: + stateID = NativeConstants.CMDLS_NORMAL; + break; + + case PushButtonState.Pressed: + stateID = NativeConstants.CMDLS_PRESSED; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + try + { + element = VisualStyleElement.CreateElement(className, partID, stateID); + + if (!VisualStyleRenderer.IsElementDefined(element)) + { + element = null; + } + } + + catch (InvalidOperationException) + { + element = null; + } + } + + if (element == null) + { + switch (state) + { + case PushButtonState.Default: + element = VisualStyleElement.Button.PushButton.Default; + alpha = 95; + break; + + case PushButtonState.Disabled: + element = VisualStyleElement.Button.PushButton.Disabled; + break; + + case PushButtonState.Hot: + element = VisualStyleElement.Button.PushButton.Hot; + break; + + case PushButtonState.Normal: + alpha = 0; + element = VisualStyleElement.Button.PushButton.Normal; + break; + case PushButtonState.Pressed: + element = VisualStyleElement.Button.PushButton.Pressed; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + + if (element != null) + { + try + { + VisualStyleRenderer renderer = new VisualStyleRenderer(element); + renderer.DrawParentBackground(g, rect, childControl); + renderer.DrawBackground(g, rect); + } + + catch (Exception) + { + element = null; + } + } + + if (element == null) + { + ButtonRenderer.DrawButton(g, rect, state); + } + + if (alpha != 255) + { + using (Brush backBrush = new SolidBrush(Color.FromArgb(255 - alpha, backColor))) + { + CompositingMode oldCM = g.CompositingMode; + + try + { + g.CompositingMode = CompositingMode.SourceOver; + g.FillRectangle(backBrush, rect); + } + + finally + { + g.CompositingMode = oldCM; + } + } + } + } + + /// + /// Sets the control's redraw state. + /// + /// The control whose state should be modified. + /// The new state for redrawing ability. + /// + /// Note to implementors: This method is used by SuspendControlPainting() and ResumeControlPainting(). + /// This may be implemented as a no-op. + /// + private static void SetControlRedrawImpl(Control control, bool enabled) + { + SafeNativeMethods.SendMessageW(control.Handle, NativeConstants.WM_SETREDRAW, enabled ? new IntPtr(1) : IntPtr.Zero, IntPtr.Zero); + GC.KeepAlive(control); + } + + private static Dictionary controlRedrawStack = new Dictionary(); + + /// + /// Suspends the control's ability to draw itself. + /// + /// The control to suspend drawing for. + /// + /// When drawing is suspended, any painting performed in the control's WM_PAINT, OnPaint(), + /// WM_ERASEBKND, or OnPaintBackground() handlers is completely ignored. Invalidation rectangles + /// are not accumulated during this period, so when drawing is resumed (with + /// ResumeControlPainting()), it is usually a good idea to call Invalidate(true) on the control. + /// This method must be matched at a later time by a corresponding call to ResumeControlPainting(). + /// If you call SuspendControlPainting() multiple times for the same control, then you must + /// call ResumeControlPainting() once for each call. + /// Note to implementors: Do not modify this method. Instead, modify SetControlRedrawImpl(), + /// which may be implemented as a no-op. + /// + public static void SuspendControlPainting(Control control) + { + int pushCount; + + if (controlRedrawStack.TryGetValue(control, out pushCount)) + { + ++pushCount; + } + else + { + pushCount = 1; + } + + if (pushCount == 1) + { + SetControlRedrawImpl(control, false); + } + + controlRedrawStack[control] = pushCount; + } + + /// + /// Resumes the control's ability to draw itself. + /// + /// The control to suspend drawing for. + /// + /// This method must be matched by a preceding call to SuspendControlPainting(). If that method + /// was called multiple times, then this method must be called a corresponding number of times + /// in order to enable drawing. + /// This method must be matched at a later time by a corresponding call to ResumeControlPainting(). + /// If you call SuspendControlPainting() multiple times for the same control, then you must + /// call ResumeControlPainting() once for each call. + /// Note to implementors: Do not modify this method. Instead, modify SetControlRedrawImpl(), + /// which may be implemented as a no-op. + /// + public static void ResumeControlPainting(Control control) + { + int pushCount; + + if (controlRedrawStack.TryGetValue(control, out pushCount)) + { + --pushCount; + } + else + { + throw new InvalidOperationException("There was no previous matching SuspendControlPainting() for this control"); + } + + if (pushCount == 0) + { + SetControlRedrawImpl(control, true); + controlRedrawStack.Remove(control); + } + else + { + controlRedrawStack[control] = pushCount; + } + } + + /// + /// Queries whether painting is enabled for the given control. + /// + /// The control to query suspension for. + /// + /// false if the control's painting has been suspended via a call to SuspendControlPainting(), + /// otherwise true. + /// + /// + /// You may use the return value of this method to optimize away painting. If this + /// method returns false, then you may skip your entire OnPaint() method. This saves + /// processor time by avoiding all of the non-painting drawing and resource initialization + /// and destruction that is typically contained in OnPaint(). + /// This method assumes painting suspension is being exclusively managed with Suspend- + /// and ResumeControlPainting(). + /// + public static bool IsControlPaintingEnabled(Control control) + { + int pushCount; + + if (!controlRedrawStack.TryGetValue(control, out pushCount)) + { + pushCount = 0; + } + + return (pushCount == 0); + } + + private static IntPtr hRgn = SafeNativeMethods.CreateRectRgn(0, 0, 1, 1); + + /// + /// This method retrieves the update region of a control. + /// + /// The control to retrieve the update region for. + /// + /// An array of rectangles specifying the area that has been invalidated, or + /// null if this could not be determined. + /// + /// + /// This method is not thread safe. + /// Note to implementors: This method may be implemented as a no-op. In this case, just return null. + /// + public static Rectangle[] GetUpdateRegion(Control control) + { + SafeNativeMethods.GetUpdateRgn(control.Handle, hRgn, false); + Rectangle[] scans; + int area; + PdnGraphics.GetRegionScans(hRgn, out scans, out area); + GC.KeepAlive(control); + return scans; + } + + /// + /// Sets a form's opacity. + /// + /// + /// + /// + /// Note to implementors: This may be implemented as just "form.Opacity = opacity". + /// This method works around some visual clumsiness in .NET 2.0 related to + /// transitioning between opacity == 1.0 and opacity != 1.0. + public static void SetFormOpacity(Form form, double opacity) + { + if (opacity < 0.0 || opacity > 1.0) + { + throw new ArgumentOutOfRangeException("opacity", "must be in the range [0, 1]"); + } + + uint exStyle = SafeNativeMethods.GetWindowLongW(form.Handle, NativeConstants.GWL_EXSTYLE); + + byte bOldAlpha = 255; + + if ((exStyle & NativeConstants.GWL_EXSTYLE) != 0) + { + uint dwOldKey; + uint dwOldFlags; + bool result = SafeNativeMethods.GetLayeredWindowAttributes(form.Handle, out dwOldKey, out bOldAlpha, out dwOldFlags); + } + + byte bNewAlpha = (byte)(opacity * 255.0); + uint newExStyle = exStyle; + + if (bNewAlpha != 255) + { + newExStyle |= NativeConstants.WS_EX_LAYERED; + } + + if (newExStyle != exStyle || (newExStyle & NativeConstants.WS_EX_LAYERED) != 0) + { + if (newExStyle != exStyle) + { + SafeNativeMethods.SetWindowLongW(form.Handle, NativeConstants.GWL_EXSTYLE, newExStyle); + } + + if ((newExStyle & NativeConstants.WS_EX_LAYERED) != 0) + { + SafeNativeMethods.SetLayeredWindowAttributes(form.Handle, 0, bNewAlpha, NativeConstants.LWA_ALPHA); + } + } + + GC.KeepAlive(form); + } + + /// + /// This WndProc implements click-through functionality. Some controls (MenuStrip, ToolStrip) will not + /// recognize a click unless the form they are hosted in is active. So the first click will activate the + /// form and then a second is required to actually make the click happen. + /// + /// The Message that was passed to your WndProc. + /// true if the message was processed, false if it was not + /// + /// You should first call base.WndProc(), and then call this method. This method is only intended to + /// change a return value, not to change actual processing before that. + /// + internal static bool ClickThroughWndProc(ref Message m) + { + bool returnVal = false; + + if (m.Msg == NativeConstants.WM_MOUSEACTIVATE) + { + if (m.Result == (IntPtr)NativeConstants.MA_ACTIVATEANDEAT) + { + m.Result = (IntPtr)NativeConstants.MA_ACTIVATE; + returnVal = true; + } + } + + return returnVal; + } + + public static bool IsOurAppActive + { + get + { + foreach (Form form in Application.OpenForms) + { + if (form == Form.ActiveForm) + { + return true; + } + } + + return false; + } + } + + private static VisualStyleClass DetermineVisualStyleClass() + { + return Do.TryCatch(DetermineVisualStyleClassImpl, ex => VisualStyleClass.Other); + } + + private static VisualStyleClass DetermineVisualStyleClassImpl() + { + VisualStyleClass vsClass; + + if (!VisualStyleInformation.IsSupportedByOS) + { + vsClass = VisualStyleClass.Classic; + } + else if (!VisualStyleInformation.IsEnabledByUser) + { + vsClass = VisualStyleClass.Classic; + } + else if (0 == string.Compare(VisualStyleInformation.Author, "MSX", StringComparison.InvariantCulture) && + 0 == string.Compare(VisualStyleInformation.DisplayName, "Aero style", StringComparison.InvariantCulture)) + { + vsClass = VisualStyleClass.Aero; + } + else if (0 == string.Compare(VisualStyleInformation.Company, "Microsoft Corporation", StringComparison.InvariantCulture) && + 0 == string.Compare(VisualStyleInformation.Author, "Microsoft Design Team", StringComparison.InvariantCulture)) + { + if (0 == string.Compare(VisualStyleInformation.DisplayName, "Windows XP style", StringComparison.InvariantCulture) || // Luna + 0 == string.Compare(VisualStyleInformation.DisplayName, "Zune Style", StringComparison.InvariantCulture) || // Zune + 0 == string.Compare(VisualStyleInformation.DisplayName, "Media Center style", StringComparison.InvariantCulture)) // Royale + { + vsClass = VisualStyleClass.Luna; + } + else + { + vsClass = VisualStyleClass.Other; + } + } + else + { + vsClass = VisualStyleClass.Other; + } + + return vsClass; + } + + public static VisualStyleClass VisualStyleClass + { + get + { + return DetermineVisualStyleClass(); + } + } + + public static void EnableShield(Button button, bool enableShield) + { + IntPtr hWnd = button.Handle; + + SafeNativeMethods.SendMessageW( + hWnd, + NativeConstants.BCM_SETSHIELD, + IntPtr.Zero, + enableShield ? new IntPtr(1) : IntPtr.Zero); + + GC.KeepAlive(button); + } + + // TODO: get rid of this somehow! (this will happen when Layers window is rewritten, post-3.0) + public static bool HideHorizontalScrollBar(Control c) + { + return SafeNativeMethods.ShowScrollBar(c.Handle, NativeConstants.SB_HORZ, false); + } + + public static void RestoreWindow(IWin32Window window) + { + IntPtr hWnd = window.Handle; + SafeNativeMethods.ShowWindow(hWnd, NativeConstants.SW_RESTORE); + GC.KeepAlive(window); + } + + public static void ShowComboBox(ComboBox comboBox, bool show) + { + IntPtr hWnd = comboBox.Handle; + + SafeNativeMethods.SendMessageW( + hWnd, + NativeConstants.CB_SHOWDROPDOWN, + show ? new IntPtr(1) : IntPtr.Zero, + IntPtr.Zero); + + GC.KeepAlive(comboBox); + } + + /// + /// Disables the system menu "Close" menu command, as well as the "X" close button on the window title bar. + /// + /// + /// Note to implementors: This method may *not* be implemented as a no-op. The purpose is to make it so that + /// calling the Close() method is the only way to close a dialog, which is something that can only be done + /// programmatically. + /// + public static void DisableCloseBox(IWin32Window window) + { + IntPtr hWnd = window.Handle; + IntPtr hMenu = SafeNativeMethods.GetSystemMenu(hWnd, false); + + if (hMenu == IntPtr.Zero) + { + NativeMethods.ThrowOnWin32Error("GetSystemMenu() returned NULL"); + } + + int result = SafeNativeMethods.EnableMenuItem( + hMenu, + NativeConstants.SC_CLOSE, + NativeConstants.MF_BYCOMMAND | NativeConstants.MF_GRAYED); + + bool bResult = SafeNativeMethods.DrawMenuBar(hWnd); + if (!bResult) + { + NativeMethods.ThrowOnWin32Error("DrawMenuBar returned FALSE"); + } + + GC.KeepAlive(window); + } + + internal static void InvokeThroughModalTrampoline(IWin32Window owner, Procedure invokeMe) + { + using (Form modalityFix = new Form()) + { + modalityFix.ShowInTaskbar = false; + modalityFix.TransparencyKey = modalityFix.BackColor; + UI.SetFormOpacity(modalityFix, 0); + modalityFix.ControlBox = false; + modalityFix.FormBorderStyle = FormBorderStyle.None; + + Control ownerAsControl = owner as Control; + if (ownerAsControl != null) + { + Form ownerForm = ownerAsControl.FindForm(); + + if (ownerForm != null) + { + Rectangle clientRect = ownerForm.RectangleToScreen(ownerForm.ClientRectangle); + + modalityFix.Icon = ownerForm.Icon; + modalityFix.Location = clientRect.Location; + modalityFix.Size = clientRect.Size; + modalityFix.StartPosition = FormStartPosition.Manual; + } + } + + modalityFix.Shown += + delegate(object sender, EventArgs e) + { + invokeMe(modalityFix); + modalityFix.Close(); + }; + + modalityFix.ShowDialog(owner); + GC.KeepAlive(modalityFix); + } + } + } +} diff --git a/src/SystemLayer/UserSessions.cs b/src/SystemLayer/UserSessions.cs new file mode 100644 index 0000000..dc84f57 --- /dev/null +++ b/src/SystemLayer/UserSessions.cs @@ -0,0 +1,149 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Encapsulates information and events about the current user session. + /// This relates to Terminal Services in Windows. + /// + public static class UserSessions + { + private static OurControl messageControl; + private static bool lastRemoteSessionValue; + private static EventHandler sessionChanged; + private static int sessionChangedCount; + private static object lockObject = new object(); + + private sealed class OurControl + : Control + { + public event EventHandler WmWtSessionChange; + + private void OnWmWtSessionChange() + { + if (WmWtSessionChange != null) + { + WmWtSessionChange(this, EventArgs.Empty); + } + } + + protected override void WndProc(ref Message m) + { + switch (m.Msg) + { + case NativeConstants.WM_WTSSESSION_CHANGE: + OnWmWtSessionChange(); + break; + + default: + base.WndProc(ref m); + break; + } + } + } + + private static void OnSessionChanged() + { + if (sessionChanged != null) + { + sessionChanged(null, EventArgs.Empty); + } + } + + /// + /// Occurs when the user changes between sessions. This event will only be + /// raised when the value returned by IsRemote() changes. + /// + /// + /// For example, if the user is currently logged in at the console, and then + /// switches to a remote session (they use Remote Desktop from another computer), + /// then this event will be raised. + /// Note to implementors: This may be implemented as a no-op. + /// + public static event EventHandler SessionChanged + { + add + { + lock (lockObject) + { + sessionChanged += value; + ++sessionChangedCount; + + if (sessionChangedCount == 1) + { + messageControl = new OurControl(); + messageControl.CreateControl(); // force the HWND to be created + messageControl.WmWtSessionChange += new EventHandler(SessionStrobeHandler); + + SafeNativeMethods.WTSRegisterSessionNotification(messageControl.Handle, NativeConstants.NOTIFY_FOR_ALL_SESSIONS); + lastRemoteSessionValue = IsRemote; + } + } + } + + remove + { + lock (lockObject) + { + sessionChanged -= value; + int decremented = Interlocked.Decrement(ref sessionChangedCount); + + if (decremented == 0) + { + try + { + SafeNativeMethods.WTSUnRegisterSessionNotification(messageControl.Handle); + } + + catch (EntryPointNotFoundException) + { + } + + messageControl.Dispose(); + messageControl = null; + } + } + } + } + + /// + /// Determines whether the user is running within a remoted session (Terminal Server, Remote Desktop). + /// + /// + /// true if we're running in a remote session, false otherwise. + /// + /// + /// You can use this to optimize the presentation of visual elements. Remote sessions + /// are often bandwidth limited and less suitable for complex drawing. + /// Note to implementors: This may be implemented as a no op; in this case, always return false. + /// + public static bool IsRemote + { + get + { + return 0 != SafeNativeMethods.GetSystemMetrics(NativeConstants.SM_REMOTESESSION); + } + } + + private static void SessionStrobeHandler(object sender, EventArgs e) + { + if (IsRemote != lastRemoteSessionValue) + { + lastRemoteSessionValue = IsRemote; + OnSessionChanged(); + } + } + } +} diff --git a/src/SystemLayer/VirtualFolderName.cs b/src/SystemLayer/VirtualFolderName.cs new file mode 100644 index 0000000..bc5ced8 --- /dev/null +++ b/src/SystemLayer/VirtualFolderName.cs @@ -0,0 +1,25 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet.SystemLayer +{ + public enum VirtualFolderName + { + UserDesktop, + UserDocuments, + UserPictures, + UserLocalAppData, + UserRoamingAppData, + SystemProgramFiles, + } +} diff --git a/src/SystemLayer/VistaFileDialog.cs b/src/SystemLayer/VistaFileDialog.cs new file mode 100644 index 0000000..d7ee6b7 --- /dev/null +++ b/src/SystemLayer/VistaFileDialog.cs @@ -0,0 +1,319 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Net; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal abstract class VistaFileDialog + : IFileDialog + { + private DialogResult dialogResult = DialogResult.None; + private NativeInterfaces.IFileDialog fileDialog; + private NativeInterfaces.IFileDialogEvents fileDialogEvents; + private IFileDialogUICallbacks uiCallbacks = null; + private string initialDirectory = null; + private string filter = null; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + protected NativeInterfaces.IFileDialogEvents FileDialogEvents + { + get + { + return this.fileDialogEvents; + } + + set + { + if (this.fileDialogEvents == null) + { + this.fileDialogEvents = value; + } + else + { + throw new InvalidOperationException("May only set this property once"); + } + } + } + + protected IFileDialogUICallbacks FileDialogUICallbacks + { + get + { + return this.uiCallbacks; + } + } + + protected NativeInterfaces.IFileDialog FileDialog + { + get + { + return this.fileDialog; + } + } + + protected void SetOptions(NativeConstants.FOS flags, bool enable) + { + if (enable) + { + EnableOption(flags); + } + else + { + DisableOption(flags); + } + } + + protected void EnableOption(NativeConstants.FOS flags) + { + NativeConstants.FOS oldOptions; + this.fileDialog.GetOptions(out oldOptions); + NativeConstants.FOS newOptions = oldOptions | flags; + this.fileDialog.SetOptions(newOptions); + } + + protected void DisableOption(NativeConstants.FOS flags) + { + NativeConstants.FOS oldOptions; + this.fileDialog.GetOptions(out oldOptions); + NativeConstants.FOS newOptions = oldOptions & ~flags; + this.fileDialog.SetOptions(newOptions); + } + + protected bool GetOptions(NativeConstants.FOS flags) + { + NativeConstants.FOS options; + this.fileDialog.GetOptions(out options); + NativeConstants.FOS masked = options & flags; + return masked == flags; + } + + private NativeInterfaces.IShellItem GetShellItem(string path) + { + Guid iid_IShellItem = new Guid(NativeConstants.IID_IShellItem); + IntPtr ppv = IntPtr.Zero; + NativeMethods.SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid_IShellItem, out ppv); + object iUnknown = Marshal.GetObjectForIUnknown(ppv); + NativeInterfaces.IShellItem shellItem = (NativeInterfaces.IShellItem)iUnknown; + return shellItem; + } + + public bool CheckPathExists + { + get + { + return GetOptions(NativeConstants.FOS.FOS_PATHMUSTEXIST); + } + + set + { + SetOptions(NativeConstants.FOS.FOS_PATHMUSTEXIST, value); + } + } + + public bool DereferenceLinks + { + get + { + return !this.GetOptions(NativeConstants.FOS.FOS_NODEREFERENCELINKS); + } + + set + { + SetOptions(NativeConstants.FOS.FOS_NODEREFERENCELINKS, !value); + } + } + + public string Filter + { + get + { + return this.filter; + } + + set + { + string[] split = value.Split('|'); + + if ((split.Length % 2) != 0) + { + throw new ArgumentException(); + } + + NativeStructs.COMDLG_FILTERSPEC[] filterSpecs = new NativeStructs.COMDLG_FILTERSPEC[split.Length / 2]; + + for (int i = 0; i < filterSpecs.Length; ++i) + { + NativeStructs.COMDLG_FILTERSPEC filterSpec = new NativeStructs.COMDLG_FILTERSPEC(); + filterSpec.pszName = split[i * 2]; + filterSpec.pszSpec = split[(i * 2) + 1]; + filterSpecs[i] = filterSpec; + } + + this.FileDialog.SetFileTypes((uint)filterSpecs.Length, filterSpecs); + this.filter = value; + } + } + + public int FilterIndex + { + get + { + uint index = 0; + this.FileDialog.GetFileTypeIndex(out index); + return (int)index; + } + + set + { + this.FileDialog.SetFileTypeIndex((uint)value); + } + } + + public string InitialDirectory + { + get + { + return this.initialDirectory; + } + + set + { + this.initialDirectory = value; + } + } + + public string Title + { + set + { + this.fileDialog.SetTitle(value); + } + } + + protected virtual void OnBeforeShow() + { + NativeInterfaces.IShellItem shellItem = null; + + try + { + shellItem = GetShellItem(this.initialDirectory); + this.fileDialog.SetDefaultFolder(shellItem); + } + + catch (Exception) + { + // Do nothing. + } + + finally + { + if (shellItem != null) + { + try + { + Marshal.ReleaseComObject(shellItem); + } + + catch (ArgumentException) + { + } + + shellItem = null; + } + } + } + + protected virtual void OnAfterShow() + { + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uiCallbacks")] + public DialogResult ShowDialog(IWin32Window owner, IFileDialogUICallbacks uiCallbacks) + { + if (uiCallbacks == null) + { + throw new ArgumentNullException("uiCallbacks"); + } + + this.uiCallbacks = uiCallbacks; + + int hrCCD = FileDialog.ClearClientData(); + + uint dwCookie = 0xdeadbeef; + + if (this.fileDialogEvents != null) + { + FileDialog.Advise(this.fileDialogEvents, out dwCookie); + } + + OnBeforeShow(); + + int hr = 0; + + UI.InvokeThroughModalTrampoline( + owner, + delegate(IWin32Window modalOwner) + { + hr = this.fileDialog.Show(modalOwner.Handle); + GC.KeepAlive(modalOwner); + }); + + DialogResult result; + + if (hr >= 0) + { + result = DialogResult.OK; + } + else + { + result = DialogResult.Cancel; + } + + this.dialogResult = result; + + if (this.fileDialogEvents != null) + { + FileDialog.Unadvise(dwCookie); + } + + OnAfterShow(); + this.uiCallbacks = null; + + GC.KeepAlive(owner); + return result; + } + + protected VistaFileDialog(NativeInterfaces.IFileDialog fileDialog) + { + this.fileDialog = fileDialog; + } + + ~VistaFileDialog() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + } +} diff --git a/src/SystemLayer/VistaFileOpenDialog.cs b/src/SystemLayer/VistaFileOpenDialog.cs new file mode 100644 index 0000000..db1985e --- /dev/null +++ b/src/SystemLayer/VistaFileOpenDialog.cs @@ -0,0 +1,524 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal sealed class VistaFileOpenDialog + : VistaFileDialog, + IFileOpenDialog, + NativeInterfaces.IFileDialogEvents + { + private CancelableTearOff cancelSink = null; + private sealed class CancelableTearOff + : ICancelable + { + private volatile bool canceled = false; + public bool Canceled + { + get + { + return this.canceled; + } + } + + public void RequestCancel() + { + this.canceled = true; + } + } + + private string[] fileNames = null; + + private NativeInterfaces.IFileOpenDialog FileOpenDialog + { + get + { + return this.FileDialog as NativeInterfaces.IFileOpenDialog; + } + } + + public bool CheckFileExists + { + get + { + return GetOptions(NativeConstants.FOS.FOS_FILEMUSTEXIST); + } + + set + { + SetOptions(NativeConstants.FOS.FOS_FILEMUSTEXIST, value); + } + } + + public bool Multiselect + { + get + { + return GetOptions(NativeConstants.FOS.FOS_ALLOWMULTISELECT); + } + + set + { + SetOptions(NativeConstants.FOS.FOS_ALLOWMULTISELECT, value); + } + } + + public string[] FileNames + { + get + { + return (string[])(this.fileNames ?? new string[0]).Clone(); + } + + private set + { + this.fileNames = (string[])value.Clone(); + } + } + + public VistaFileOpenDialog() + : base(new NativeInterfaces.NativeFileOpenDialog()) + { + this.FileDialogEvents = this; + } + + int NativeInterfaces.IFileDialogEvents.OnFileOk(NativeInterfaces.IFileDialog pfd) + { + int hr = NativeConstants.S_OK; + + NativeInterfaces.IShellItemArray results = null; + FileOpenDialog.GetResults(out results); + + uint count = 0; + results.GetCount(out count); + + List items = new List(); + List needLocalCopy = new List(); + List cannotCopy = new List(); + List localPathNames = new List(); + + for (uint i = 0; i < count; ++i) + { + NativeInterfaces.IShellItem item = null; + results.GetItemAt(i, out item); + items.Add(item); + } + + foreach (NativeInterfaces.IShellItem item in items) + { + // If it's a file system object, nothing special needs to be done. + NativeConstants.SFGAO sfgaoAttribs; + item.GetAttributes((NativeConstants.SFGAO)0xffffffff, out sfgaoAttribs); + + if ((sfgaoAttribs & NativeConstants.SFGAO.SFGAO_FILESYSTEM) == NativeConstants.SFGAO.SFGAO_FILESYSTEM) + { + string pathName = null; + item.GetDisplayName(NativeConstants.SIGDN.SIGDN_FILESYSPATH, out pathName); + + localPathNames.Add(pathName); + } + else if ((sfgaoAttribs & NativeConstants.SFGAO.SFGAO_STREAM) == NativeConstants.SFGAO.SFGAO_STREAM) + { + needLocalCopy.Add(item); + } + else + { + cannotCopy.Add(item); + } + } + + Marshal.ReleaseComObject(results); + results = null; + + if (needLocalCopy.Count > 0) + { + IntPtr hwnd = IntPtr.Zero; + NativeInterfaces.IOleWindow oleWindow = (NativeInterfaces.IOleWindow)pfd; + oleWindow.GetWindow(out hwnd); + Win32Window win32Window = new Win32Window(hwnd, oleWindow); + + IFileTransferProgressEvents progressEvents = this.FileDialogUICallbacks.CreateFileTransferProgressEvents(); + + ThreadStart copyThreadProc = + delegate() + { + try + { + progressEvents.SetItemCount(needLocalCopy.Count); + + for (int i = 0; i < needLocalCopy.Count; ++i) + { + NativeInterfaces.IShellItem item = needLocalCopy[i]; + + string pathName = null; + + progressEvents.SetItemOrdinal(i); + CopyResult result = CreateLocalCopy(item, progressEvents, out pathName); + + if (result == CopyResult.Success) + { + localPathNames.Add(pathName); + } + else if (result == CopyResult.Skipped) + { + // do nothing + } + else if (result == CopyResult.CancelOperation) + { + hr = NativeConstants.S_FALSE; + break; + } + else + { + throw new InvalidEnumArgumentException(); + } + } + } + + finally + { + OperationResult result; + + if (hr == NativeConstants.S_OK) + { + result = OperationResult.Finished; + } + else + { + result = OperationResult.Canceled; + } + + progressEvents.EndOperation(result); + } + }; + + Thread copyThread = new Thread(copyThreadProc); + copyThread.SetApartmentState(ApartmentState.STA); + + EventHandler onUIShown = + delegate(object sender, EventArgs e) + { + copyThread.Start(); + }; + + this.cancelSink = new CancelableTearOff(); + progressEvents.BeginOperation(win32Window, onUIShown, cancelSink); + this.cancelSink = null; + copyThread.Join(); + + Marshal.ReleaseComObject(oleWindow); + oleWindow = null; + } + + this.FileNames = localPathNames.ToArray(); + + // If they selected a bunch of files, and then they all errored or something, then don't proceed. + if (this.FileNames.Length == 0) + { + hr = NativeConstants.S_FALSE; + } + + foreach (NativeInterfaces.IShellItem item in items) + { + Marshal.ReleaseComObject(item); + } + + items.Clear(); + items = null; + + GC.KeepAlive(pfd); + return hr; + } + + private enum CopyResult + { + Success, + Skipped, + CancelOperation + } + + // Returns true if the item copied successfully, false if it didn't (error or skipped) + private CopyResult CreateLocalCopy( + NativeInterfaces.IShellItem item, + IFileTransferProgressEvents progressEvents, + out string pathNameResult) + { + CopyResult returnResult; + WorkItemResult itemResult; + + string displayName = null; + item.GetDisplayName(NativeConstants.SIGDN.SIGDN_NORMALDISPLAY, out displayName); + progressEvents.SetItemInfo(displayName); + + progressEvents.BeginItem(); + + while (true) + { + // Determine whether to copy from HTTP or from IStream. The heuristic we use here is simple: + // if the attributes has SFGAO_CANCOPY, we IStream it. Else, we HTTP it. + NativeConstants.SFGAO attribs; + item.GetAttributes((NativeConstants.SFGAO)0xfffffff, out attribs); + + try + { + if ((attribs & NativeConstants.SFGAO.SFGAO_CANCOPY) == NativeConstants.SFGAO.SFGAO_CANCOPY) + { + CreateLocalCopyFromIStreamSource(item, progressEvents, out pathNameResult); + } + else + { + CreateLocalCopyFromHttpSource(item, progressEvents, out pathNameResult); + } + + returnResult = CopyResult.Success; + itemResult = WorkItemResult.Finished; + break; + } + + catch (OperationCanceledException) + { + returnResult = CopyResult.CancelOperation; + itemResult = WorkItemResult.Skipped; + pathNameResult = null; + break; + } + + catch (Exception ex) + { + WorkItemFailureAction choice = progressEvents.ReportItemFailure(ex); + + if (choice == WorkItemFailureAction.SkipItem) + { + pathNameResult = null; + returnResult = CopyResult.Skipped; + itemResult = WorkItemResult.Skipped; + break; + } + else if (choice == WorkItemFailureAction.RetryItem) + { + continue; + } + else if (choice == WorkItemFailureAction.CancelOperation) + { + pathNameResult = null; + returnResult = CopyResult.CancelOperation; + itemResult = WorkItemResult.Skipped; + break; + } + } + } + + progressEvents.EndItem(itemResult); + + return returnResult; + } + + private void CreateLocalCopyFromHttpSource( + NativeInterfaces.IShellItem item, + IFileTransferProgressEvents progressEvents, + out string pathNameResult) + { + string url = null; + item.GetDisplayName(NativeConstants.SIGDN.SIGDN_URL, out url); + + Uri uri = new Uri(url); + + string pathName = FileSystem.GetTempPathName(url); + + WebRequest webRequest = WebRequest.Create(uri); + webRequest.Timeout = 5000; + + using (WebResponse webResponse = webRequest.GetResponse()) + { + VerifyNotCanceled(); + + using (Stream uriStream = webResponse.GetResponseStream()) + { + VerifyNotCanceled(); + + using (FileStream outStream = new FileStream(pathName, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + VerifyNotCanceled(); + + const int bufSize = 512; + long length = webResponse.ContentLength; + long bytesLeft = length; + byte[] buffer = new byte[bufSize]; + + progressEvents.SetItemWorkTotal(length); + + while (bytesLeft > 0) + { + int amtRead = uriStream.Read(buffer, 0, buffer.Length); + VerifyNotCanceled(); + + outStream.Write(buffer, 0, amtRead); + VerifyNotCanceled(); + + bytesLeft -= amtRead; + + progressEvents.SetItemWorkProgress(length - bytesLeft); + VerifyNotCanceled(); + + } + } + } + } + + pathNameResult = pathName; + } + + private unsafe void CreateLocalCopyFromIStreamSource( + NativeInterfaces.IShellItem item, + IFileTransferProgressEvents progressEvents, + out string pathNameResult) + { + string fileName = null; + item.GetDisplayName(NativeConstants.SIGDN.SIGDN_NORMALDISPLAY, out fileName); + + string pathName = FileSystem.GetTempPathName(fileName); + + Guid bhidStream = NativeConstants.BHID_Stream; + Guid iid_IStream = new Guid(NativeConstants.IID_IStream); + NativeInterfaces.IStream iStream = null; + item.BindToHandler(IntPtr.Zero, ref bhidStream, ref iid_IStream, out iStream); + + try + { + VerifyNotCanceled(); + + NativeStructs.STATSTG statstg = new NativeStructs.STATSTG(); + iStream.Stat(out statstg, NativeConstants.STATFLAG.STATFLAG_NONAME); + + progressEvents.SetItemWorkTotal((long)statstg.cbSize); + + const int bufSize = 4096; + byte[] buffer = new byte[bufSize]; + + fixed (void* pbBuffer = buffer) + { + IntPtr pbBuffer2 = new IntPtr(pbBuffer); + + ulong qwBytesLeft = statstg.cbSize; + + using (FileStream localFile = new FileStream(pathName, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + VerifyNotCanceled(); + + // NOTE: We do not call VerifyNotCanceled() during any individual item. This is because, while testing + // this, it was determined that oftentimes the transfer gets very confused if we just stop + // calling Read() and jump straight to Marshal.ReleaseComObject(). By "confused" I mean that + // the "Canceling..." text would remain on the progress dialog for up to a minute, and then + // the blinking light on the camera would continue indefinitely and you wouldn't be able to + // use the camera again until you unplugged it and plugged it back in. + while (qwBytesLeft > 0) + { + uint wantToRead = (uint)Math.Min(qwBytesLeft, bufSize); + uint amtRead = 0; + + iStream.Read(pbBuffer2, wantToRead, out amtRead); + + if (amtRead > qwBytesLeft) + { + throw new InvalidOperationException("IStream::Read() reported that more bytes were read than were in the file"); + } + + qwBytesLeft -= amtRead; + localFile.Write(buffer, 0, (int)amtRead); + progressEvents.SetItemWorkProgress((long)(statstg.cbSize - qwBytesLeft)); + } + } + + VerifyNotCanceled(); + } + } + + finally + { + if (iStream != null) + { + try + { + Marshal.ReleaseComObject(iStream); + } + + catch (Exception) + { + } + + iStream = null; + } + } + + pathNameResult = pathName; + } + + int NativeInterfaces.IFileDialogEvents.OnFolderChanging( + NativeInterfaces.IFileDialog pfd, + NativeInterfaces.IShellItem psiFolder) + { + return NativeConstants.E_NOTIMPL; + } + + void NativeInterfaces.IFileDialogEvents.OnFolderChange(NativeInterfaces.IFileDialog pfd) + { + } + + void NativeInterfaces.IFileDialogEvents.OnSelectionChange(NativeInterfaces.IFileDialog pfd) + { + } + + void NativeInterfaces.IFileDialogEvents.OnShareViolation( + NativeInterfaces.IFileDialog pfd, + NativeInterfaces.IShellItem psi, + out NativeConstants.FDE_SHAREVIOLATION_RESPONSE pResponse) + { + pResponse = NativeConstants.FDE_SHAREVIOLATION_RESPONSE.FDESVR_DEFAULT; + } + + void NativeInterfaces.IFileDialogEvents.OnTypeChange(NativeInterfaces.IFileDialog pfd) + { + } + + void NativeInterfaces.IFileDialogEvents.OnOverwrite( + NativeInterfaces.IFileDialog pfd, + NativeInterfaces.IShellItem psi, + out NativeConstants.FDE_OVERWRITE_RESPONSE pResponse) + { + pResponse = NativeConstants.FDE_OVERWRITE_RESPONSE.FDEOR_DEFAULT; + } + + /// + /// If the operation has been canceled, then this throws OperationCanceledException. + /// + /// + /// The general guideline for when to call this method is: (1) right after calling any + /// method that may take awhile to complete, such as Read() or Write(), and (2) right + /// after calling ReportItemProgress(). + /// + private void VerifyNotCanceled() + { + if (this.cancelSink != null && this.cancelSink.Canceled) + { + throw new OperationCanceledException(); + } + } + } +} diff --git a/src/SystemLayer/VistaFileSaveDialog.cs b/src/SystemLayer/VistaFileSaveDialog.cs new file mode 100644 index 0000000..399742b --- /dev/null +++ b/src/SystemLayer/VistaFileSaveDialog.cs @@ -0,0 +1,351 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace PaintDotNet.SystemLayer +{ + internal sealed class VistaFileSaveDialog + : VistaFileDialog, + IFileSaveDialog, + NativeInterfaces.IFileDialogEvents + { + private bool addExtension = true; + private bool overwritePrompt = false; + + private NativeInterfaces.IFileSaveDialog FileSaveDialog + { + get + { + return this.FileDialog as NativeInterfaces.IFileSaveDialog; + } + } + + public bool AddExtension + { + get + { + return this.addExtension; + } + + set + { + this.addExtension = value; + } + } + + private string FileNameCore + { + get + { + NativeInterfaces.IShellItem shellItem = null; + string path; + + try + { + int hr = this.FileSaveDialog.GetResult(out shellItem); + + if (NativeMethods.SUCCEEDED(hr)) + { + shellItem.GetDisplayName(NativeConstants.SIGDN.SIGDN_FILESYSPATH, out path); + } + else + { + this.FileSaveDialog.GetFileName(out path); + } + } + + catch (Exception) + { + this.FileSaveDialog.GetFileName(out path); + } + + finally + { + if (shellItem != null) + { + try + { + Marshal.ReleaseComObject(shellItem); + } + + catch (ArgumentException) + { + // Ignore error + } + + shellItem = null; + } + } + + return path; + } + } + + public string FileName + { + get + { + string pathNameCore = FileNameCore; + string pathNameResolved = ResolveName(pathNameCore); + return pathNameResolved; + } + + set + { + this.FileSaveDialog.SetFileName(value); + } + } + + private string ResolveName(string path) + { + string returnPath; + + if (this.addExtension && path != null) + { + string ext = Path.GetExtension(path); + + // If they did not specify an extension, then we should add on the default one for the file type they chose + if (string.IsNullOrEmpty(ext)) + { + int filterIndex = this.FilterIndex; + string allFilters = this.Filter; + string[] filtersArray = allFilters.Split('|'); + string filter = filtersArray[1 + ((filterIndex - 1) * 2)]; + string[] exts = filter.Split(';'); + + string newSpec = exts[0]; + if (newSpec[0] == '*') + { + newSpec = newSpec.Substring(1); + } + + returnPath = Path.ChangeExtension(path, newSpec); + } + else + { + returnPath = path; + } + } + else + { + returnPath = path; + } + + return returnPath; + } + + protected override void OnBeforeShow() + { + try + { + string fileNameCore = FileNameCore; + + if (!string.IsNullOrEmpty(fileNameCore)) + { + string justTheFileName = Path.GetFileName(fileNameCore); + string dir = Path.GetDirectoryName(fileNameCore); + + string fullPathName = Path.GetFullPath(dir); + InitialDirectory = fullPathName; + + FileName = justTheFileName; + } + } + + catch (Exception) + { + } + + SetOptions(NativeConstants.FOS.FOS_FORCEFILESYSTEM, true); + SetOptions(NativeConstants.FOS.FOS_OVERWRITEPROMPT, false); // we handle this ourself + + base.OnBeforeShow(); + } + + public bool OverwritePrompt + { + get + { + return this.overwritePrompt; + } + + set + { + this.overwritePrompt = value; + } + } + + public VistaFileSaveDialog() + : base(new NativeInterfaces.NativeFileSaveDialog()) + { + this.FileDialogEvents = this; + } + + int NativeInterfaces.IFileDialogEvents.OnFileOk(NativeInterfaces.IFileDialog pfd) + { + int hr = NativeConstants.S_OK; + + NativeInterfaces.IShellItem shellItem = null; + + if (NativeMethods.SUCCEEDED(hr)) + { + hr = FileSaveDialog.GetResult(out shellItem); + } + + if (!NativeMethods.SUCCEEDED(hr)) + { + throw Marshal.GetExceptionForHR(hr); + } + + string pathName = null; + + try + { + shellItem.GetDisplayName(NativeConstants.SIGDN.SIGDN_FILESYSPATH, out pathName); + } + + finally + { + if (shellItem != null) + { + try + { + Marshal.ReleaseComObject(shellItem); + } + + catch (Exception) + { + } + + shellItem = null; + } + } + + string pathNameResolved = ResolveName(pathName); + NativeInterfaces.IOleWindow oleWindow = (NativeInterfaces.IOleWindow)pfd; + + try + { + IntPtr hWnd = IntPtr.Zero; + oleWindow.GetWindow(out hWnd); + Win32Window win32Window = new Win32Window(hWnd, oleWindow); + + // File name/path validation + if (hr >= 0) + { + try + { + // Verify that these can be parsed correctly + string fileName = Path.GetFileName(pathNameResolved); + string dirName = Path.GetDirectoryName(pathNameResolved); + } + + catch (Exception ex) + { + if (!FileDialogUICallbacks.ShowError(win32Window, pathNameResolved, ex)) + { + throw; + } + + hr = NativeConstants.S_FALSE; + } + } + + if (hr >= 0) + { + // Overwrite existing file + if (!OverwritePrompt) + { + hr = NativeConstants.S_OK; + } + else if (File.Exists(pathNameResolved)) + { + FileOverwriteAction action = FileDialogUICallbacks.ShowOverwritePrompt(win32Window, pathNameResolved); + + switch (action) + { + case FileOverwriteAction.Cancel: + hr = NativeConstants.S_FALSE; + break; + + case FileOverwriteAction.Overwrite: + hr = NativeConstants.S_OK; + break; + + default: + throw new InvalidEnumArgumentException(); + } + } + } + } + + catch (Exception) + { + } + + finally + { + try + { + Marshal.ReleaseComObject(oleWindow); + } + + catch (Exception) + { + } + + oleWindow = null; + } + + return hr; + } + + int NativeInterfaces.IFileDialogEvents.OnFolderChanging( + NativeInterfaces.IFileDialog pfd, + NativeInterfaces.IShellItem psiFolder) + { + return NativeConstants.E_NOTIMPL; + } + + void NativeInterfaces.IFileDialogEvents.OnFolderChange(NativeInterfaces.IFileDialog pfd) + { + } + + void NativeInterfaces.IFileDialogEvents.OnSelectionChange(NativeInterfaces.IFileDialog pfd) + { + } + + void NativeInterfaces.IFileDialogEvents.OnShareViolation( + NativeInterfaces.IFileDialog pfd, + NativeInterfaces.IShellItem psi, + out NativeConstants.FDE_SHAREVIOLATION_RESPONSE pResponse) + { + pResponse = NativeConstants.FDE_SHAREVIOLATION_RESPONSE.FDESVR_DEFAULT; + } + + void NativeInterfaces.IFileDialogEvents.OnTypeChange(NativeInterfaces.IFileDialog pfd) + { + } + + void NativeInterfaces.IFileDialogEvents.OnOverwrite( + NativeInterfaces.IFileDialog pfd, + NativeInterfaces.IShellItem psi, + out NativeConstants.FDE_OVERWRITE_RESPONSE pResponse) + { + pResponse = NativeConstants.FDE_OVERWRITE_RESPONSE.FDEOR_DEFAULT; + } + } +} diff --git a/src/SystemLayer/VisualStyleClass.cs b/src/SystemLayer/VisualStyleClass.cs new file mode 100644 index 0000000..1e481bb --- /dev/null +++ b/src/SystemLayer/VisualStyleClass.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.SystemLayer +{ + public enum VisualStyleClass + { + Classic, + Luna, // also covers Royale, which is a derivative of Luna + Aero, + Other + } +} diff --git a/src/SystemLayer/WaitHandleArray.cs b/src/SystemLayer/WaitHandleArray.cs new file mode 100644 index 0000000..d5e11fb --- /dev/null +++ b/src/SystemLayer/WaitHandleArray.cs @@ -0,0 +1,128 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Threading; + +namespace PaintDotNet.SystemLayer +{ + /// + /// Encapsulates an array of WaitHandles and methods for waiting on them. + /// This class does not take ownership of the WaitHandles; you must still + /// Dispose() them yourself. + /// + /// + /// This class exists because System.Threading.WaitHandle.Wait[Any|All] will throw an exception + /// in an STA apartment. So we must P/Invoke down to WaitForMultipleObjects(). + /// + public sealed class WaitHandleArray + { + private WaitHandle[] waitHandles; + private IntPtr[] nativeHandles; + + /// + /// The minimum value that may be passed to the constructor for initialization. + /// + public const int MinimumCount = 1; + + /// + /// The maximum value that may be passed to the construct for initialization. + /// + public const int MaximumCount = 64; // WaitForMultipleObjects() can only wait on up to 64 objects at once + + /// + /// Gets or sets the WaitHandle at the specified index. + /// + public WaitHandle this[int index] + { + get + { + return this.waitHandles[index]; + } + + set + { + this.waitHandles[index] = value; + this.nativeHandles[index] = value.SafeWaitHandle.DangerousGetHandle(); + } + } + + /// + /// Gets the length of the array. + /// + public int Length + { + get + { + return this.waitHandles.Length; + } + } + + /// + /// Initializes a new instance of the WaitHandleArray class. + /// + /// The size of the array. + public WaitHandleArray(int count) + { + if (count < 1 || count > 64) + { + throw new ArgumentOutOfRangeException("count", "must be between 1 and 64, inclusive"); + } + + this.waitHandles = new WaitHandle[count]; + this.nativeHandles = new IntPtr[count]; + } + + private uint WaitForAll(uint dwTimeout) + { + return SafeNativeMethods.WaitForMultipleObjects(this.nativeHandles, true, dwTimeout); + } + + /// + /// Waits for all of the WaitHandles to be signaled. + /// + public void WaitAll() + { + WaitForAll(NativeConstants.INFINITE); + } + + public bool AreAllSignaled() + { + return AreAllSignaled(0); + } + + public bool AreAllSignaled(uint msTimeout) + { + uint result = WaitForAll(msTimeout); + + if (result >= NativeConstants.WAIT_OBJECT_0 && result < NativeConstants.WAIT_OBJECT_0 + this.Length) + { + return true; + } + else + { + return false; + } + } + + /// + /// Waits for any of the WaitHandles to be signaled. + /// + /// + /// The index of the first item in the array that completed the wait operation. + /// If this value is outside the bounds of the array, it is an indication of an + /// error. + /// + public int WaitAny() + { + int returnVal = (int)SafeNativeMethods.WaitForMultipleObjects(this.nativeHandles, false, NativeConstants.INFINITE); + return returnVal; + } + } +} diff --git a/src/SystemLayer/WiaInterfaceInProc.cs b/src/SystemLayer/WiaInterfaceInProc.cs new file mode 100644 index 0000000..fe8de60 --- /dev/null +++ b/src/SystemLayer/WiaInterfaceInProc.cs @@ -0,0 +1,180 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + internal sealed class WiaInterfaceInProc + : IWiaInterface + { + public bool IsComponentAvailable + { + get + { + return IsWia2Available(); + } + } + + public bool CanPrint + { + get + { + return IsWia2Available(); + } + } + + public bool CanScan + { + get + { + bool result; + + if (IsWia2Available()) + { + WIA.DeviceManagerClass dmc = new WIA.DeviceManagerClass(); + + if (dmc.DeviceInfos.Count > 0) + { + result = true; + } + else + { + result = false; + } + + Marshal.ReleaseComObject(dmc); + dmc = null; + } + else + { + result = false; + } + + return result; + } + } + + public void Print(Control owner, string fileName) + { + Tracing.Enter(); + + WIA.VectorClass vector = new WIA.VectorClass(); + object tempName_o = (object)fileName; + vector.Add(ref tempName_o, 0); + object vector_o = (object)vector; + WIA.CommonDialogClass cdc = new WIA.CommonDialogClass(); + + // Ok, this looks weird, but here's the story. + // When we show the WIA printing dialog, it is a modal dialog but the way + // it handles itself is that the main window can still be interacted with. + // I don't know why, and it doesn't matter to me except that it causes all + // sorts of other problems (esp. related to the scratch surface.) + // So we show a modal dialog that is effectively invisible so that the user + // cannot interact with the main window while the print dialog is still open. + + Form modal = new Form(); + modal.ShowInTaskbar = false; + modal.TransparencyKey = modal.BackColor; + modal.FormBorderStyle = FormBorderStyle.None; + + modal.Shown += + delegate(object sender, EventArgs e) + { + cdc.ShowPhotoPrintingWizard(ref vector_o); + modal.Close(); + }; + + modal.ShowDialog(owner); + modal = null; + + Marshal.ReleaseComObject(cdc); + cdc = null; + + Tracing.Leave(); + } + + public ScanResult Scan(Control owner, string fileName) + { + ScanResult result; + + WIA.CommonDialogClass cdc = new WIA.CommonDialogClass(); + WIA.ImageFile imageFile = null; + + try + { + imageFile = cdc.ShowAcquireImage(WIA.WiaDeviceType.UnspecifiedDeviceType, + WIA.WiaImageIntent.UnspecifiedIntent, + WIA.WiaImageBias.MaximizeQuality, + "{00000000-0000-0000-0000-000000000000}", + true, + true, + false); + + Marshal.ReleaseComObject(cdc); + cdc = null; + } + + catch (System.Runtime.InteropServices.COMException) + { + result = ScanResult.DeviceBusy; + imageFile = null; + } + + if (imageFile != null) + { + imageFile.SaveFile(fileName); + result = ScanResult.Success; + } + else + { + result = ScanResult.UserCancelled; + } + + return result; + } + + // Have to split this in to two functions because the WIA DLL is resolved + // at the time a function is entered that depends on it (IsWia2AvailableImpl). + // This way we can avoid a runtime error and maintain the semantics of + // IsWia2Available(). + + private static bool IsWia2Available() + { + try + { + return IsWia2AvailableImpl(); + } + + catch + { + return false; + } + } + + private static bool IsWia2AvailableImpl() + { + try + { + WIA.DeviceManagerClass dmc = new WIA.DeviceManagerClass(); + Marshal.ReleaseComObject(dmc); + dmc = null; + + return true; + } + + catch + { + return false; + } + } + } +} diff --git a/src/SystemLayer/WiaInterfaceOutOfProc.cs b/src/SystemLayer/WiaInterfaceOutOfProc.cs new file mode 100644 index 0000000..dfc5d07 --- /dev/null +++ b/src/SystemLayer/WiaInterfaceOutOfProc.cs @@ -0,0 +1,201 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.SystemLayer +{ + // Because wiaaut.dll is not available in 64-bit form for Windows Server 2003 or Windows XP, + // we must out-of-proc this stuff. + + internal sealed class WiaInterfaceOutOfProc + : IWiaInterface + { + private const string wiaProxy32ExeName = "WiaProxy32.exe"; + + public int CallWiaProxy32(string args, bool spinEvents) + { + string ourPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); + string proxyPath = Path.Combine(ourPath, wiaProxy32ExeName); + ProcessStartInfo psi = new ProcessStartInfo(proxyPath, args); + + psi.CreateNoWindow = true; + psi.UseShellExecute = false; + + int exitCode = -1; + + try + { + Process process = Process.Start(psi); + + // Can't just use process.WaitForExit() because then the Paint.NET UI + // will not repaint and it'll look weird because of that. + while (!process.HasExited) + { + if (spinEvents) + { + Application.DoEvents(); + } + + Thread.Sleep(10); + } + + exitCode = process.ExitCode; + process.Dispose(); + } + + catch (Exception) + { + } + + return exitCode; + } + + /// + /// Gets whether or not the scanning and printing features are available without + /// taking into account whether a scanner or printer are actually connected. + /// + public bool IsComponentAvailable + { + get + { + return 1 == CallWiaProxy32("IsComponentAvailable 1", false); + } + } + + /// + /// Gets whether printing is possible. This does not take into account whether a printer + /// is actually connected or available, just that it is possible to print (it is possible + /// that the printing UI has a facility for adding or loading a new printer). + /// + public bool CanPrint + { + get + { + return 1 == CallWiaProxy32("CanPrint 1", false); + } + } + + /// + /// Gets whether scanning is possible. The user must have a scanner connect for this to return true. + /// + /// + /// This also covers image acquisition from, say, a camera. + /// + public bool CanScan + { + get + { + return 1 == CallWiaProxy32("CanScan 1", false); + } + } + + /// + /// Presents a user interface for printing the given image. + /// + /// The parent/owner control for the UI that will be presented for printing. + /// The name of a file containing a bitmap (.BMP) to print. + public void Print(Control owner, string fileName) + { + // Disable the entire UI, otherwise it's possible to close PDN while the + // print wizard is active! And then it crashes. + Form ownedForm = owner.FindForm(); + bool[] ownedFormsEnabled = null; + + if (ownedForm != null) + { + ownedFormsEnabled = new bool[ownedForm.OwnedForms.Length]; + + for (int i = 0; i < ownedForm.OwnedForms.Length; ++i) + { + ownedFormsEnabled[i] = ownedForm.OwnedForms[i].Enabled; + ownedForm.OwnedForms[i].Enabled = false; + } + + ownedForm.Enabled = false; + } + + CallWiaProxy32("Print \"" + fileName + "\"", true); + + if (ownedForm != null) + { + for (int i = 0; i < ownedForm.OwnedForms.Length; ++i) + { + ownedForm.OwnedForms[i].Enabled = ownedFormsEnabled[i]; + } + + ownedForm.Enabled = true; + ownedForm.Activate(); + } + } + + /// + /// Presents a user interface for scanning. + /// + /// + /// The filename of where to stored the scanned/acquired image. Only valid if the return value is ScanResult.Success. + /// + /// The result of the scanning operation. + public ScanResult Scan(Control owner, string fileName) + { + if (!CanScan) + { + throw new InvalidOperationException("Scanning is not available"); + } + + // Disable the entire UI, otherwise it's possible to close PDN while the + // print wizard is active! And then it crashes. + Form ownedForm = owner.FindForm(); + bool[] ownedFormsEnabled = null; + + if (ownedForm != null) + { + ownedFormsEnabled = new bool[ownedForm.OwnedForms.Length]; + + for (int i = 0; i < ownedForm.OwnedForms.Length; ++i) + { + ownedFormsEnabled[i] = ownedForm.OwnedForms[i].Enabled; + ownedForm.OwnedForms[i].Enabled = false; + } + + owner.FindForm().Enabled = false; + } + + // Do scanning + int retVal = CallWiaProxy32("Scan \"" + fileName + "\"", true); + + // Un-disable everything + if (ownedForm != null) + { + for (int i = 0; i < ownedForm.OwnedForms.Length; ++i) + { + ownedForm.OwnedForms[i].Enabled = ownedFormsEnabled[i]; + } + + owner.FindForm().Enabled = true; + } + + owner.FindForm().Activate(); + + // Marshal the return code + ScanResult result = (ScanResult)retVal; + + if (!Enum.IsDefined(typeof(ScanResult), result)) + { + throw new ApplicationException("WiaProxy32 returned an error: " + retVal.ToString()); + } + + return result; + } + } +} diff --git a/src/TextAlignment.cs b/src/TextAlignment.cs new file mode 100644 index 0000000..b003438 --- /dev/null +++ b/src/TextAlignment.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; + +namespace PaintDotNet +{ + internal enum TextAlignment + { + Left, + Center, + Right + } +} \ No newline at end of file diff --git a/src/ToleranceSliderControl.cs b/src/ToleranceSliderControl.cs new file mode 100644 index 0000000..43a3afb --- /dev/null +++ b/src/ToleranceSliderControl.cs @@ -0,0 +1,248 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ToleranceSliderControl + : Control + { + private bool tracking = false; + private bool hovering = false; + private bool isValid; + private string toleranceText; + private string percentageFormat; + + private float tolerance; + + public float Tolerance + { + get + { + return tolerance; + } + set + { + if (tolerance != value) + { + tolerance = Utility.Clamp(value, 0, 1); + OnToleranceChanged(); + } + } + } + + public EventHandler ToleranceChanged; + protected void OnToleranceChanged() + { + this.isValid = false; + this.Invalidate(); + this.Update(); + if (ToleranceChanged != null) + { + ToleranceChanged(this, EventArgs.Empty); + } + } + + public void PerformToleranceChanged() + { + OnToleranceChanged(); + } + + protected Bitmap buffer = null; + protected Graphics bufferGraphics = null; + + protected void UpdateBitmap() + { + this.Invalidate(); + + if (buffer == null || buffer.Width != this.ClientSize.Width || buffer.Height != this.ClientSize.Height) + { + if (buffer != null) + { + buffer.Dispose(); + buffer = null; + } + + buffer = new Bitmap(this.ClientSize.Width, this.ClientSize.Height); + + if (bufferGraphics != null) + { + bufferGraphics.Dispose(); + bufferGraphics = null; + } + + bufferGraphics = Graphics.FromImage(buffer); + } + + bufferGraphics.Clear(this.BackColor); + + using (LinearGradientBrush lgb = new LinearGradientBrush(this.ClientRectangle, Color.Black, Color.White, 0, false)) + { + bufferGraphics.FillRectangle(lgb, 0, 0, ClientSize.Width, ClientSize.Height); + } + + bufferGraphics.FillRectangle(Brushes.DarkBlue, 0.0f, 0.0f, ClientRectangle.Width * tolerance, this.ClientRectangle.Height); + bufferGraphics.DrawRectangle(hovering ? Pens.White : Pens.Black, 0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1); + bufferGraphics.SmoothingMode = SmoothingMode.HighQuality; + bufferGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; + + using (Font ourFont = new Font(this.Font.FontFamily, 8.0f, this.Font.Style)) + { + Brush textBrush; + + if (hovering) + { + textBrush = Brushes.White; + } + else + { + textBrush = Brushes.White; + } + + int number = (int)(tolerance * 100); + string text = string.Format(percentageFormat, number); + + bufferGraphics.DrawString(text, ourFont, textBrush, 2, 1); + } + + this.isValid = true; + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + if (!isValid) + { + UpdateBitmap(); + } + + if (buffer != null) + { + Rectangle bounds = new Rectangle(0, 0, buffer.Width, buffer.Height); + e.Graphics.DrawImage(buffer, bounds, bounds, GraphicsUnit.Pixel); + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (!tracking && (e.Button & MouseButtons.Left) == MouseButtons.Left) + { + tracking = true; + isValid = false; + this.Invalidate(); + this.Update(); + OnMouseMove(e); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove (e); + + if (tracking) + { + Tolerance = (float)e.X / this.ClientSize.Width; + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + if (tracking && (e.Button & MouseButtons.Left) == MouseButtons.Left) + { + tracking = false; + isValid = false; + this.Invalidate(); + this.Update(); + } + } + + protected override void OnMouseEnter(EventArgs e) + { + base.OnMouseEnter(e); + this.hovering = true; + this.UpdateBitmap(); + this.Update(); + } + + protected override void OnMouseLeave(EventArgs e) + { + base.OnMouseLeave(e); + this.hovering = false; + this.UpdateBitmap(); + this.Update(); + } + + protected override void OnResize(EventArgs e) + { + base.OnResize (e); + + if (bufferGraphics != null) + { + bufferGraphics.Dispose(); + bufferGraphics = null; + } + + if (buffer != null) + { + buffer.Dispose(); + buffer = null; + } + } + + public ToleranceSliderControl() + { + InitializeComponent(); + this.tolerance = 0.5f; + this.toleranceText = PdnResources.GetString("ToleranceSliderControl.Tolerance"); + this.percentageFormat = PdnResources.GetString("ToleranceSliderControl.Percentage.Format"); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (bufferGraphics != null) + { + bufferGraphics.Dispose(); + bufferGraphics = null; + } + + if (buffer != null) + { + buffer.Dispose(); + buffer = null; + } + } + + base.Dispose(disposing); + } + + private void InitializeComponent() + { + this.Name = "ToleranceSliderControl"; + } + } +} \ No newline at end of file diff --git a/src/ToleranceSliderControl.resx b/src/ToleranceSliderControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/Tool.cs b/src/Tool.cs new file mode 100644 index 0000000..7e586ac --- /dev/null +++ b/src/Tool.cs @@ -0,0 +1,1555 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryFunctions; +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Diagnostics; +using System.Drawing; +using System.Reflection; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// Encapsulates the functionality for a tool that goes in the main window's toolbar + /// and that affects the Document. + /// A Tool should only emit a HistoryMemento when it actually modifies the canvas. + /// So, for instance, if the user draws a line but that line doesn't fall within + /// the canvas (like if the seleciton region excludes it), then since the user + /// hasn't really done anything there should be no HistoryMemento emitted. + /// + /// + /// A bit about the eventing model: + /// * Perform[Event]() methods are ALWAYS used to trigger the events. This can be called by + /// either instance methods or outside (client) callers. + /// * [Event]() methods are called first by Perform[Event](). This gives the base Tool class a + /// first chance at handling the event. These methods are private and non-overridable. + /// * On[Event]() methods are then called by [Event]() if necessary, and should be overrided + /// as necessary by derived classes. Always call the base implementation unless the + /// documentation says otherwise. The base implementation gives the Tool a chance to provide + /// default, overridable behavior for an event. + /// + internal class Tool + : IDisposable, + IHotKeyTarget + { + public static readonly Type DefaultToolType = typeof(Tools.PaintBrushTool); + + private ImageResource toolBarImage; + private Cursor cursor; + private ToolInfo toolInfo; + private int mouseDown = 0; // incremented for every MouseDown, decremented for every MouseUp + private int ignoreMouseMove = 0; // when >0, MouseMove is ignored and then this is decremented + + protected Cursor handCursor; + protected Cursor handCursorMouseDown; + protected Cursor handCursorInvalid; + private Cursor panOldCursor; + private Point lastMouseXY; + private Point lastPanMouseXY; + private bool panMode = false; // 'true' when the user is holding down the spacebar + private bool panTracking = false; // 'true' when panMode is true, and when the mouse is down (which is when MouseMove should do panning) + private MoveNubRenderer trackingNub = null; // when we are in pan-tracking mode, we draw this in the center of the screen + + private DocumentWorkspace documentWorkspace; + + private bool active = false; + protected bool autoScroll = true; + private Hashtable keysThatAreDown = new Hashtable(); + private MouseButtons lastButton = MouseButtons.None; + private Surface scratchSurface; + private PdnRegion saveRegion; +#if DEBUG + private bool haveClearedScratch = false; +#endif + + private int mouseEnter; // increments on MouseEnter, decrements on MouseLeave. The MouseLeave event is ONLY raised when this value decrements to 0, and MouseEnter is ONLY raised when this value increments to 1 + + protected Surface ScratchSurface + { + get + { +#if DEBUG + if (!haveClearedScratch) + { + scratchSurface.Clear(ColorBgra.FromBgra(64, 128, 192, 128)); + haveClearedScratch = true; + } +#endif + + return scratchSurface; + } + } + + protected Document Document + { + get + { + return DocumentWorkspace.Document; + } + } + + public DocumentWorkspace DocumentWorkspace + { + get + { + return this.documentWorkspace; + } + } + + public AppWorkspace AppWorkspace + { + get + { + return DocumentWorkspace.AppWorkspace; + } + } + + protected HistoryStack HistoryStack + { + get + { + return DocumentWorkspace.History; + } + } + + protected AppEnvironment AppEnvironment + { + get + { + return this.documentWorkspace.AppWorkspace.AppEnvironment; + } + } + + protected Selection Selection + { + get + { + return DocumentWorkspace.Selection; + } + } + + protected Layer ActiveLayer + { + get + { + return DocumentWorkspace.ActiveLayer; + } + } + + protected int ActiveLayerIndex + { + get + { + return DocumentWorkspace.ActiveLayerIndex; + } + + set + { + DocumentWorkspace.ActiveLayerIndex = value; + } + } + + public void ClearSavedMemory() + { + this.savedTiles = null; + } + + public void ClearSavedRegion() + { + if (this.saveRegion != null) + { + this.saveRegion.Dispose(); + this.saveRegion = null; + } + } + + public void RestoreRegion(PdnRegion region) + { + if (region != null) + { + BitmapLayer activeLayer = (BitmapLayer)ActiveLayer; + activeLayer.Surface.CopySurface(this.ScratchSurface, region); + activeLayer.Invalidate(region); + } + } + + public void RestoreSavedRegion() + { + if (this.saveRegion != null) + { + BitmapLayer activeLayer = (BitmapLayer)ActiveLayer; + activeLayer.Surface.CopySurface(this.ScratchSurface, this.saveRegion); + activeLayer.Invalidate(this.saveRegion); + this.saveRegion.Dispose(); + this.saveRegion = null; + } + } + + private const int saveTileGranularity = 32; + private BitVector2D savedTiles; + + public void SaveRegion(PdnRegion saveMeRegion, Rectangle saveMeBounds) + { + BitmapLayer activeLayer = (BitmapLayer)ActiveLayer; + + if (savedTiles == null) + { + savedTiles = new BitVector2D( + (activeLayer.Width + saveTileGranularity - 1) / saveTileGranularity, + (activeLayer.Height + saveTileGranularity - 1) / saveTileGranularity); + + savedTiles.Clear(false); + } + + Rectangle regionBounds; + if (saveMeRegion == null) + { + regionBounds = saveMeBounds; + } + else + { + regionBounds = saveMeRegion.GetBoundsInt(); + } + + Rectangle bounds = Rectangle.Union(regionBounds, saveMeBounds); + bounds.Intersect(activeLayer.Bounds); + + int leftTile = bounds.Left / saveTileGranularity; + int topTile = bounds.Top / saveTileGranularity; + int rightTile = (bounds.Right - 1) / saveTileGranularity; + int bottomTile = (bounds.Bottom - 1) / saveTileGranularity; + + for (int tileY = topTile; tileY <= bottomTile; ++tileY) + { + Rectangle rowAccumBounds = Rectangle.Empty; + + for (int tileX = leftTile; tileX <= rightTile; ++tileX) + { + if (!savedTiles.Get(tileX, tileY)) + { + Rectangle tileBounds = new Rectangle(tileX * saveTileGranularity, tileY * saveTileGranularity, + saveTileGranularity, saveTileGranularity); + + tileBounds.Intersect(activeLayer.Bounds); + + if (rowAccumBounds == Rectangle.Empty) + { + rowAccumBounds = tileBounds; + } + else + { + rowAccumBounds = Rectangle.Union(rowAccumBounds, tileBounds); + } + + savedTiles.Set(tileX, tileY, true); + } + else + { + if (rowAccumBounds != Rectangle.Empty) + { + using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds), + src = activeLayer.Surface.CreateWindow(rowAccumBounds)) + { + dst.CopySurface(src); + } + + rowAccumBounds = Rectangle.Empty; + } + } + } + + if (rowAccumBounds != Rectangle.Empty) + { + using (Surface dst = ScratchSurface.CreateWindow(rowAccumBounds), + src = activeLayer.Surface.CreateWindow(rowAccumBounds)) + { + dst.CopySurface(src); + } + + rowAccumBounds = Rectangle.Empty; + } + } + + if (this.saveRegion != null) + { + this.saveRegion.Dispose(); + this.saveRegion = null; + } + + if (saveMeRegion != null) + { + this.saveRegion = saveMeRegion.Clone(); + } + } + + private sealed class KeyTimeInfo + { + public DateTime KeyDownTime; + public DateTime LastKeyPressPulse; + private int repeats = 0; + + public int Repeats + { + get + { + return repeats; + } + + set + { + repeats = value; + } + } + + public KeyTimeInfo() + { + KeyDownTime = DateTime.Now; + LastKeyPressPulse = KeyDownTime; + } + } + + /// + /// Tells you whether the tool is "active" or not. If the tool is not active + /// it is not safe to call any other method besides PerformActivate. All + /// properties are safe to get values from. + /// + public bool Active + { + get + { + return this.active; + } + } + + /// + /// Returns true if the Tool has the input focus, or false if it does not. + /// + /// + /// This is used, for instanced, by the Text Tool so that it doesn't blink the + /// cursor unless it's actually going to do something in response to your + /// keyboard input! + /// + public bool Focused + { + get + { + return DocumentWorkspace.Focused; + } + } + + public bool IsMouseDown + { + get + { + return this.mouseDown > 0; + } + } + + /// + /// Gets a flag that determines whether the Tool is deactivated while the current + /// layer is changing, and then reactivated afterwards. + /// + /// + /// This property is queried every time the ActiveLayer property of DocumentWorkspace + /// is changed. If false is returned, then the tool is not deactivated during the + /// layer change and must manually maintain coherency. + /// + public virtual bool DeactivateOnLayerChange + { + get + { + return true; + } + } + + /// + /// Tells you which keys are pressed + /// + public Keys ModifierKeys + { + get + { + return Control.ModifierKeys; + } + } + + /// + /// Represents the Image that is displayed in the toolbar. + /// + public ImageResource Image + { + get + { + return this.toolBarImage; + } + } + + public event EventHandler CursorChanging; + protected virtual void OnCursorChanging() + { + if (CursorChanging != null) + { + CursorChanging(this, EventArgs.Empty); + } + } + + public event EventHandler CursorChanged; + protected virtual void OnCursorChanged() + { + if (CursorChanged != null) + { + CursorChanged(this, EventArgs.Empty); + } + } + + /// + /// The Cursor that is displayed when this Tool is active and the + /// mouse cursor is inside the document view. + /// + public Cursor Cursor + { + get + { + return this.cursor; + } + + set + { + OnCursorChanging(); + this.cursor = value; + OnCursorChanged(); + } + } + + /// + /// The name of the Tool. For instance, "Pencil". This name should *not* end in "Tool", e.g. "Pencil Tool" + /// + public string Name + { + get + { + return this.toolInfo.Name; + } + } + + /// + /// A short description of how to use the tool. + /// + public string HelpText + { + get + { + return this.toolInfo.HelpText; + } + } + + public ToolInfo Info + { + get + { + return this.toolInfo; + } + } + + public ToolBarConfigItems ToolBarConfigItems + { + get + { + return this.toolInfo.ToolBarConfigItems; + } + } + + /// + /// Specifies whether or not an inherited tool should take Ink commands + /// + protected virtual bool SupportsInk + { + get + { + return false; + } + } + + public char HotKey + { + get + { + return this.toolInfo.HotKey; + } + } + + // Methods to send messages to this class + public void PerformActivate() + { + Activate(); + } + + public void PerformDeactivate() + { + Deactivate(); + } + + private bool IsOverflow(MouseEventArgs e) + { + PointF clientPt = DocumentWorkspace.DocumentToClient(new PointF(e.X, e.Y)); + return clientPt.X < -16384 || clientPt.Y < -16384; + } + + public bool IsMouseEntered + { + get + { + return this.mouseEnter > 0; + } + } + + public void PerformMouseEnter() + { + MouseEnter(); + } + + private void MouseEnter() + { + ++this.mouseEnter; + + if (this.mouseEnter == 1) + { + OnMouseEnter(); + } + } + + protected virtual void OnMouseEnter() + { + } + + public void PerformMouseLeave() + { + MouseLeave(); + } + + private void MouseLeave() + { + if (this.mouseEnter == 1) + { + this.mouseEnter = 0; + OnMouseLeave(); + } + else + { + this.mouseEnter = Math.Max(0, this.mouseEnter - 1); + } + } + + protected virtual void OnMouseLeave() + { + } + + public void PerformMouseMove(MouseEventArgs e) + { + if (IsOverflow(e)) + { + return; + } + + if (e is StylusEventArgs) + { + if (this.SupportsInk) + { + StylusMove(e as StylusEventArgs); + } + + // if the tool does not claim ink support, discard + } + else + { + MouseMove(e); + } + } + + public void PerformMouseDown(MouseEventArgs e) + { + if (IsOverflow(e)) + { + return; + } + + if (e is StylusEventArgs) + { + if (this.SupportsInk) + { + StylusDown(e as StylusEventArgs); + } + + // if the tool does not claim ink support, discard + } + else + { + if (this.SupportsInk) + { + DocumentWorkspace.Focus(); + } + + MouseDown(e); + } + } + + public void PerformMouseUp(MouseEventArgs e) + { + if (IsOverflow(e)) + { + return; + } + + if (e is StylusEventArgs) + { + if (this.SupportsInk) + { + StylusUp(e as StylusEventArgs); + } + + // if the tool does not claim ink support, discard + } + else + { + MouseUp(e); + } + } + + public void PerformKeyPress(KeyPressEventArgs e) + { + KeyPress(e); + } + + public void PerformKeyPress(Keys key) + { + KeyPress(key); + } + + public void PerformKeyUp(KeyEventArgs e) + { + KeyUp(e); + } + + public void PerformKeyDown(KeyEventArgs e) + { + KeyDown(e); + } + + public void PerformClick() + { + Click(); + } + + public void PerformPulse() + { + Pulse(); + } + + public void PerformPaste(IDataObject data, out bool handled) + { + Paste(data, out handled); + } + + public void PerformPasteQuery(IDataObject data, out bool canHandle) + { + PasteQuery(data, out canHandle); + } + + private void Activate() + { + Debug.Assert(this.active != true, "already active!"); + this.active = true; + + this.handCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur")); + this.handCursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur")); + this.handCursorInvalid = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorInvalid.cur")); + + this.panTracking = false; + this.panMode = false; + this.mouseDown = 0; + this.savedTiles = null; + this.saveRegion = null; + + this.scratchSurface = DocumentWorkspace.BorrowScratchSurface(this.GetType().Name + ": Tool.Activate()"); +#if DEBUG + this.haveClearedScratch = false; +#endif + + Selection.Changing += new EventHandler(SelectionChangingHandler); + Selection.Changed += new EventHandler(SelectionChangedHandler); + HistoryStack.ExecutingHistoryMemento += new ExecutingHistoryMementoEventHandler(ExecutingHistoryMemento); + HistoryStack.ExecutedHistoryMemento += new ExecutedHistoryMementoEventHandler(ExecutedHistoryMemento); + HistoryStack.FinishedStepGroup += new EventHandler(FinishedHistoryStepGroup); + + this.trackingNub = new MoveNubRenderer(this.RendererList); + this.trackingNub.Visible = false; + this.trackingNub.Size = new SizeF(10, 10); + this.trackingNub.Shape = MoveNubShape.Compass; + this.RendererList.Add(this.trackingNub, false); + + OnActivate(); + } + + void FinishedHistoryStepGroup(object sender, EventArgs e) + { + OnFinishedHistoryStepGroup(); + } + + protected virtual void OnFinishedHistoryStepGroup() + { + } + + /// + /// This method is called when the tool is being activated; that is, when the + /// user has chosen to use this tool by clicking on it on a toolbar. + /// + protected virtual void OnActivate() + { + } + + private void Deactivate() + { + Debug.Assert(this.active != false, "not active!"); + + this.active = false; + + Selection.Changing -= new EventHandler(SelectionChangingHandler); + Selection.Changed -= new EventHandler(SelectionChangedHandler); + + HistoryStack.ExecutingHistoryMemento -= new ExecutingHistoryMementoEventHandler(ExecutingHistoryMemento); + HistoryStack.ExecutedHistoryMemento -= new ExecutedHistoryMementoEventHandler(ExecutedHistoryMemento); + HistoryStack.FinishedStepGroup -= new EventHandler(FinishedHistoryStepGroup); + + OnDeactivate(); + + this.RendererList.Remove(this.trackingNub); + this.trackingNub.Dispose(); + this.trackingNub = null; + + DocumentWorkspace.ReturnScratchSurface(this.scratchSurface); + this.scratchSurface = null; + + if (this.saveRegion != null) + { + this.saveRegion.Dispose(); + this.saveRegion = null; + } + + this.savedTiles = null; + + if (this.handCursor != null) + { + this.handCursor.Dispose(); + this.handCursor = null; + } + + if (this.handCursorMouseDown != null) + { + this.handCursorMouseDown.Dispose(); + this.handCursorMouseDown = null; + } + + if (this.handCursorInvalid != null) + { + this.handCursorInvalid.Dispose(); + this.handCursorInvalid = null; + } + } + + /// + /// This method is called when the tool is being deactivated; that is, when the + /// user has chosen to use another tool by clicking on another tool on a + /// toolbar. + /// + protected virtual void OnDeactivate() + { + } + + private void StylusDown(StylusEventArgs e) + { + if (!this.panMode) + { + OnStylusDown(e); + } + } + + protected virtual void OnStylusDown(StylusEventArgs e) + { + } + + private void StylusMove(StylusEventArgs e) + { + if (!this.panMode) + { + OnStylusMove(e); + } + } + + protected virtual void OnStylusMove(StylusEventArgs e) + { + if (this.mouseDown > 0) + { + ScrollIfNecessary(new PointF(e.X, e.Y)); + } + } + + private void StylusUp(StylusEventArgs e) + { + if (this.panTracking) + { + this.trackingNub.Visible = false; + this.panTracking = false; + this.Cursor = this.handCursor; + } + else + { + OnStylusUp(e); + } + } + + protected virtual void OnStylusUp(StylusEventArgs e) + { + } + + private void MouseMove(MouseEventArgs e) + { + if (this.ignoreMouseMove > 0) + { + --this.ignoreMouseMove; + } + else if (this.panTracking && e.Button == MouseButtons.Left) + { + // Pan the document, using Stylus coordinates. This is done in + // MouseMove instead of StylusMove because StylusMove is + // asynchronous, and would not 'feel' right (pan motions would + // stack up) + + Point position = new Point(e.X, e.Y); + RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF; + PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect); + PointF delta = new PointF(e.X - lastPanMouseXY.X, e.Y - lastPanMouseXY.Y); + PointF newScroll = DocumentWorkspace.DocumentScrollPositionF; + + if (delta.X != 0 || delta.Y != 0) + { + newScroll.X -= delta.X; + newScroll.Y -= delta.Y; + + lastPanMouseXY = new Point(e.X, e.Y); + lastPanMouseXY.X -= (int)Math.Truncate(delta.X); + lastPanMouseXY.Y -= (int)Math.Truncate(delta.Y); + + ++this.ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event. ignore it prevents 'jittering' at non-integral zoom levels (like, say, 743%) + DocumentWorkspace.DocumentScrollPositionF = newScroll; + Update(); + } + + } + else if (!this.panMode) + { + OnMouseMove(e); + } + + this.lastMouseXY = new Point(e.X, e.Y); + this.lastButton = e.Button; + } + + /// + /// This method is called when the Tool is active and the mouse is moving within + /// the document canvas area. + /// + /// Contains information about where the mouse cursor is, in document coordinates. + protected virtual void OnMouseMove(MouseEventArgs e) + { + if (this.panMode || this.mouseDown > 0) + { + ScrollIfNecessary(new PointF(e.X, e.Y)); + } + } + + private void MouseDown(MouseEventArgs e) + { + ++this.mouseDown; + + if (this.panMode) + { + this.panTracking = true; + this.lastPanMouseXY = new Point(e.X, e.Y); + + if (this.CanPan()) + { + this.Cursor = this.handCursorMouseDown; + } + } + else + { + OnMouseDown(e); + } + + this.lastMouseXY = new Point(e.X, e.Y); + } + + /// + /// This method is called when the Tool is active and a mouse button has been + /// pressed within the document area. + /// + /// Contains information about where the mouse cursor is, in document coordinates, and which mouse buttons were pressed. + protected virtual void OnMouseDown(MouseEventArgs e) + { + this.lastButton = e.Button; + } + + private void MouseUp(MouseEventArgs e) + { + --this.mouseDown; + + if (!this.panMode) + { + OnMouseUp(e); + } + + this.lastMouseXY = new Point(e.X, e.Y); + } + + /// + /// This method is called when the Tool is active and a mouse button has been + /// released within the document area. + /// + /// Contains information about where the mouse cursor is, in document coordinates, and which mouse buttons were released. + protected virtual void OnMouseUp(MouseEventArgs e) + { + this.lastButton = e.Button; + } + + private void Click() + { + OnClick(); + } + + /// + /// This method is called when the Tool is active and a mouse button has been + /// clicked within the document area. If you need more specific information, + /// such as where the mouse was clicked and which button was used, respond to + /// the MouseDown/MouseUp events. + /// + protected virtual void OnClick() + { + } + + private void KeyPress(KeyPressEventArgs e) + { + OnKeyPress(e); + } + + private static DateTime lastToolSwitch = DateTime.MinValue; + + // if we are pressing 'S' to switch to the selection tools, then consecutive + // presses of 'S' should switch to the next selection tol in the list. however, + // if we wait awhile then pressing 'S' should go to the *first* selection + // tool. 'awhile' is defined by this variable. + private static readonly TimeSpan toolSwitchReset = new TimeSpan(0, 0, 0, 2, 0); + + private const char decPenSizeShortcut = '['; + private const char decPenSizeBy5Shortcut = (char)27; // Ctrl [ but must also test that Ctrl is down + private const char incPenSizeShortcut = ']'; + private const char incPenSizeBy5Shortcut = (char)29; // Ctrl ] but must also test that Ctrl is down + private const char swapColorsShortcut = 'x'; + private const char swapPrimarySecondaryChoice = 'c'; + private char[] wildShortcuts = new char[] { ',', '.', '/' }; + + // Return true if the key is handled, false if not. + protected virtual bool OnWildShortcutKey(int ordinal) + { + return false; + } + + /// + /// This method is called when the tool is active and a keyboard key is pressed + /// and released. If you respond to the keyboard key, set e.Handled to true. + /// + protected virtual void OnKeyPress(KeyPressEventArgs e) + { + if (!e.Handled && DocumentWorkspace.Focused) + { + int wsIndex = Array.IndexOf(wildShortcuts, e.KeyChar); + + if (wsIndex != -1) + { + e.Handled = OnWildShortcutKey(wsIndex); + } + else if (e.KeyChar == swapColorsShortcut) + { + AppWorkspace.Widgets.ColorsForm.SwapUserColors(); + e.Handled = true; + } + else if (e.KeyChar == swapPrimarySecondaryChoice) + { + AppWorkspace.Widgets.ColorsForm.ToggleWhichUserColor(); + e.Handled = true; + } + else if (e.KeyChar == decPenSizeShortcut) + { + AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-1.0f); + e.Handled = true; + } + else if (e.KeyChar == decPenSizeBy5Shortcut && (ModifierKeys & Keys.Control) != 0) + { + AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(-5.0f); + e.Handled = true; + } + else if (e.KeyChar == incPenSizeShortcut) + { + AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(+1.0f); + e.Handled = true; + } + else if (e.KeyChar == incPenSizeBy5Shortcut && (ModifierKeys & Keys.Control) != 0) + { + AppWorkspace.Widgets.ToolConfigStrip.AddToPenSize(+5.0f); + e.Handled = true; + } + else + { + ToolInfo[] toolInfos = DocumentWorkspace.ToolInfos; + Type currentToolType = DocumentWorkspace.GetToolType(); + int currentTool = 0; + + if (0 != (ModifierKeys & Keys.Shift)) + { + Array.Reverse(toolInfos); + } + + if (char.ToLower(this.HotKey) != char.ToLower(e.KeyChar) || + (DateTime.Now - lastToolSwitch) > toolSwitchReset) + { + // If it's been a short time since they pressed a tool switching hotkey, + // we will start from the beginning of this list. This helps to enable two things: + // 1) If multiple tools have the same hotkey, the user may press that hotkey + // to cycle through them + // 2) After a period of time, pressing the hotkey will revert to always + // choosing the first tool in that list of tools which have the same hotkey. + currentTool = -1; + } + else + { + for (int t = 0; t < toolInfos.Length; ++t) + { + if (toolInfos[t].ToolType == currentToolType) + { + currentTool = t; + break; + } + } + } + + for (int t = 0; t < toolInfos.Length; ++t) + { + int newTool = (t + currentTool + 1) % toolInfos.Length; + ToolInfo localToolInfo = toolInfos[newTool]; + + if (localToolInfo.ToolType == DocumentWorkspace.GetToolType() && + localToolInfo.SkipIfActiveOnHotKey) + { + continue; + } + + if (char.ToLower(localToolInfo.HotKey) == char.ToLower(e.KeyChar)) + { + if (!this.IsMouseDown) + { + AppWorkspace.Widgets.ToolsControl.SelectTool(localToolInfo.ToolType); + } + + e.Handled = true; + lastToolSwitch = DateTime.Now; + break; + } + } + + // If the keypress is still not handled ... + if (!e.Handled) + { + switch (e.KeyChar) + { + // By default, Esc/Enter clear the current selection if there is any + case (char)13: // Enter + case (char)27: // Escape + if (this.mouseDown == 0 && !Selection.IsEmpty) + { + e.Handled = true; + DocumentWorkspace.ExecuteFunction(new DeselectFunction()); + } + + break; + } + } + } + } + } + + private DateTime lastKeyboardMove = DateTime.MinValue; + private Keys lastKey; + private int keyboardMoveSpeed = 1; + private int keyboardMoveRepeats = 0; + + private void KeyPress(Keys key) + { + OnKeyPress(key); + } + + /// + /// This method is called when the tool is active and a keyboard key is pressed + /// and released that is not representable with a regular Unicode chararacter. + /// An example would be the arrow keys. + /// + protected virtual void OnKeyPress(Keys key) + { + Point dir = Point.Empty; + + if (key != lastKey) + { + lastKeyboardMove = DateTime.MinValue; + } + + lastKey = key; + + switch (key) + { + case Keys.Left: + --dir.X; + break; + + case Keys.Right: + ++dir.X; + break; + + case Keys.Up: + --dir.Y; + break; + + case Keys.Down: + ++dir.Y; + break; + } + + if (!dir.Equals(Point.Empty)) + { + long span = DateTime.Now.Ticks - lastKeyboardMove.Ticks; + + if ((span * 4) > TimeSpan.TicksPerSecond) + { + keyboardMoveRepeats = 0; + keyboardMoveSpeed = 1; + } + else + { + keyboardMoveRepeats++; + + if (keyboardMoveRepeats > 15 && (keyboardMoveRepeats % 4) == 0) + { + keyboardMoveSpeed++; + } + } + + lastKeyboardMove = DateTime.Now; + + int offset = (int)(Math.Ceiling(DocumentWorkspace.ScaleFactor.Ratio) * (double)keyboardMoveSpeed); + Cursor.Position = new Point(Cursor.Position.X + offset * dir.X, Cursor.Position.Y + offset * dir.Y); + + Point location = DocumentWorkspace.PointToScreen(Point.Truncate(DocumentWorkspace.DocumentToClient(PointF.Empty))); + + PointF stylusLocF = new PointF((float)Cursor.Position.X - (float)location.X, (float)Cursor.Position.Y - (float)location.Y); + Point stylusLoc = new Point(Cursor.Position.X - location.X, Cursor.Position.Y - location.Y); + + stylusLoc = DocumentWorkspace.ScaleFactor.UnscalePoint(stylusLoc); + stylusLocF = DocumentWorkspace.ScaleFactor.UnscalePoint(stylusLocF); + + DocumentWorkspace.PerformDocumentMouseMove(new StylusEventArgs(lastButton, 1, stylusLocF.X, stylusLocF.Y, 0, 1.0f)); + DocumentWorkspace.PerformDocumentMouseMove(new MouseEventArgs(lastButton, 1, stylusLoc.X, stylusLoc.Y, 0)); + } + } + + private bool CanPan() + { + Rectangle vis = Utility.RoundRectangle(DocumentWorkspace.VisibleDocumentRectangleF); + vis.Intersect(Document.Bounds); + + if (vis == Document.Bounds) + { + return false; + } + else + { + return true; + } + } + + private void KeyUp(KeyEventArgs e) + { + if (this.panMode) + { + this.panMode = false; + this.panTracking = false; + this.trackingNub.Visible = false; + this.Cursor = this.panOldCursor; + this.panOldCursor = null; + e.Handled = true; + } + + OnKeyUp(e); + } + + /// + /// This method is called when the tool is active and a keyboard key is pressed. + /// If you respond to the keyboard key, set e.Handled to true. + /// + protected virtual void OnKeyUp(KeyEventArgs e) + { + keysThatAreDown.Clear(); + } + + private void KeyDown(KeyEventArgs e) + { + OnKeyDown(e); + } + + /// + /// This method is called when the tool is active and a keyboard key is released + /// Before responding, check that e.Handled is false, and if you then respond to + /// the keyboard key, set e.Handled to true. + /// + protected virtual void OnKeyDown(KeyEventArgs e) + { + if (!e.Handled) + { + if (!keysThatAreDown.Contains(e.KeyData)) + { + keysThatAreDown.Add(e.KeyData, new KeyTimeInfo()); + } + + if (!this.IsMouseDown && + !this.panMode && + e.KeyCode == Keys.Space) + { + this.panMode = true; + this.panOldCursor = this.Cursor; + + if (CanPan()) + { + this.Cursor = this.handCursor; + } + else + { + this.Cursor = this.handCursorInvalid; + } + } + + // arrow keys are processed in another way + // we get their KeyDown but no KeyUp, so they can not be handled + // by our normal methods + OnKeyPress(e.KeyData); + + } + } + + private void SelectionChanging() + { + OnSelectionChanging(); + } + + /// + /// This method is called when the Tool is active and the selection area is + /// about to be changed. + /// + protected virtual void OnSelectionChanging() + { + } + + private void SelectionChanged() + { + OnSelectionChanged(); + } + + /// + /// This method is called when the Tool is active and the selection area has + /// been changed. + /// + protected virtual void OnSelectionChanged() + { + } + + private void ExecutingHistoryMemento(object sender, ExecutingHistoryMementoEventArgs e) + { + OnExecutingHistoryMemento(e); + } + + protected virtual void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e) + { + } + + private void ExecutedHistoryMemento(object sender, ExecutedHistoryMementoEventArgs e) + { + OnExecutedHistoryMemento(e); + } + + protected virtual void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e) + { + } + + private void PasteQuery(IDataObject data, out bool canHandle) + { + OnPasteQuery(data, out canHandle); + } + + /// + /// This method is called when the system is querying a tool as to whether + /// it can handle a pasted object. + /// + /// + /// The clipboard data that was pasted by the user that should be inspected. + /// + /// + /// true if the data can be handled by the tool, false if not. + /// + /// + /// If you do not set canHandle to true then the tool will not be + /// able to respond to the Edit menu's Paste item. + /// + protected virtual void OnPasteQuery(IDataObject data, out bool canHandle) + { + canHandle = false; + } + + private void Paste(IDataObject data, out bool handled) + { + OnPaste(data, out handled); + } + + /// + /// This method is called when the user invokes a paste operation. Tools get + /// the first chance to handle this data. + /// + /// + /// The data that was pasted by the user. + /// + /// + /// true if the data was handled and pasted, false if not. + /// + /// + /// If you do not set handled to true the event will be passed to the + /// global paste handler. + /// + protected virtual void OnPaste(IDataObject data, out bool handled) + { + handled = false; + } + + private void Pulse() + { + OnPulse(); + } + + protected bool IsFormActive + { + get + { + return (object.ReferenceEquals(Form.ActiveForm, DocumentWorkspace.FindForm())); + } + } + + /// + /// This method is called many times per second, called by the DocumentWorkspace. + /// + protected virtual void OnPulse() + { + if (this.panTracking && this.lastButton == MouseButtons.Right) + { + Point position = this.lastMouseXY; + RectangleF visibleRect = DocumentWorkspace.VisibleDocumentRectangleF; + PointF visibleCenterPt = Utility.GetRectangleCenter(visibleRect); + PointF delta = new PointF(position.X - visibleCenterPt.X, position.Y - visibleCenterPt.Y); + PointF newScroll = DocumentWorkspace.DocumentScrollPositionF; + + this.trackingNub.Visible = true; + + if (delta.X != 0 || delta.Y != 0) + { + newScroll.X += delta.X; + newScroll.Y += delta.Y; + + ++this.ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event. ignore it prevents 'jittering' at non-integral zoom levels (like, say, 743%) + UI.SuspendControlPainting(DocumentWorkspace); + DocumentWorkspace.DocumentScrollPositionF = newScroll; + this.trackingNub.Visible = true; + this.trackingNub.Location = Utility.GetRectangleCenter(DocumentWorkspace.VisibleDocumentRectangleF); + UI.ResumeControlPainting(DocumentWorkspace); + DocumentWorkspace.Invalidate(true); + Update(); + } + } + } + + protected bool ScrollIfNecessary(PointF position) + { + if (!autoScroll || !CanPan()) + { + return false; + } + + RectangleF visible = DocumentWorkspace.VisibleDocumentRectangleF; + PointF lastScrollPosition = DocumentWorkspace.DocumentScrollPositionF; + PointF delta = PointF.Empty; + PointF zoomedPoint = PointF.Empty; + + zoomedPoint.X = Utility.Lerp((visible.Left + visible.Right) / 2.0f, position.X, 1.02f); + zoomedPoint.Y = Utility.Lerp((visible.Top + visible.Bottom) / 2.0f, position.Y, 1.02f); + + if (zoomedPoint.X < visible.Left) + { + delta.X = zoomedPoint.X - visible.Left; + } + else if (zoomedPoint.X > visible.Right) + { + delta.X = zoomedPoint.X - visible.Right; + } + + if (zoomedPoint.Y < visible.Top) + { + delta.Y = zoomedPoint.Y - visible.Top; + } + else if (zoomedPoint.Y > visible.Bottom) + { + delta.Y = zoomedPoint.Y - visible.Bottom; + } + + if (!delta.IsEmpty) + { + PointF newScrollPosition = new PointF(lastScrollPosition.X + delta.X, lastScrollPosition.Y + delta.Y); + DocumentWorkspace.DocumentScrollPositionF = newScrollPosition; + Update(); + return true; + } + else + { + return false; + } + } + + private void SelectionChangingHandler(object sender, EventArgs e) + { + OnSelectionChanging(); + } + + private void SelectionChangedHandler(object sender, EventArgs e) + { + OnSelectionChanged(); + } + + protected void SetStatus(ImageResource statusIcon, string statusText) + { + if (statusIcon == null && statusText != null) + { + statusIcon = PdnResources.GetImageResource("Icons.MenuHelpHelpTopicsIcon.png"); + } + + DocumentWorkspace.SetStatus(statusText, statusIcon); + } + + protected SurfaceBoxRendererList RendererList + { + get + { + return this.DocumentWorkspace.RendererList; + } + } + + protected void Update() + { + DocumentWorkspace.Update(); + } + + protected object GetStaticData() + { + return DocumentWorkspace.GetStaticToolData(this.GetType()); + } + + protected void SetStaticData(object data) + { + DocumentWorkspace.SetStaticToolData(this.GetType(), data); + } + + // NOTE: Your constructor must be able to run successfully with a documentWorkspace + // of null. This is sent in while the DocumentControl static constructor + // class is building a list of ToolInfo instances, so that it may construct + // the list without having to also construct a DocumentControl. + public Tool(DocumentWorkspace documentWorkspace, + ImageResource toolBarImage, + string name, + string helpText, + char hotKey, + bool skipIfActiveOnHotKey, + ToolBarConfigItems toolBarConfigItems) + { + this.documentWorkspace = documentWorkspace; + this.toolBarImage = toolBarImage; + this.toolInfo = new ToolInfo(name, helpText, toolBarImage, hotKey, skipIfActiveOnHotKey, toolBarConfigItems, this.GetType()); + + if (this.documentWorkspace != null) + { + this.documentWorkspace.UpdateStatusBarToToolHelpText(this); + } + } + + ~Tool() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + Debug.Assert(!this.active, "Tool is still active!"); + + if (disposing) + { + if (this.saveRegion != null) + { + this.saveRegion.Dispose(); + this.saveRegion = null; + } + + OnDisposed(); + } + } + + public event EventHandler Disposed; + private void OnDisposed() + { + if (Disposed != null) + { + Disposed(this, EventArgs.Empty); + } + } + + public Form AssociatedForm + { + get + { + return AppWorkspace.FindForm(); + } + } + } +} diff --git a/src/ToolBarConfigItems.cs b/src/ToolBarConfigItems.cs new file mode 100644 index 0000000..eb8882c --- /dev/null +++ b/src/ToolBarConfigItems.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; + +namespace PaintDotNet +{ + [Flags] + internal enum ToolBarConfigItems + : uint + { + None = 0, + All = ~None, + + // IMPORTANT: Keep these in alphabetical order. + AlphaBlending = 1, + Antialiasing = 2, + Brush = 4, + ColorPickerBehavior = 8, + FloodMode = 4096, + Gradient = 16, + Pen = 32, + PenCaps = 64, + SelectionCombineMode = 2048, + SelectionDrawMode = 8192, + ShapeType = 128, + Resampling = 256, + Text = 512, + Tolerance = 1024, + } +} diff --git a/src/ToolChooserStrip.cs b/src/ToolChooserStrip.cs new file mode 100644 index 0000000..333de5c --- /dev/null +++ b/src/ToolChooserStrip.cs @@ -0,0 +1,251 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ToolChooserStrip + : ToolStripEx, + IToolChooser + { + private ToolStripSplitButton chooseToolButton; + private int ignoreToolClicked = 0; + private ToolInfo[] toolInfos = null; + private Type activeTool = null; + private bool showChooseDefaults = true; + private bool useToolNameForLabel = false; + private string chooseToolLabelText; + + public ToolChooserStrip() + { + this.chooseToolLabelText = PdnResources.GetString("ToolStripChooser.ChooseToolButton.Text"); + InitializeComponent(); + } + + public bool ShowChooseDefaults + { + get + { + return this.showChooseDefaults; + } + + set + { + this.showChooseDefaults = value; + } + } + + public bool UseToolNameForLabel + { + get + { + return this.useToolNameForLabel; + } + + set + { + this.useToolNameForLabel = value; + SetToolButtonLabel(); + } + } + + private void SetToolButtonLabel() + { + if (!this.useToolNameForLabel) + { + this.chooseToolButton.TextImageRelation = TextImageRelation.TextBeforeImage; + this.chooseToolButton.Text = this.chooseToolLabelText; + } + else + { + this.chooseToolButton.TextImageRelation = TextImageRelation.ImageBeforeText; + ToolInfo ti = null; + + if (this.toolInfos != null) + { + ti = Array.Find( + this.toolInfos, + delegate(ToolInfo check) + { + return (check.ToolType == this.activeTool); + }); + } + + if (ti == null) + { + this.chooseToolButton.Text = string.Empty; + } + else + { + this.chooseToolButton.Text = ti.Name; + } + } + } + + public event EventHandler ChooseDefaultsClicked; + protected virtual void OnChooseDefaultsClicked() + { + if (ChooseDefaultsClicked != null) + { + ChooseDefaultsClicked(this, EventArgs.Empty); + } + } + + private void InitializeComponent() + { + this.chooseToolButton = new ToolStripSplitButton(); + this.SuspendLayout(); + // + // chooseToolButton + // + this.chooseToolButton.Name = "chooseToolButton"; + this.chooseToolButton.Text = this.chooseToolLabelText; + this.chooseToolButton.TextImageRelation = TextImageRelation.TextBeforeImage; + this.chooseToolButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText; + this.chooseToolButton.DropDownOpening += new EventHandler(ChooseToolButton_DropDownOpening); + this.chooseToolButton.DropDownClosed += new EventHandler(ChooseToolButton_DropDownClosed); + this.chooseToolButton.DropDownItemClicked += new ToolStripItemClickedEventHandler(ChooseToolButton_DropDownItemClicked); + this.chooseToolButton.Click += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolChooserStrip(chooseToolButton.Click)"); + this.chooseToolButton.ShowDropDown(); + }; + // + // ToolChooserStrip + // + this.Items.Add(new ToolStripSeparator()); + this.Items.Add(this.chooseToolButton); + this.ResumeLayout(false); + } + + private void ChooseToolButton_DropDownClosed(object sender, EventArgs e) + { + this.chooseToolButton.DropDownItems.Clear(); + } + + private void ChooseToolButton_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e) + { + ToolInfo ti = e.ClickedItem.Tag as ToolInfo; + + if (ti != null) + { + Tracing.LogFeature("ToolChooserStrip(itemClicked(" + ti.ToolType.GetType().FullName + "))"); + OnToolClicked(ti.ToolType); + } + } + + private void ChooseTool_Click(object sender, EventArgs e) + { + OnChooseDefaultsClicked(); + } + + private void ChooseToolButton_DropDownOpening(object sender, EventArgs e) + { + this.chooseToolButton.DropDownItems.Clear(); + + if (this.showChooseDefaults) + { + string chooseToolText = PdnResources.GetString("ToolChooserStrip.ChooseToolDefaults.Text"); + ImageResource chooseToolIcon = PdnResources.GetImageResource("Icons.MenuLayersLayerPropertiesIcon.png"); + + ToolStripMenuItem tsmi = new ToolStripMenuItem( + chooseToolText, + chooseToolIcon.Reference, + ChooseTool_Click); + + this.chooseToolButton.DropDownItems.Add(tsmi); + this.chooseToolButton.DropDownItems.Add(new ToolStripSeparator()); + } + + for (int i = 0; i < this.toolInfos.Length; ++i) + { + ToolStripMenuItem toolMI = new ToolStripMenuItem(); + toolMI.Image = this.toolInfos[i].Image.Reference; + toolMI.Text = this.toolInfos[i].Name; + toolMI.Tag = this.toolInfos[i]; + + if (this.toolInfos[i].ToolType == this.activeTool) + { + toolMI.Checked = true; + } + else + { + toolMI.Checked = false; + } + + this.chooseToolButton.DropDownItems.Add(toolMI); + } + } + + public void SelectTool(Type toolType) + { + SelectTool(toolType, true); + } + + public void SelectTool(Type toolType, bool raiseEvent) + { + if (!raiseEvent) + { + ++this.ignoreToolClicked; + } + + try + { + if (toolType != this.activeTool) + { + foreach (ToolInfo ti in this.toolInfos) + { + if (ti.ToolType == toolType) + { + this.chooseToolButton.Image = ti.Image.Reference; + this.activeTool = toolType; + SetToolButtonLabel(); + break; + } + } + } + } + + finally + { + if (!raiseEvent) + { + --this.ignoreToolClicked; + } + } + } + + public void SetTools(ToolInfo[] newToolInfos) + { + this.toolInfos = newToolInfos; + SetToolButtonLabel(); + } + + public event ToolClickedEventHandler ToolClicked; + protected virtual void OnToolClicked(Type toolType) + { + if (this.ignoreToolClicked <= 0) + { + SetToolButtonLabel(); + + if (ToolClicked != null) + { + ToolClicked(this, new ToolClickedEventArgs(toolType)); + } + } + } + } +} diff --git a/src/ToolClickedEventArgs.cs b/src/ToolClickedEventArgs.cs new file mode 100644 index 0000000..3d7dc6a --- /dev/null +++ b/src/ToolClickedEventArgs.cs @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal class ToolClickedEventArgs + : System.EventArgs + { + private Type toolType; + public Type ToolType + { + get + { + return toolType; + } + } + + public ToolClickedEventArgs(Tool tool) + { + this.toolType = tool.GetType(); + } + + public ToolClickedEventArgs(Type toolType) + { + this.toolType = toolType; + } + } +} diff --git a/src/ToolClickedEventHandler.cs b/src/ToolClickedEventHandler.cs new file mode 100644 index 0000000..275e61e --- /dev/null +++ b/src/ToolClickedEventHandler.cs @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + internal delegate void ToolClickedEventHandler(object sender, ToolClickedEventArgs e); +} diff --git a/src/ToolConfigStrip.cs b/src/ToolConfigStrip.cs new file mode 100644 index 0000000..cbd7e68 --- /dev/null +++ b/src/ToolConfigStrip.cs @@ -0,0 +1,3124 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Globalization; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet +{ + // TODO: for 4.0, refactor into smaller ToolConfigStrip "Sections" + // better yet, use IndirectUI + internal class ToolConfigStrip + : ToolStripEx, + IBrushConfig, + IShapeTypeConfig, + IPenConfig, + IAntiAliasingConfig, + IAlphaBlendingConfig, + ITextConfig, + IToleranceConfig, + IColorPickerConfig, + IGradientConfig, + IResamplingConfig, + ISelectionCombineModeConfig, + IFloodModeConfig, + ISelectionDrawModeConfig + { + private ToolBarConfigItems toolBarConfigItems = ToolBarConfigItems.None; + + private EnumLocalizer hatchStyleNames = EnumLocalizer.Create(typeof(HatchStyle)); + private string solidBrushText; + private ImageResource shapeOutlineImage = PdnResources.GetImageResource("Icons.ShapeOutlineIcon.png"); + private ImageResource shapeInteriorImage = PdnResources.GetImageResource("Icons.ShapeInteriorIcon.png"); + private ImageResource shapeBothImage = PdnResources.GetImageResource("Icons.ShapeBothIcon.png"); + + private ToolStripSeparator brushSeparator; + private ToolStripLabel brushStyleLabel; + private ToolStripComboBox brushStyleComboBox; + + private ToolStripSeparator shapeSeparator; + private ToolStripSplitButton shapeButton; + + private ToolStripSeparator penSeparator; + private ToolStripLabel penSizeLabel; + private ToolStripButton penSizeDecButton; + private ToolStripComboBox penSizeComboBox; + private ToolStripButton penSizeIncButton; + private ToolStripLabel penStyleLabel; + private ToolStripSplitButton penStartCapSplitButton; // Tag property is used to store chosen LineCap value + private ToolStripSplitButton penDashStyleSplitButton; // Tag property is used to store chosen DashStyle value + private ToolStripSplitButton penEndCapSplitButton; // Tag property is used to store chosen LineCap value + + private EnumLocalizer lineCapLocalizer = EnumLocalizer.Create(typeof(LineCap2)); + private EnumLocalizer dashStyleLocalizer = EnumLocalizer.Create(typeof(DashStyle)); + + private ToolStripSeparator blendingSeparator; + private ToolStripSplitButton alphaBlendingSplitButton; + private bool alphaBlendingEnabled = true; + private ImageResource alphaBlendingEnabledImage; + private ImageResource alphaBlendingOverwriteImage; + + private ToolStripSplitButton antiAliasingSplitButton; + private bool antiAliasingEnabled = true; + private ImageResource antiAliasingEnabledImage; + private ImageResource antiAliasingDisabledImage; + + private EnumLocalizer resamplingAlgorithmNames = EnumLocalizer.Create(typeof(ResamplingAlgorithm)); + private ToolStripSeparator resamplingSeparator; + private ToolStripLabel resamplingLabel; + private ToolStripComboBox resamplingComboBox; + + private EnumLocalizer colorPickerBehaviorNames = EnumLocalizer.Create(typeof(ColorPickerClickBehavior)); + private ToolStripSeparator colorPickerSeparator; + private ToolStripLabel colorPickerLabel; + private ToolStripComboBox colorPickerComboBox; + + private ToolStripSeparator toleranceSeparator; + private ToolStripLabel toleranceLabel; + private ToolStripControlHost toleranceSliderStrip; + private ToleranceSliderControl toleranceSlider; + + private LineCap2[] lineCaps = + new LineCap2[] + { + LineCap2.Flat, + LineCap2.Arrow, + LineCap2.ArrowFilled, + LineCap2.Rounded + }; + + private DashStyle[] dashStyles = + new DashStyle[] + { + DashStyle.Solid, + DashStyle.Dash, + DashStyle.DashDot, + DashStyle.DashDotDot, + DashStyle.Dot + }; + + private const float minPenSize = 1.0f; + private const float maxPenSize = 500.0f; + private int[] brushSizes = + new int[] + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 20, 25, 30, + 35, 40, 45, 50, 55, 60, 65, 70, + 75, 80, 85, 90, 95, 100, 125, + 150, 175, 200, 225, 250, 275, 300, + 325, 350, 375, 400, 425, 450, 475, + 500 + }; + private ShapeDrawType shapeDrawType; + + private EnumLocalizer gradientTypeNames = EnumLocalizer.Create(typeof(GradientType)); + private GradientInfo gradientInfo = new GradientInfo(GradientType.LinearClamped, false); + private ToolStripSeparator gradientSeparator1; + private ToolStripButton gradientLinearClampedButton; + private ToolStripButton gradientLinearReflectedButton; + private ToolStripButton gradientLinearDiamondButton; + private ToolStripButton gradientRadialButton; + private ToolStripButton gradientConicalButton; + private ToolStripSeparator gradientSeparator2; + private ToolStripSplitButton gradientChannelsSplitButton; + private ImageResource gradientAllColorChannelsImage; + private ImageResource gradientAlphaChannelOnlyImage; + + private EnumLocalizer fontSmoothingLocalizer = EnumLocalizer.Create(typeof(FontSmoothing)); + private const int maxFontSize = 2000; + private const int minFontSize = 1; + private const int initialFontSize = 12; + + private FontFamily arialFontFamily; + private FontStyle fontStyle; + private TextAlignment alignment; + private float oldSizeValue; + private Brush highlightBrush; + private Brush highlightTextBrush; + private Brush windowBrush; + private Brush windowTextBrush; + private Font arialFontBase; + private const string arialName = "Arial"; + + private static ManualResetEvent staticFontNamesPopulatedEvent = new ManualResetEvent(false); + private static List staticFontNames = null; + private bool fontsComboBoxPopulated = false; + + private ToolStripSeparator fontSeparator; + private ToolStripLabel fontLabel; + private ToolStripComboBox fontFamilyComboBox; + private ToolStripComboBox fontSizeComboBox; + private ToolStripComboBox fontSmoothingComboBox; + private ToolStripSeparator fontStyleSeparator; + private ToolStripButton fontBoldButton; + private ToolStripButton fontItalicsButton; + private ToolStripButton fontUnderlineButton; + private ToolStripSeparator fontAlignSeparator; + private ToolStripButton fontAlignLeftButton; + private ToolStripButton fontAlignCenterButton; + private ToolStripButton fontAlignRightButton; + + private int[] defaultFontSizes = + new int[] + { + 8, 9, 10, 11, 12, 14, 16, 18, 20, + 22, 24, 26, 28, 36, 48, 72, 84, 96, + 108, 144, 192, 216, 288 + }; + + private ToolStripSeparator selectionCombineModeSeparator; + private ToolStripLabel selectionCombineModeLabel; + private ToolStripSplitButton selectionCombineModeSplitButton; + + private ToolStripSeparator floodModeSeparator; + private ToolStripLabel floodModeLabel; + private ToolStripSplitButton floodModeSplitButton; + + private SelectionDrawModeInfo selectionDrawModeInfo; + private ToolStripSeparator selectionDrawModeSeparator; + private ToolStripLabel selectionDrawModeModeLabel; + private ToolStripSplitButton selectionDrawModeSplitButton; + private ToolStripLabel selectionDrawModeWidthLabel; + private ToolStripTextBox selectionDrawModeWidthTextBox; + private ToolStripButton selectionDrawModeSwapButton; + private ToolStripLabel selectionDrawModeHeightLabel; + private ToolStripTextBox selectionDrawModeHeightTextBox; + private UnitsComboBoxStrip selectionDrawModeUnits; + + public event EventHandler SelectionDrawModeUnitsChanging; + protected void OnSelectionDrawModeUnitsChanging() + { + if (SelectionDrawModeUnitsChanging != null) + { + SelectionDrawModeUnitsChanging(this, EventArgs.Empty); + } + } + + public event EventHandler SelectionDrawModeUnitsChanged; + protected void OnSelectionDrawModeUnitsChanged() + { + if (SelectionDrawModeUnitsChanged != null) + { + SelectionDrawModeUnitsChanged(this, EventArgs.Empty); + } + } + + public void LoadFromAppEnvironment(AppEnvironment appEnvironment) + { + AlphaBlending = appEnvironment.AlphaBlending; + AntiAliasing = appEnvironment.AntiAliasing; + BrushInfo = appEnvironment.BrushInfo; + ColorPickerClickBehavior = appEnvironment.ColorPickerClickBehavior; + GradientInfo = appEnvironment.GradientInfo; + PenInfo = appEnvironment.PenInfo; + ResamplingAlgorithm = appEnvironment.ResamplingAlgorithm; + ShapeDrawType = appEnvironment.ShapeDrawType; + FontInfo = appEnvironment.FontInfo; + FontSmoothing = appEnvironment.FontSmoothing; + FontAlignment = appEnvironment.TextAlignment; + Tolerance = appEnvironment.Tolerance; + SelectionCombineMode = appEnvironment.SelectionCombineMode; + FloodMode = appEnvironment.FloodMode; + SelectionDrawModeInfo = appEnvironment.SelectionDrawModeInfo; + } + + public event EventHandler BrushInfoChanged; + protected virtual void OnBrushChanged() + { + if (BrushInfoChanged != null) + { + BrushInfoChanged(this, EventArgs.Empty); + } + } + + public BrushInfo BrushInfo + { + get + { + if (this.brushStyleComboBox.SelectedItem.ToString() == this.solidBrushText) + { + return new BrushInfo(BrushType.Solid, HatchStyle.BackwardDiagonal); + } + + if (this.brushStyleComboBox.SelectedIndex == -1) + { + return new BrushInfo(BrushType.Solid, HatchStyle.BackwardDiagonal); + } + else + { + return new BrushInfo( + BrushType.Hatch, + (HatchStyle)this.hatchStyleNames.LocalizedNameToEnumValue(this.brushStyleComboBox.SelectedItem.ToString())); + } + } + + set + { + if (value.BrushType == BrushType.Solid) + { + this.brushStyleComboBox.SelectedItem = this.solidBrushText; + } + else + { + this.brushStyleComboBox.SelectedItem = this.hatchStyleNames.EnumValueToLocalizedName(value.HatchStyle); + } + } + } + + public event EventHandler GradientInfoChanged; + + protected virtual void OnGradientInfoChanged() + { + if (GradientInfoChanged != null) + { + GradientInfoChanged(this, EventArgs.Empty); + } + } + + public void PerformGradientInfoChanged() + { + OnGradientInfoChanged(); + } + + public GradientInfo GradientInfo + { + get + { + return this.gradientInfo; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(); + } + + this.gradientInfo = value; + OnGradientInfoChanged(); + SyncGradientInfo(); + } + } + + private void SyncGradientInfo() + { + this.gradientConicalButton.Checked = false; + this.gradientRadialButton.Checked = false; + this.gradientLinearClampedButton.Checked = false; + this.gradientLinearReflectedButton.Checked = false; + this.gradientLinearDiamondButton.Checked = false; + + switch (this.gradientInfo.GradientType) + { + case GradientType.Conical: + this.gradientConicalButton.Checked = true; + break; + + case GradientType.LinearClamped: + this.gradientLinearClampedButton.Checked = true; + break; + + case GradientType.LinearReflected: + this.gradientLinearReflectedButton.Checked = true; + break; + + case GradientType.LinearDiamond: + this.gradientLinearDiamondButton.Checked = true; + break; + + case GradientType.Radial: + this.gradientRadialButton.Checked = true; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + if (this.gradientInfo.AlphaOnly) + { + this.gradientChannelsSplitButton.Image = this.gradientAlphaChannelOnlyImage.Reference; + } + else + { + this.gradientChannelsSplitButton.Image = this.gradientAllColorChannelsImage.Reference; + } + } + + private void ShapeButton_DropDownOpening(object sender, EventArgs e) + { + ToolStripMenuItem outlineMI = new ToolStripMenuItem(); + outlineMI.Text = PdnResources.GetString("ShapeDrawTypeConfigWidget.OutlineButton.ToolTipText"); + outlineMI.Image = this.shapeOutlineImage.Reference; + outlineMI.Tag = (object)ShapeDrawType.Outline; + outlineMI.Click += new EventHandler(ShapeMI_Click); + + ToolStripMenuItem interiorMI = new ToolStripMenuItem(); + interiorMI.Text = PdnResources.GetString("ShapeDrawTypeConfigWidget.InteriorButton.ToolTipText"); + interiorMI.Image = this.shapeInteriorImage.Reference; + interiorMI.Tag = (object)ShapeDrawType.Interior; + interiorMI.Click += new EventHandler(ShapeMI_Click); + + ToolStripMenuItem bothMI = new ToolStripMenuItem(); + bothMI.Text = PdnResources.GetString("ShapeDrawTypeConfigWidget.BothButton.ToolTipText"); + bothMI.Image = this.shapeBothImage.Reference; + bothMI.Tag = (object)ShapeDrawType.Both; + bothMI.Click += new EventHandler(ShapeMI_Click); + + switch (this.shapeDrawType) + { + case ShapeDrawType.Outline: + outlineMI.Checked = true; + break; + + case ShapeDrawType.Interior: + interiorMI.Checked = true; + break; + + case ShapeDrawType.Both: + bothMI.Checked = true; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + this.shapeButton.DropDownItems.AddRange( + new ToolStripItem[] + { + outlineMI, + interiorMI, + bothMI + }); + } + + private void ShapeMI_Click(object sender, EventArgs e) + { + ShapeDrawType sdt = (ShapeDrawType)((ToolStripMenuItem)sender).Tag; + Tracing.LogFeature("ToolConfigStrip(" + sdt.ToString() + ")"); + this.ShapeDrawType = sdt; + } + + public ToolConfigStrip() + { + SuspendLayout(); + InitializeComponent(); + + this.solidBrushText = PdnResources.GetString("BrushConfigWidget.SolidBrush.Text"); // "Solid Brush" + this.brushStyleComboBox.Items.Add(this.solidBrushText); + string[] styleNames = this.hatchStyleNames.GetLocalizedNames(); + Array.Sort(styleNames); + + foreach (string styleName in styleNames) + { + brushStyleComboBox.Items.Add(styleName); + } + + brushStyleComboBox.SelectedIndex = 0; + + this.brushStyleLabel.Text = PdnResources.GetString("BrushConfigWidget.FillStyleLabel.Text"); + + this.shapeDrawType = ShapeDrawType.Outline; + this.shapeButton.Image = this.shapeOutlineImage.Reference; + + this.penSizeLabel.Text = PdnResources.GetString("PenConfigWidget.BrushWidthLabel"); + + this.penSizeComboBox.ComboBox.SuspendLayout(); + + for (int i = 0; i < this.brushSizes.Length; ++i) + { + this.penSizeComboBox.Items.Add(this.brushSizes[i].ToString()); + } + + this.penSizeComboBox.ComboBox.ResumeLayout(false); + this.penSizeComboBox.SelectedIndex = 1; // default to brush size of 2 + + this.penSizeDecButton.ToolTipText = PdnResources.GetString("ToolConfigStrip.PenSizeDecButton.ToolTipText"); + this.penSizeDecButton.Image = PdnResources.GetImageResource("Icons.MinusButtonIcon.png").Reference; + this.penSizeIncButton.ToolTipText = PdnResources.GetString("ToolConfigStrip.PenSizeIncButton.ToolTipText"); + this.penSizeIncButton.Image = PdnResources.GetImageResource("Icons.PlusButtonIcon.png").Reference; + this.penStyleLabel.Text = PdnResources.GetString("ToolConfigStrip.PenStyleLabel.Text"); + this.penStartCapSplitButton.Tag = PenInfo.DefaultLineCap; + this.penStartCapSplitButton.Image = GetLineCapImage(PenInfo.DefaultLineCap, true).Reference; + this.penStartCapSplitButton.ToolTipText = PdnResources.GetString("ToolConfigStrip.PenStartCapSplitButton.ToolTipText"); + this.penDashStyleSplitButton.Tag = PenInfo.DefaultDashStyle; + this.penDashStyleSplitButton.Image = GetDashStyleImage(PenInfo.DefaultDashStyle); + this.penDashStyleSplitButton.ToolTipText = PdnResources.GetString("ToolConfigStrip.PenDashStyleSplitButton.ToolTipText"); + this.penEndCapSplitButton.Tag = PenInfo.DefaultLineCap; + this.penEndCapSplitButton.Image = GetLineCapImage(PenInfo.DefaultLineCap, false).Reference; + this.penEndCapSplitButton.ToolTipText = PdnResources.GetString("ToolConfigStrip.PenEndCapSplitButton.ToolTipText"); + + this.gradientLinearClampedButton.ToolTipText = this.gradientTypeNames.EnumValueToLocalizedName(GradientType.LinearClamped); + this.gradientLinearClampedButton.Image = PdnResources.GetImageResource("Icons.LinearClampedGradientIcon.png").Reference; + this.gradientLinearReflectedButton.ToolTipText = this.gradientTypeNames.EnumValueToLocalizedName(GradientType.LinearReflected); + this.gradientLinearReflectedButton.Image = PdnResources.GetImageResource("Icons.LinearReflectedGradientIcon.png").Reference; + this.gradientLinearDiamondButton.ToolTipText = this.gradientTypeNames.EnumValueToLocalizedName(GradientType.LinearDiamond); + this.gradientLinearDiamondButton.Image = PdnResources.GetImageResource("Icons.LinearDiamondGradientIcon.png").Reference; + this.gradientRadialButton.ToolTipText = this.gradientTypeNames.EnumValueToLocalizedName(GradientType.Radial); + this.gradientRadialButton.Image = PdnResources.GetImageResource("Icons.RadialGradientIcon.png").Reference; + this.gradientConicalButton.ToolTipText = this.gradientTypeNames.EnumValueToLocalizedName(GradientType.Conical); + this.gradientConicalButton.Image = PdnResources.GetImageResource("Icons.ConicalGradientIcon.png").Reference; + + this.gradientAllColorChannelsImage = PdnResources.GetImageResource("Icons.AllColorChannelsIcon.png"); + this.gradientAlphaChannelOnlyImage = PdnResources.GetImageResource("Icons.AlphaChannelOnlyIcon.png"); + this.gradientChannelsSplitButton.Image = this.gradientAllColorChannelsImage.Reference; + + this.antiAliasingEnabledImage = PdnResources.GetImageResource("Icons.AntiAliasingEnabledIcon.png"); + this.antiAliasingDisabledImage = PdnResources.GetImageResource("Icons.AntiAliasingDisabledIcon.png"); + this.antiAliasingSplitButton.Image = this.antiAliasingEnabledImage.Reference; + + this.alphaBlendingEnabledImage = PdnResources.GetImageResource("Icons.BlendingEnabledIcon.png"); + this.alphaBlendingOverwriteImage = PdnResources.GetImageResource("Icons.BlendingOverwriteIcon.png"); + this.alphaBlendingSplitButton.Image = this.alphaBlendingEnabledImage.Reference; + + this.penSizeComboBox.Size = new Size(UI.ScaleWidth(this.penSizeComboBox.Width), penSizeComboBox.Height); + this.brushStyleComboBox.Size = new Size(UI.ScaleWidth(this.brushStyleComboBox.Width), brushStyleComboBox.Height); + this.brushStyleComboBox.DropDownWidth = UI.ScaleWidth(this.brushStyleComboBox.DropDownWidth); + this.brushStyleComboBox.DropDownHeight = UI.ScaleHeight(this.brushStyleComboBox.DropDownHeight); + + this.toleranceLabel.Text = PdnResources.GetString("ToleranceConfig.ToleranceLabel.Text"); + this.toleranceSlider.Tolerance = 0.5f; + + this.fontSizeComboBox.ComboBox.SuspendLayout(); + for (int i = 0; i < this.defaultFontSizes.Length; ++i) + { + this.fontSizeComboBox.Items.Add(this.defaultFontSizes[i].ToString()); + } + this.fontSizeComboBox.ComboBox.ResumeLayout(false); + + this.fontSmoothingComboBox.Items.AddRange( + new object[] + { + this.fontSmoothingLocalizer.EnumValueToLocalizedName(FontSmoothing.Smooth), + this.fontSmoothingLocalizer.EnumValueToLocalizedName(FontSmoothing.Sharp) + }); + + this.fontSmoothingComboBox.SelectedIndex = 0; + + this.fontLabel.Text = PdnResources.GetString("TextConfigWidget.FontLabel.Text"); + + try + { + this.arialFontFamily = new FontFamily(arialName); + } + + catch (Exception) + { + this.arialFontFamily = new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); + } + + try + { + this.arialFontBase = new Font(arialFontFamily, initialFontSize, FontStyle.Regular); + } + + catch (Exception) + { + this.arialFontBase = new Font(FontFamily.GenericSansSerif, initialFontSize, FontStyle.Regular); + } + + this.fontFamilyComboBox.ComboBox.DropDownHeight = 600; + + this.alignment = TextAlignment.Left; + this.fontAlignLeftButton.Checked = true; + this.oldSizeValue = initialFontSize; + + this.highlightBrush = new SolidBrush(SystemColors.Highlight); + this.highlightTextBrush = new SolidBrush(SystemColors.HighlightText); + this.windowBrush = new SolidBrush(SystemColors.Window); + this.windowTextBrush = new SolidBrush(SystemColors.WindowText); + + // These buttons need a color key to maintain consistency with v2.5 language packs + this.fontBoldButton.ImageTransparentColor = Utility.TransparentKey; + this.fontItalicsButton.ImageTransparentColor = Utility.TransparentKey; + this.fontUnderlineButton.ImageTransparentColor = Utility.TransparentKey; + + this.fontBoldButton.Image = PdnResources.GetImageBmpOrPng("Icons.FontBoldIcon"); + this.fontItalicsButton.Image = PdnResources.GetImageBmpOrPng("Icons.FontItalicIcon"); + this.fontUnderlineButton.Image = PdnResources.GetImageBmpOrPng("Icons.FontUnderlineIcon"); + + this.fontAlignLeftButton.Image = PdnResources.GetImageResource("Icons.TextAlignLeftIcon.png").Reference; + this.fontAlignCenterButton.Image = PdnResources.GetImageResource("Icons.TextAlignCenterIcon.png").Reference; + this.fontAlignRightButton.Image = PdnResources.GetImageResource("Icons.TextAlignRightIcon.png").Reference; + + this.fontBoldButton.ToolTipText = PdnResources.GetString("TextConfigWidget.BoldButton.ToolTipText"); + this.fontItalicsButton.ToolTipText = PdnResources.GetString("TextConfigWidget.ItalicButton.ToolTipText"); + this.fontUnderlineButton.ToolTipText = PdnResources.GetString("TextConfigWidget.UnderlineButton.ToolTipText"); + this.fontAlignLeftButton.ToolTipText = PdnResources.GetString("TextConfigWidget.AlignLeftButton.ToolTipText"); + this.fontAlignCenterButton.ToolTipText = PdnResources.GetString("TextConfigWidget.AlignCenterButton.ToolTipText"); + this.fontAlignRightButton.ToolTipText = PdnResources.GetString("TextConfigWidget.AlignRightButton.ToolTipText"); + + this.fontFamilyComboBox.Size = new Size(UI.ScaleWidth(this.fontFamilyComboBox.Width), fontFamilyComboBox.Height); + this.fontFamilyComboBox.DropDownWidth = UI.ScaleWidth(this.fontFamilyComboBox.DropDownWidth); + this.fontSizeComboBox.Size = new Size(UI.ScaleWidth(this.fontSizeComboBox.Width), fontSizeComboBox.Height); + + this.fontSmoothingComboBox.Size = new Size(UI.ScaleWidth(this.fontSmoothingComboBox.Width), fontSmoothingComboBox.Height); + this.fontSmoothingComboBox.DropDownWidth = UI.ScaleWidth(this.fontSmoothingComboBox.DropDownWidth); + + this.resamplingLabel.Text = PdnResources.GetString("ToolConfigStrip.ResamplingLabel.Text"); + this.resamplingComboBox.BeginUpdate(); + this.resamplingComboBox.Items.Add(this.resamplingAlgorithmNames.EnumValueToLocalizedName(ResamplingAlgorithm.Bilinear)); + this.resamplingComboBox.Items.Add(this.resamplingAlgorithmNames.EnumValueToLocalizedName(ResamplingAlgorithm.NearestNeighbor)); + this.resamplingComboBox.EndUpdate(); + this.resamplingComboBox.SelectedIndex = 0; // bilinear + + this.resamplingComboBox.Size = new Size(UI.ScaleWidth(this.resamplingComboBox.Width), resamplingComboBox.Height); + this.resamplingComboBox.DropDownWidth = UI.ScaleWidth(this.resamplingComboBox.DropDownWidth); + + this.colorPickerLabel.Text = PdnResources.GetString("ToolConfigStrip.ColorPickerLabel.Text"); + string[] colorPickerBehaviorNames = this.colorPickerBehaviorNames.GetLocalizedNames(); + + // Make sure these items are sorted to be in the order specified by the enumeration + Array.Sort( + colorPickerBehaviorNames, + delegate(string lhs, string rhs) + { + ColorPickerClickBehavior lhsE = (ColorPickerClickBehavior)this.colorPickerBehaviorNames.LocalizedNameToEnumValue(lhs); + ColorPickerClickBehavior rhsE = (ColorPickerClickBehavior)this.colorPickerBehaviorNames.LocalizedNameToEnumValue(rhs); + + if ((int)lhsE < (int)rhsE) + { + return -1; + } + else if ((int)lhsE > (int)rhsE) + { + return +1; + } + else + { + return 0; + } + }); + + this.colorPickerComboBox.Items.AddRange(colorPickerBehaviorNames); + this.colorPickerComboBox.SelectedIndex = 0; + + this.colorPickerComboBox.Size = new Size(UI.ScaleWidth(this.colorPickerComboBox.Width), colorPickerComboBox.Height); + this.colorPickerComboBox.DropDownWidth = UI.ScaleWidth(this.colorPickerComboBox.DropDownWidth); + + this.toleranceSlider.Size = UI.ScaleSize(this.toleranceSlider.Size); + + this.selectionCombineModeLabel.Text = PdnResources.GetString("ToolConfigStrip.SelectionCombineModeLabel.Text"); + + this.floodModeLabel.Text = PdnResources.GetString("ToolConfigStrip.FloodModeLabel.Text"); + + this.selectionDrawModeModeLabel.Text = PdnResources.GetString("ToolConfigStrip.SelectionDrawModeLabel.Text"); + this.selectionDrawModeWidthLabel.Text = PdnResources.GetString("ToolConfigStrip.SelectionDrawModeWidthLabel.Text"); + this.selectionDrawModeHeightLabel.Text = PdnResources.GetString("ToolConfigStrip.SelectionDrawModeHeightLabel.Text"); + this.selectionDrawModeSwapButton.Image = PdnResources.GetImageResource("Icons.ToolConfigStrip.SelectionDrawModeSwapButton.png").Reference; + + this.selectionDrawModeWidthTextBox.Size = new Size(UI.ScaleWidth(this.selectionDrawModeWidthTextBox.Width), this.selectionDrawModeWidthTextBox.Height); + this.selectionDrawModeHeightTextBox.Size = new Size(UI.ScaleWidth(this.selectionDrawModeHeightTextBox.Width), this.selectionDrawModeHeightTextBox.Height); + this.selectionDrawModeUnits.Size = new Size(UI.ScaleWidth(this.selectionDrawModeUnits.Width), this.selectionDrawModeUnits.Height); + + ToolBarConfigItems = ToolBarConfigItems.None; + ResumeLayout(false); + } + + private void AsyncInitFontNames() + { + if (!IsHandleCreated) + { + CreateControl(); + } + + if (!this.fontFamilyComboBox.ComboBox.IsHandleCreated) + { + this.fontFamilyComboBox.ComboBox.CreateControl(); + } + + if (staticFontNames == null) + { + ThreadPool.QueueUserWorkItem(new WaitCallback(this.PopulateFontsBackgroundThread), null); + } + } + + protected override void OnHandleCreated(EventArgs e) + { + if ((this.toolBarConfigItems & ToolBarConfigItems.Text) == ToolBarConfigItems.Text) + { + AsyncInitFontNames(); + } + + base.OnHandleCreated(e); + } + + private void InitializeComponent() + { + this.brushSeparator = new ToolStripSeparator(); + this.brushStyleLabel = new ToolStripLabel(); + this.brushStyleComboBox = new ToolStripComboBox(); + + this.shapeSeparator = new ToolStripSeparator(); + this.shapeButton = new ToolStripSplitButton(); + + this.gradientSeparator1 = new ToolStripSeparator(); + this.gradientLinearClampedButton = new ToolStripButton(); + this.gradientLinearReflectedButton = new ToolStripButton(); + this.gradientLinearDiamondButton = new ToolStripButton(); + this.gradientRadialButton = new ToolStripButton(); + this.gradientConicalButton = new ToolStripButton(); + this.gradientSeparator2 = new ToolStripSeparator(); + this.gradientChannelsSplitButton = new ToolStripSplitButton(); + + this.penSeparator = new ToolStripSeparator(); + this.penSizeLabel = new ToolStripLabel(); + this.penSizeDecButton = new ToolStripButton(); + this.penSizeComboBox = new ToolStripComboBox(); + this.penSizeIncButton = new ToolStripButton(); + this.penStyleLabel = new ToolStripLabel(); + this.penStartCapSplitButton = new ToolStripSplitButton(); + this.penDashStyleSplitButton = new ToolStripSplitButton(); + this.penEndCapSplitButton = new ToolStripSplitButton(); + + this.blendingSeparator = new ToolStripSeparator(); + this.antiAliasingSplitButton = new ToolStripSplitButton(); + this.alphaBlendingSplitButton = new ToolStripSplitButton(); + + this.toleranceSeparator = new ToolStripSeparator(); + this.toleranceLabel = new ToolStripLabel(); + this.toleranceSlider = new ToleranceSliderControl(); + this.toleranceSliderStrip = new ToolStripControlHost(this.toleranceSlider); + + this.fontSeparator = new ToolStripSeparator(); + this.fontLabel = new ToolStripLabel(); + this.fontFamilyComboBox = new ToolStripComboBox(); + this.fontSizeComboBox = new ToolStripComboBox(); + this.fontSmoothingComboBox = new ToolStripComboBox(); + this.fontStyleSeparator = new ToolStripSeparator(); + this.fontBoldButton = new ToolStripButton(); + this.fontItalicsButton = new ToolStripButton(); + this.fontUnderlineButton = new ToolStripButton(); + this.fontAlignSeparator = new ToolStripSeparator(); + this.fontAlignLeftButton = new ToolStripButton(); + this.fontAlignCenterButton = new ToolStripButton(); + this.fontAlignRightButton = new ToolStripButton(); + + this.resamplingSeparator = new ToolStripSeparator(); + this.resamplingLabel = new ToolStripLabel(); + this.resamplingComboBox = new ToolStripComboBox(); + + this.colorPickerSeparator = new ToolStripSeparator(); + this.colorPickerLabel = new ToolStripLabel(); + this.colorPickerComboBox = new ToolStripComboBox(); + + this.selectionCombineModeSeparator = new ToolStripSeparator(); + this.selectionCombineModeLabel = new ToolStripLabel(); + this.selectionCombineModeSplitButton = new ToolStripSplitButton(); + + this.floodModeSeparator = new ToolStripSeparator(); + this.floodModeLabel = new ToolStripLabel(); + this.floodModeSplitButton = new ToolStripSplitButton(); + + this.selectionDrawModeSeparator = new ToolStripSeparator(); + this.selectionDrawModeModeLabel = new ToolStripLabel(); + this.selectionDrawModeSplitButton = new ToolStripSplitButton(); + this.selectionDrawModeWidthLabel = new ToolStripLabel(); + this.selectionDrawModeWidthTextBox = new ToolStripTextBox(); + this.selectionDrawModeSwapButton = new ToolStripButton(); + this.selectionDrawModeHeightLabel = new ToolStripLabel(); + this.selectionDrawModeHeightTextBox = new ToolStripTextBox(); + this.selectionDrawModeUnits = new UnitsComboBoxStrip(); + + this.SuspendLayout(); + // + // brushStyleLabel + // + this.brushStyleLabel.Name = "fillStyleLabel"; + // + // brushStyleComboBox + // + this.brushStyleComboBox.Name = "styleComboBox"; + this.brushStyleComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.brushStyleComboBox.DropDownWidth = 234; + this.brushStyleComboBox.AutoSize = true; + // + // brushStyleComboBox.ComboBox + // + this.brushStyleComboBox.ComboBox.DrawMode = DrawMode.OwnerDrawVariable; + this.brushStyleComboBox.ComboBox.MeasureItem += ComboBoxStyle_MeasureItem; + this.brushStyleComboBox.ComboBox.SelectedValueChanged += ComboBoxStyle_SelectedValueChanged; + this.brushStyleComboBox.ComboBox.DrawItem += ComboBoxStyle_DrawItem; + // + // shapeButton + // + this.shapeButton.Name = "shapeButton"; + this.shapeButton.DropDownOpening += new EventHandler(ShapeButton_DropDownOpening); + + this.shapeButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.shapeButton.DropDownItems.Clear(); + }; + + this.shapeButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(shapeButton)"); + + switch (ShapeDrawType) + { + case ShapeDrawType.Outline: + ShapeDrawType = ShapeDrawType.Interior; + break; + + case ShapeDrawType.Interior: + ShapeDrawType = ShapeDrawType.Both; + break; + + case ShapeDrawType.Both: + ShapeDrawType = ShapeDrawType.Outline; + break; + + default: + throw new InvalidEnumArgumentException(); + } + }; + // + // gradientSeparator + // + this.gradientSeparator1.Name = "gradientSeparator"; + // + // gradientLinearClampedButton + // + this.gradientLinearClampedButton.Name = "gradientLinearClampedButton"; + this.gradientLinearClampedButton.Click += GradientTypeButtonClicked; + this.gradientLinearClampedButton.Tag = GradientType.LinearClamped; + // + // gradientLinearReflectedButton + // + this.gradientLinearReflectedButton.Name = "gradientLinearReflectedButton"; + this.gradientLinearReflectedButton.Click += GradientTypeButtonClicked; + this.gradientLinearReflectedButton.Tag = GradientType.LinearReflected; + // + // gradientLinearDiamondButton + // + this.gradientLinearDiamondButton.Name = "gradientLinearDiamondButton"; + this.gradientLinearDiamondButton.Click += GradientTypeButtonClicked; + this.gradientLinearDiamondButton.Tag = GradientType.LinearDiamond; + // + // gradientRadialButton + // + this.gradientRadialButton.Name = "gradientRadialButton"; + this.gradientRadialButton.Click += GradientTypeButtonClicked; + this.gradientRadialButton.Tag = GradientType.Radial; + // + // gradientConicalButton + // + this.gradientConicalButton.Name = "gradientConicalButton"; + this.gradientConicalButton.Click += GradientTypeButtonClicked; + this.gradientConicalButton.Tag = GradientType.Conical; + // + // gradientSeparator2 + // + this.gradientSeparator2.Name = "gradientSeparator2"; + // + // gradientChannelsSplitButton + // + this.gradientChannelsSplitButton.Name = "gradientChannelsSplitButton"; + this.gradientChannelsSplitButton.DropDownOpening += new EventHandler(GradientChannelsSplitButton_DropDownOpening); + this.gradientChannelsSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.gradientChannelsSplitButton.DropDownItems.Clear(); + }; + this.gradientChannelsSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(gradientChannelsSplitButton)"); + GradientInfo = new GradientInfo(GradientInfo.GradientType, !GradientInfo.AlphaOnly); + }; + // + // penSeparator + // + this.penSeparator.Name = "penSeparator"; + // + // penSizeLabel + // + this.penSizeLabel.Name = "brushSizeLabel"; + // + // penSizeDecButton + // + this.penSizeDecButton.Name = "penSizeDecButton"; + this.penSizeDecButton.Click += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(penSizeDecButton)"); + + float amount = -1.0f; + + if ((Control.ModifierKeys & Keys.Control) != 0) + { + amount *= 5.0f; + } + + AddToPenSize(amount); + }; + // + // penSizeComboBox + // + this.penSizeComboBox.Name = "penSizeComboBox"; + this.penSizeComboBox.Validating += new CancelEventHandler(this.BrushSizeComboBox_Validating); + this.penSizeComboBox.TextChanged += new EventHandler(this.SizeComboBox_TextChanged); + this.penSizeComboBox.AutoSize = false; + this.penSizeComboBox.Width = 44; + // + // penSizeIncButton + // + this.penSizeIncButton.Name = "penSizeIncButton"; + this.penSizeIncButton.Click += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(penSizeIncButton)"); + + float amount = 1.0f; + + if ((Control.ModifierKeys & Keys.Control) != 0) + { + amount *= 5.0f; + } + + AddToPenSize(amount); + }; + // + // penStartCapLabel + // + this.penStyleLabel.Name = "penStartCapLabel"; + // + // penStartCapSplitButton + // + this.penStartCapSplitButton.Name = "penStartCapSplitButton"; + this.penStartCapSplitButton.DropDownOpening += PenCapSplitButton_DropDownOpening; + this.penStartCapSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.penStartCapSplitButton.DropDownItems.Clear(); + }; + this.penStartCapSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(penStartCapSplitButton)"); + CyclePenStartCap(); + }; + // + // penDashStyleSplitButton + // + this.penDashStyleSplitButton.Name = "penDashStyleSplitButton"; + this.penDashStyleSplitButton.ImageScaling = ToolStripItemImageScaling.None; + this.penDashStyleSplitButton.DropDownOpening += PenDashStyleButton_DropDownOpening; + this.penDashStyleSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.penDashStyleSplitButton.DropDownItems.Clear(); + }; + this.penDashStyleSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(penDashStyleSplitButton)"); + CyclePenDashStyle(); + }; + // + // penEndCapSplitButton + // + this.penEndCapSplitButton.Name = "penEndCapSplitButton"; + this.penEndCapSplitButton.DropDownOpening += PenCapSplitButton_DropDownOpening; + this.penEndCapSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.penEndCapSplitButton.DropDownItems.Clear(); + }; + this.penEndCapSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(penEndCapSplitButton)"); + CyclePenEndCap(); + }; + // + // antiAliasingSplitButton + // + this.antiAliasingSplitButton.Name = "antiAliasingSplitButton"; + this.antiAliasingSplitButton.DisplayStyle = ToolStripItemDisplayStyle.Image; + this.antiAliasingSplitButton.DropDownOpening += AntiAliasingSplitButton_DropDownOpening; + this.antiAliasingSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.antiAliasingSplitButton.DropDownItems.Clear(); + }; + this.antiAliasingSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(antiAliasingSplitButton)"); + AntiAliasing = !AntiAliasing; + }; + // + // alphaBlendingSplitButton + // + this.alphaBlendingSplitButton.Name = "alphaBlendingSplitButton"; + this.alphaBlendingSplitButton.DisplayStyle = ToolStripItemDisplayStyle.Image; + this.alphaBlendingSplitButton.DropDownOpening += new EventHandler(AlphaBlendingSplitButton_DropDownOpening); + this.alphaBlendingSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.alphaBlendingSplitButton.DropDownItems.Clear(); + }; + this.alphaBlendingSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(alphaBlendingSplitButton)"); + AlphaBlending = !AlphaBlending; + }; + // + // toleranceLabel + // + this.toleranceLabel.Name = "toleranceLabel"; + // + // toleranceSlider + // + this.toleranceSlider.Name = "toleranceSlider"; + this.toleranceSlider.ToleranceChanged += new EventHandler(ToleranceSlider_ToleranceChanged); + this.toleranceSlider.Size = new Size(150, 16); + // + // toleranceSliderStrip + // + this.toleranceSliderStrip.Name = "toleranceSliderStrip"; + this.toleranceSliderStrip.AutoSize = false; + // + // fontLabel + // + this.fontLabel.Name = "fontLabel"; + // + // fontFamilyComboBox + // + this.fontFamilyComboBox.Name = "fontComboBox"; + this.fontFamilyComboBox.DropDownWidth = 240; + this.fontFamilyComboBox.MaxDropDownItems = 12; + this.fontFamilyComboBox.Sorted = true; + this.fontFamilyComboBox.GotFocus += new EventHandler(FontFamilyComboBox_GotFocus); + this.fontFamilyComboBox.Items.Add(arialName); + this.fontFamilyComboBox.SelectedItem = arialName; + this.fontFamilyComboBox.SelectedIndexChanged += FontFamilyComboBox_SelectedIndexChanged; + this.fontFamilyComboBox.DropDownStyle = ComboBoxStyle.DropDownList; + // + // fontFamilyComboBox.ComboBox + // + this.fontFamilyComboBox.ComboBox.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; + this.fontFamilyComboBox.ComboBox.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(this.FontFamilyComboBox_MeasureItem); + this.fontFamilyComboBox.ComboBox.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.FontFamilyComboBox_DrawItem); + // + // fontSizeComboBox + // + this.fontSizeComboBox.Name = "fontSizeComboBox"; + this.fontSizeComboBox.AutoSize = false; + this.fontSizeComboBox.TextChanged += new EventHandler(FontSizeComboBox_TextChanged); + this.fontSizeComboBox.Validating += new CancelEventHandler(FontSizeComboBox_Validating); + this.fontSizeComboBox.Text = initialFontSize.ToString(); + this.fontSizeComboBox.Width = 44; + // + // fontSmoothingComboBox + // + this.fontSmoothingComboBox.Name = "smoothingComboBOx"; + this.fontSmoothingComboBox.AutoSize = false; + this.fontSmoothingComboBox.Sorted = false; + this.fontSmoothingComboBox.Width = 70; + this.fontSmoothingComboBox.DropDownStyle = ComboBoxStyle.DropDownList; + this.fontSmoothingComboBox.SelectedIndexChanged += new EventHandler(SmoothingComboBox_SelectedIndexChanged); + // + // fontBoldButton + // + this.fontBoldButton.Name = "boldButton"; + // + // fontItalicsButton + // + this.fontItalicsButton.Name = "italicsButton"; + // + // fontUnderlineButton + // + this.fontUnderlineButton.Name = "underlineButton"; + // + // fontAlignLeftButton + // + this.fontAlignLeftButton.Name = "alignLeftButton"; + // + // fontAlignCenterButton + // + this.fontAlignCenterButton.Name = "alignCenterButton"; + // + // fontAlignRightButton + // + this.fontAlignRightButton.Name = "alignRightButton"; + // + // resamplingSeparator + // + this.resamplingSeparator.Name = "resamplingSeparator"; + // + // resamplingLabel + // + this.resamplingLabel.Name = "resamplingLabel"; + // + // resamplingComboBox + // + this.resamplingComboBox.Name = "resamplingComboBox"; + this.resamplingComboBox.AutoSize = true; + this.resamplingComboBox.DropDownStyle = ComboBoxStyle.DropDownList; + this.resamplingComboBox.Sorted = false; + this.resamplingComboBox.Width = 100; + this.resamplingComboBox.DropDownWidth = 100; + this.resamplingComboBox.SelectedIndexChanged += new EventHandler(ResamplingComboBox_SelectedIndexChanged); + // + // colorPickerSeparator + // + this.colorPickerSeparator.Name = "colorPickerSeparator"; + // + // colorPickerLabel + // + this.colorPickerLabel.Name = "colorPickerLabel"; + // + // colorPickerComboBox + // + this.colorPickerComboBox.Name = "colorPickerComboBox"; + this.colorPickerComboBox.AutoSize = true; + this.colorPickerComboBox.DropDownStyle = ComboBoxStyle.DropDownList; + this.colorPickerComboBox.Width = 200; + this.colorPickerComboBox.DropDownWidth = 200; + this.colorPickerComboBox.Sorted = false; + this.colorPickerComboBox.SelectedIndexChanged += new EventHandler(ColorPickerComboBox_SelectedIndexChanged); + // + // selectionCombineModeSeparator + // + this.selectionCombineModeSeparator.Name = "selectionCombineModeSeparator"; + // + // selectionCombineModeLabel + // + this.selectionCombineModeLabel.Name = "selectionCombineModeLabel"; + // + // selectionCombineModeSplitButton + // + this.selectionCombineModeSplitButton.Name = "selectionCombineModeSplitButton"; + this.selectionCombineModeSplitButton.DisplayStyle = ToolStripItemDisplayStyle.Image; + this.selectionCombineModeSplitButton.DropDownOpening += new EventHandler(SelectionCombineModeSplitButton_DropDownOpening); + this.selectionCombineModeSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.selectionCombineModeSplitButton.DropDownItems.Clear(); + }; + this.selectionCombineModeSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(selectionCombineModeSplitButton)"); + this.SelectionCombineMode = CycleSelectionCombineMode(this.SelectionCombineMode); + }; + // + // floodModeSeparator + // + this.floodModeSeparator.Name = "floodModeSeparator"; + // + // floodModeLabel + // + this.floodModeLabel.Name = "floodModeLabel"; + // + // floodModeSplitButton + // + this.floodModeSplitButton.Name = "floodModeSplitButton"; + this.floodModeSplitButton.DisplayStyle = ToolStripItemDisplayStyle.Image; + this.floodModeSplitButton.DropDownOpening += new EventHandler(FloodModeSplitButton_DropDownOpening); + this.floodModeSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.floodModeSplitButton.DropDownItems.Clear(); + }; + this.floodModeSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(floodModeSplitButton)"); + this.FloodMode = CycleFloodMode(this.FloodMode); + }; + // + // selectionDrawModeSeparator + // + this.selectionDrawModeSeparator.Name = "selectionDrawModeSeparator"; + // + // selectionDrawModeModeLabel + // + this.selectionDrawModeModeLabel.Name = "selectionDrawModeModeLabel"; + // + // selectionDrawModeSplitButton + // + this.selectionDrawModeSplitButton.Name = "selectionDrawModeSplitButton"; + this.selectionDrawModeSplitButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText; + this.selectionDrawModeSplitButton.DropDownOpening += new EventHandler(SelectionDrawModeSplitButton_DropDownOpening); + this.selectionDrawModeSplitButton.DropDownClosed += + delegate(object sender, EventArgs e) + { + this.selectionDrawModeSplitButton.DropDownItems.Clear(); + }; + this.selectionDrawModeSplitButton.ButtonClick += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(selectionDrawModeSplitButton)"); + SelectionDrawMode newSDM = CycleSelectionDrawMode(this.SelectionDrawModeInfo.DrawMode); + this.SelectionDrawModeInfo = this.SelectionDrawModeInfo.CloneWithNewDrawMode(newSDM); + }; + // + // selectionDrawModeWidthLabel + // + this.selectionDrawModeWidthLabel.Name = "selectionDrawModeWidthLabel"; + // + // selectionDrawModeWidthTextBox + // + this.selectionDrawModeWidthTextBox.Name = "selectionDrawModeWidthTextBox"; + this.selectionDrawModeWidthTextBox.TextBox.Width = 50; + this.selectionDrawModeWidthTextBox.TextBoxTextAlign = HorizontalAlignment.Right; + this.selectionDrawModeWidthTextBox.Enter += + delegate(object sender, EventArgs e) + { + this.selectionDrawModeWidthTextBox.TextBox.Select(0, this.selectionDrawModeWidthTextBox.TextBox.Text.Length); + }; + this.selectionDrawModeWidthTextBox.Leave += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(selectionDrawModeWidthTextBox.Leave)"); + + double newWidth; + if (double.TryParse(this.selectionDrawModeWidthTextBox.Text, out newWidth)) + { + this.SelectionDrawModeInfo = this.selectionDrawModeInfo.CloneWithNewWidth(newWidth); + } + else + { + this.selectionDrawModeWidthTextBox.Text = this.selectionDrawModeInfo.Width.ToString(); + } + }; + // + // selectionDrawModeSwapButton + // + this.selectionDrawModeSwapButton.Name = "selectionDrawModeSwapButton"; + this.selectionDrawModeSwapButton.Click += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(selectionDrawModeSwapButton)"); + + SelectionDrawModeInfo oldSDMI = this.SelectionDrawModeInfo; + SelectionDrawModeInfo newSDMI = new SelectionDrawModeInfo(oldSDMI.DrawMode, oldSDMI.Height, oldSDMI.Width, oldSDMI.Units); + this.SelectionDrawModeInfo = newSDMI; + }; + // + // selectionDrawModeHeightLabel + // + this.selectionDrawModeHeightLabel.Name = "selectionDrawModeHeightLabel"; + // + // selectionDrawModeHeightTextBox + // + this.selectionDrawModeHeightTextBox.Name = "selectionDrawModeHeightTextBox"; + this.selectionDrawModeHeightTextBox.TextBox.Width = 50; + this.selectionDrawModeHeightTextBox.TextBoxTextAlign = HorizontalAlignment.Right; + this.selectionDrawModeHeightTextBox.Enter += + delegate(object sender, EventArgs e) + { + this.selectionDrawModeHeightTextBox.TextBox.Select(0, this.selectionDrawModeHeightTextBox.TextBox.Text.Length); + }; + this.selectionDrawModeHeightTextBox.Leave += + delegate(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(selectionDrawModeHeightTextBox.Leave)"); + + double newHeight; + if (double.TryParse(this.selectionDrawModeHeightTextBox.Text, out newHeight)) + { + this.SelectionDrawModeInfo = this.selectionDrawModeInfo.CloneWithNewHeight(newHeight); + } + else + { + this.selectionDrawModeHeightTextBox.Text = this.selectionDrawModeInfo.Height.ToString(); + } + }; + // + // selectionDrawModeUnits + // + this.selectionDrawModeUnits.Name = "selectionDrawModeUnits"; + this.selectionDrawModeUnits.UnitsDisplayType = UnitsDisplayType.Plural; + this.selectionDrawModeUnits.LowercaseStrings = true; + this.selectionDrawModeUnits.Size = new Size(90, this.selectionDrawModeUnits.Height); + // + // DrawConfigStrip + // + this.AutoSize = true; + + this.Items.AddRange( + new ToolStripItem[] + { + this.selectionCombineModeSeparator, + this.selectionCombineModeLabel, + this.selectionCombineModeSplitButton, + + this.selectionDrawModeSeparator, + this.selectionDrawModeModeLabel, + this.selectionDrawModeSplitButton, + this.selectionDrawModeWidthLabel, + this.selectionDrawModeWidthTextBox, + this.selectionDrawModeSwapButton, + this.selectionDrawModeHeightLabel, + this.selectionDrawModeHeightTextBox, + this.selectionDrawModeUnits, + + this.floodModeSeparator, + this.floodModeLabel, + this.floodModeSplitButton, + + this.resamplingSeparator, + this.resamplingLabel, + this.resamplingComboBox, + + this.colorPickerSeparator, + this.colorPickerLabel, + this.colorPickerComboBox, + + this.fontSeparator, + this.fontLabel, + this.fontFamilyComboBox, + this.fontSizeComboBox, + this.fontSmoothingComboBox, + this.fontStyleSeparator, + this.fontBoldButton, + this.fontItalicsButton, + this.fontUnderlineButton, + this.fontAlignSeparator, + this.fontAlignLeftButton, + this.fontAlignCenterButton, + this.fontAlignRightButton, + + this.shapeSeparator, + this.shapeButton, + + this.gradientSeparator1, + this.gradientLinearClampedButton, + this.gradientLinearReflectedButton, + this.gradientLinearDiamondButton, + this.gradientRadialButton, + this.gradientConicalButton, + this.gradientSeparator2, + this.gradientChannelsSplitButton, + + this.penSeparator, + this.penSizeLabel, + this.penSizeDecButton, + this.penSizeComboBox, + this.penSizeIncButton, + this.penStyleLabel, + this.penStartCapSplitButton, + this.penDashStyleSplitButton, + this.penEndCapSplitButton, + + this.brushSeparator, + this.brushStyleLabel, + this.brushStyleComboBox, + + this.toleranceSeparator, + this.toleranceLabel, + this.toleranceSliderStrip, + + this.blendingSeparator, + this.antiAliasingSplitButton, + this.alphaBlendingSplitButton + }); + + this.ResumeLayout(false); + } + + public void CyclePenEndCap() + { + PenInfo newPenInfo = PenInfo.Clone(); + newPenInfo.EndCap = NextLineCap(newPenInfo.EndCap); + PenInfo = newPenInfo; + } + + public void CyclePenStartCap() + { + PenInfo newPenInfo = PenInfo.Clone(); + newPenInfo.StartCap = NextLineCap(newPenInfo.StartCap); + PenInfo = newPenInfo; + } + + public void CyclePenDashStyle() + { + PenInfo newPenInfo = PenInfo.Clone(); + newPenInfo.DashStyle = NextDashStyle(newPenInfo.DashStyle); + PenInfo = newPenInfo; + } + + private DashStyle NextDashStyle(DashStyle oldDash) + { + int dashIndex = Array.IndexOf(this.dashStyles, oldDash); + int newDashIndex = (dashIndex + 1) % this.dashStyles.Length; + return this.dashStyles[newDashIndex]; + } + + private LineCap2 NextLineCap(LineCap2 oldCap) + { + int capIndex = Array.IndexOf(this.lineCaps, oldCap); + int newCapIndex = (capIndex + 1) % this.lineCaps.Length; + return this.lineCaps[newCapIndex]; + } + + private void GradientTypeButtonClicked(object sender, EventArgs e) + { + Tracing.LogFeature("ToolConfigStrip(GradientTypeButtonClicked)"); + + GradientType newType = (GradientType)((ToolStripButton)sender).Tag; + GradientInfo = new GradientInfo(newType, GradientInfo.AlphaOnly); + } + + private void ComboBoxStyle_SelectedValueChanged(object sender, EventArgs e) + { + OnBrushChanged(); + } + + public void PerformBrushChanged() + { + OnBrushChanged(); + } + + private void ComboBoxStyle_DrawItem(object sender, DrawItemEventArgs e) + { + e.DrawBackground(); + + Rectangle r = e.Bounds; + + if (e.Index != -1) + { + string itemName = (string)this.brushStyleComboBox.Items[e.Index]; + + if (itemName != this.solidBrushText) + { + Rectangle rd = r; + rd.Width = rd.Left + 25; + + Rectangle rt = r; + r.X = rd.Right; + + string displayText = this.brushStyleComboBox.Items[e.Index].ToString(); + HatchStyle hs = (HatchStyle)this.hatchStyleNames.LocalizedNameToEnumValue(displayText); + + using (HatchBrush b = new HatchBrush(hs, e.ForeColor, e.BackColor)) + { + e.Graphics.FillRectangle(b, rd); + } + + StringFormat sf = new StringFormat(); + sf.Alignment = StringAlignment.Near; + + using (SolidBrush sb = new SolidBrush(Color.White)) + { + if ((e.State & DrawItemState.Focus) == 0) + { + sb.Color = SystemColors.Window; + e.Graphics.FillRectangle(sb, r); + sb.Color = SystemColors.WindowText; + e.Graphics.DrawString(displayText, this.Font, sb, r, sf); + } + else + { + sb.Color = SystemColors.Highlight; + e.Graphics.FillRectangle(sb, r); + sb.Color = SystemColors.HighlightText; + e.Graphics.DrawString(displayText, this.Font, sb, r, sf); + } + } + + sf.Dispose(); + sf = null; + } + else + { + // Solid Brush + using (SolidBrush sb = new SolidBrush(Color.White)) + { + if ((e.State & DrawItemState.Focus) == 0) + { + sb.Color = SystemColors.Window; + e.Graphics.FillRectangle(sb, e.Bounds); + string displayText = this.brushStyleComboBox.Items[e.Index].ToString(); + sb.Color = SystemColors.WindowText; + e.Graphics.DrawString(displayText, this.Font, sb, e.Bounds); + } + else + { + sb.Color = SystemColors.Highlight; + e.Graphics.FillRectangle(sb, e.Bounds); + string displayText = this.brushStyleComboBox.Items[e.Index].ToString(); + sb.Color = SystemColors.HighlightText; + e.Graphics.DrawString(displayText, this.Font, sb, e.Bounds); + } + } + } + + e.DrawFocusRectangle(); + } + } + + private void ComboBoxStyle_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e) + { + // Work out what the text will be + string displayText = this.brushStyleComboBox.Items[e.Index].ToString(); + + // Get width & height of string + SizeF stringSize = e.Graphics.MeasureString(displayText, this.Font); + + // set height to text height + e.ItemHeight = (int)stringSize.Height; + + // set width to text width + e.ItemWidth = (int)stringSize.Width; + } + + public event EventHandler ShapeDrawTypeChanged; + protected virtual void OnShapeDrawTypeChanged() + { + if (ShapeDrawTypeChanged != null) + { + ShapeDrawTypeChanged(this, EventArgs.Empty); + } + } + + public void PerformShapeDrawTypeChanged() + { + OnShapeDrawTypeChanged(); + } + + public ShapeDrawType ShapeDrawType + { + get + { + return shapeDrawType; + } + + set + { + if (shapeDrawType != value) + { + shapeDrawType = value; + + // if the user sets the shape the buttons must be updated + if (shapeDrawType == ShapeDrawType.Outline) + { + this.shapeButton.Image = shapeOutlineImage.Reference; + } + else if (shapeDrawType == ShapeDrawType.Both) + { + this.shapeButton.Image = shapeBothImage.Reference; + } + else if (shapeDrawType == ShapeDrawType.Interior) + { + this.shapeButton.Image = shapeInteriorImage.Reference; + } + else + { + // invalid shape + throw new InvalidOperationException("Shape draw type is invalid"); + } + + this.OnShapeDrawTypeChanged(); + } + } + } + + private void SetFontStyleButtons(FontStyle style) + { + bool bold = ((style & FontStyle.Bold) != 0); + bool italic = ((style & FontStyle.Italic) != 0); + bool underline = ((style & FontStyle.Underline) != 0); + + this.fontBoldButton.Checked = bold; + this.fontItalicsButton.Checked = italic; + this.fontUnderlineButton.Checked = underline; + } + + protected override void OnItemClicked(ToolStripItemClickedEventArgs e) + { + if (e.ClickedItem == fontBoldButton) + { + Tracing.LogFeature("ToolConfigStrip(fontBoldButton)"); + + this.fontStyle ^= FontStyle.Bold; + SetFontStyleButtons(this.fontStyle); + this.OnFontInfoChanged(); + } + else if (e.ClickedItem == fontItalicsButton) + { + Tracing.LogFeature("ToolConfigStrip(fontItalicsButton)"); + + this.fontStyle ^= FontStyle.Italic; + SetFontStyleButtons(this.fontStyle); + this.OnFontInfoChanged(); + } + else if (e.ClickedItem == fontUnderlineButton) + { + Tracing.LogFeature("ToolConfigStrip(fontUnderlineButton)"); + + this.fontStyle ^= FontStyle.Underline; + SetFontStyleButtons(this.fontStyle); + this.OnFontInfoChanged(); + } + else if (e.ClickedItem == fontAlignLeftButton) + { + Tracing.LogFeature("ToolConfigStrip(fontAlignLeftButton)"); + this.FontAlignment = TextAlignment.Left; + } + else if (e.ClickedItem == fontAlignCenterButton) + { + Tracing.LogFeature("ToolConfigStrip(fontAlignCenterButton)"); + this.FontAlignment = TextAlignment.Center; + } + else if (e.ClickedItem == fontAlignRightButton) + { + Tracing.LogFeature("ToolConfigStrip(fontAlignRightButton)"); + this.FontAlignment = TextAlignment.Right; + } + + base.OnItemClicked(e); + } + + public event EventHandler PenInfoChanged; + protected virtual void OnPenChanged() + { + if (PenInfoChanged != null) + { + PenInfoChanged(this, EventArgs.Empty); + } + } + + public void PerformPenChanged() + { + OnPenChanged(); + } + + public void AddToPenSize(float delta) + { + if ((this.toolBarConfigItems & ToolBarConfigItems.Pen) == ToolBarConfigItems.Pen) + { + float newWidth = Utility.Clamp(PenInfo.Width + delta, minPenSize, maxPenSize); + PenInfo newPenInfo = PenInfo.Clone(); + newPenInfo.Width += delta; + newPenInfo.Width = (float)Utility.Clamp(newPenInfo.Width, minPenSize, maxPenSize); + PenInfo = newPenInfo; + } + } + + public PenInfo PenInfo + { + get + { + float width; + + try + { + width = float.Parse(this.penSizeComboBox.Text); + } + + catch (FormatException) + { + // TODO: would much rather grab the value from AppEnvironment + // or in some way that we keep track of the "last good value" + width = 2; + } + + LineCap2 startCap; + + try + { + startCap = (LineCap2)this.penStartCapSplitButton.Tag; + } + + catch (Exception) + { + startCap = PenInfo.DefaultLineCap; + } + + LineCap2 endCap; + + try + { + endCap = (LineCap2)this.penEndCapSplitButton.Tag; + } + + catch (Exception) + { + endCap = PenInfo.DefaultLineCap; + } + + DashStyle dashStyle = (DashStyle)this.penDashStyleSplitButton.Tag; + + return new PenInfo(dashStyle, width, startCap, endCap, PenInfo.DefaultCapScale); + } + + set + { + if (this.PenInfo != value) + { + this.penSizeComboBox.Text = value.Width.ToString(); + this.penStartCapSplitButton.Tag = value.StartCap; + this.penStartCapSplitButton.Image = GetLineCapImage(value.StartCap, true).Reference; + this.penDashStyleSplitButton.Tag = value.DashStyle; + this.penDashStyleSplitButton.Image = GetDashStyleImage(value.DashStyle); + this.penEndCapSplitButton.Tag = value.EndCap; + this.penEndCapSplitButton.Image = GetLineCapImage(value.EndCap, false).Reference; + OnPenChanged(); + } + } + } + + private void SizeComboBox_TextChanged(object sender, System.EventArgs e) + { + BrushSizeComboBox_Validating(this, new CancelEventArgs()); + } + + private void BrushSizeComboBox_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + float penSize; + bool valid = float.TryParse(this.penSizeComboBox.Text, out penSize); + + if (!valid) + { + this.penSizeComboBox.BackColor = Color.Red; + this.penSizeComboBox.ToolTipText = PdnResources.GetString("PenConfigWidget.Error.InvalidNumber"); + } + else + { + if (penSize < minPenSize) + { + // Set the error if the size is too small. + this.penSizeComboBox.BackColor = Color.Red; + string tooSmallFormat = PdnResources.GetString("PenConfigWidget.Error.TooSmall.Format"); + string tooSmall = string.Format(tooSmallFormat, minPenSize); + this.penSizeComboBox.ToolTipText = tooSmall; + } + else if (penSize > maxPenSize) + { + // Set the error if the size is too large. + this.penSizeComboBox.BackColor = Color.Red; + string tooLargeFormat = PdnResources.GetString("PenConfigWidget.Error.TooLarge.Format"); + string tooLarge = string.Format(tooLargeFormat, maxPenSize); + this.penSizeComboBox.ToolTipText = tooLarge; + } + else + { + // Clear the error, if any + this.penSizeComboBox.BackColor = SystemColors.Window; + this.penSizeComboBox.ToolTipText = string.Empty; + OnPenChanged(); + } + } + } + + public event EventHandler AlphaBlendingChanged; + protected virtual void OnAlphaBlendingChanged() + { + if (AlphaBlendingChanged != null) + { + AlphaBlendingChanged(this, EventArgs.Empty); + } + } + + public void PerformAlphaBlendingChanged() + { + OnAlphaBlendingChanged(); + } + + private void GradientChannelsSplitButton_DropDownOpening(object sender, EventArgs e) + { + ToolStripMenuItem allChannels = new ToolStripMenuItem( + PdnResources.GetString("GradientChannels.AllColorChannels.Text"), + this.gradientAllColorChannelsImage.Reference, + delegate(object sender2, EventArgs e2) + { + GradientInfo = new GradientInfo(GradientInfo.GradientType, false); + }); + + ToolStripMenuItem alphaOnly = new ToolStripMenuItem( + PdnResources.GetString("GradientChannels.AlphaChannelOnly.Text"), + this.gradientAlphaChannelOnlyImage.Reference, + delegate(object sender3, EventArgs e3) + { + GradientInfo = new GradientInfo(GradientInfo.GradientType, true); + }); + + allChannels.Checked = !GradientInfo.AlphaOnly; + alphaOnly.Checked = GradientInfo.AlphaOnly; + + this.gradientChannelsSplitButton.DropDownItems.Clear(); + this.gradientChannelsSplitButton.DropDownItems.AddRange( + new ToolStripItem[] + { + allChannels, + alphaOnly + }); + } + + private void AlphaBlendingSplitButton_DropDownOpening(object sender, EventArgs e) + { + ToolStripMenuItem abEnabled = new ToolStripMenuItem( + PdnResources.GetString("AlphaBlendingSplitButton.BlendingEnabled.Text"), + this.alphaBlendingEnabledImage.Reference, + delegate(object sender2, EventArgs e2) + { + AlphaBlending = true; + }); + + ToolStripMenuItem abOverwrite = new ToolStripMenuItem( + PdnResources.GetString("AlphaBlendingSplitButton.BlendingOverwrite.Text"), + this.alphaBlendingOverwriteImage.Reference, + delegate(object sender3, EventArgs e3) + { + AlphaBlending = false; + }); + + abEnabled.Checked = AlphaBlending; + abOverwrite.Checked = !AlphaBlending; + + this.alphaBlendingSplitButton.DropDownItems.Clear(); + this.alphaBlendingSplitButton.DropDownItems.AddRange( + new ToolStripItem[] + { + abEnabled, + abOverwrite + }); + } + + public bool AlphaBlending + { + get + { + return this.alphaBlendingEnabled; + } + + set + { + if (value != this.alphaBlendingEnabled) + { + this.alphaBlendingEnabled = value; + + if (value) + { + this.alphaBlendingSplitButton.Image = this.alphaBlendingEnabledImage.Reference; + } + else + { + this.alphaBlendingSplitButton.Image = this.alphaBlendingOverwriteImage.Reference; + } + + OnAlphaBlendingChanged(); + } + } + } + + private void AlphaBlendingComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + OnAlphaBlendingChanged(); + } + + public event EventHandler AntiAliasingChanged; + protected virtual void OnAntiAliasingChanged() + { + if (AntiAliasingChanged != null) + { + AntiAliasingChanged(this, EventArgs.Empty); + } + } + + public void PerformAntiAliasingChanged() + { + OnAntiAliasingChanged(); + } + + public bool AntiAliasing + { + get + { + return this.antiAliasingEnabled; + } + + set + { + if (this.antiAliasingEnabled != value) + { + if (value) + { + this.antiAliasingSplitButton.Image = this.antiAliasingEnabledImage.Reference; + } + else + { + this.antiAliasingSplitButton.Image = this.antiAliasingDisabledImage.Reference; + } + + this.antiAliasingEnabled = value; + OnAntiAliasingChanged(); + } + } + } + + private Image GetDashStyleImage(DashStyle dashStyle) + { + string nameFormat = "Images.DashStyleButton.{0}.png"; + string name = string.Format(nameFormat, dashStyle.ToString()); + ImageResource imageResource = PdnResources.GetImageResource(name); + + Image returnImage; + + if (UI.GetXScaleFactor() == 1.0f && UI.GetYScaleFactor() == 1.0f) + { + returnImage = imageResource.Reference; + } + else + { + returnImage = new Bitmap(imageResource.Reference, UI.ScaleSize(imageResource.Reference.Size)); + } + + return returnImage; + } + + private ImageResource GetLineCapImage(LineCap2 lineCap, bool isStartCap) + { + string nameFormat = "Images.LineCapButton.{0}.{1}.png"; + string name = string.Format(nameFormat, lineCap.ToString(), isStartCap ? "Start" : "End"); + ImageResource imageResource = PdnResources.GetImageResource(name); + return imageResource; + } + + private void PenDashStyleButton_DropDownOpening(object sender, EventArgs e) + { + List menuItems = new List(); + + foreach (DashStyle dashStyle in this.dashStyles) + { + ToolStripMenuItem mi = new ToolStripMenuItem( + this.dashStyleLocalizer.EnumValueToLocalizedName(dashStyle), + GetDashStyleImage(dashStyle), + delegate(object sender2, EventArgs e2) + { + ToolStripMenuItem tsmi = (ToolStripMenuItem)sender2; + DashStyle newDashStyle = (DashStyle)tsmi.Tag; + + PenInfo newPenInfo = PenInfo.Clone(); + newPenInfo.DashStyle = newDashStyle; + PenInfo = newPenInfo; + }); + + mi.ImageScaling = ToolStripItemImageScaling.None; + + if (dashStyle == PenInfo.DashStyle) + { + mi.Checked = true; + } + + mi.Tag = dashStyle; + menuItems.Add(mi); + } + + this.penDashStyleSplitButton.DropDownItems.Clear(); + this.penDashStyleSplitButton.DropDownItems.AddRange(menuItems.ToArray()); + } + + private void PenCapSplitButton_DropDownOpening(object sender, EventArgs e) + { + ToolStripSplitButton splitButton = (ToolStripSplitButton)sender; + List menuItems = new List(); + + LineCap2 currentLineCap = PenInfo.DefaultLineCap; + bool isStartCap; + + if (object.ReferenceEquals(splitButton, this.penStartCapSplitButton)) + { + isStartCap = true; + currentLineCap = PenInfo.StartCap; + } + else if (object.ReferenceEquals(splitButton, this.penEndCapSplitButton)) + { + isStartCap = false; + currentLineCap = PenInfo.EndCap; + } + else + { + throw new InvalidOperationException(); + } + + foreach (LineCap2 lineCap in this.lineCaps) + { + ToolStripMenuItem mi = new ToolStripMenuItem( + this.lineCapLocalizer.EnumValueToLocalizedName(lineCap), + GetLineCapImage(lineCap, isStartCap).Reference, + delegate(object sender2, EventArgs e2) + { + ToolStripMenuItem tsmi = (ToolStripMenuItem)sender2; + Pair data = (Pair)tsmi.Tag; + + PenInfo newPenInfo = PenInfo.Clone(); + + if (object.ReferenceEquals(data.First, this.penStartCapSplitButton)) + { + newPenInfo.StartCap = data.Second; + } + else if (object.ReferenceEquals(data.First, this.penEndCapSplitButton)) + { + newPenInfo.EndCap = data.Second; + } + + PenInfo = newPenInfo; + }); + + mi.Tag = Pair.Create(splitButton, lineCap); + + if (lineCap == currentLineCap) + { + mi.Checked = true; + } + + menuItems.Add(mi); + } + + splitButton.DropDownItems.Clear(); + splitButton.DropDownItems.AddRange(menuItems.ToArray()); + } + + private void AntiAliasingSplitButton_DropDownOpening(object sender, EventArgs e) + { + ToolStripMenuItem aaEnabled = new ToolStripMenuItem( + PdnResources.GetString("AntiAliasingSplitButton.Enabled.Text"), + this.antiAliasingEnabledImage.Reference, + delegate(object sender2, EventArgs e2) + { + AntiAliasing = true; + }); + + ToolStripMenuItem aaDisabled = new ToolStripMenuItem( + PdnResources.GetString("AntiAliasingSplitButton.Disabled.Text"), + this.antiAliasingDisabledImage.Reference, + delegate(object sender3, EventArgs e3) + { + AntiAliasing = false; + }); + + aaEnabled.Checked = AntiAliasing; + aaDisabled.Checked = !AntiAliasing; + + this.antiAliasingSplitButton.DropDownItems.Clear(); + this.antiAliasingSplitButton.DropDownItems.AddRange( + new ToolStripItem[] + { + aaEnabled, + aaDisabled + }); + } + + private void SmoothingComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + OnFontSmoothingChanged(); + } + + private void PopulateFontsBackgroundThread(object ignored) + { + PopulateFontsBackgroundThread(); + } + + private void PopulateFontsBackgroundThread() + { + try + { + using (new ThreadBackground(ThreadBackgroundFlags.Cpu)) + { + PopulateFonts(); + } + } + + finally + { + staticFontNamesPopulatedEvent.Set(); + } + } + + private void PopulateFonts() + { + List fontNames = new List(); + + using (Graphics g = this.CreateGraphics()) + { + FontFamily[] families = FontFamily.GetFamilies(g); + + foreach (FontFamily family in families) + { + using (FontInfo fi = new FontInfo(family, 10, FontStyle.Regular)) + { + if (!fontNames.Contains(family.Name) && fi.CanCreateFont()) + { + fontNames.Add(family.Name); + } + } + } + } + + staticFontNames = fontNames; + Tracing.LogFeature("PopulateFonts()"); + } + + public event EventHandler FontInfoChanged; + protected virtual void OnFontInfoChanged() + { + if (FontInfoChanged != null) + { + FontInfoChanged(this, EventArgs.Empty); + } + } + + public event EventHandler FontAlignmentChanged; + protected virtual void OnTextAlignmentChanged() + { + if (FontAlignmentChanged != null) + { + FontAlignmentChanged(this, EventArgs.Empty); + } + } + + public event EventHandler FontSmoothingChanged; + protected virtual void OnFontSmoothingChanged() + { + if (FontSmoothingChanged != null) + { + FontSmoothingChanged(this, EventArgs.Empty); + } + } + + public FontSmoothing FontSmoothing + { + get + { + return (FontSmoothing)this.fontSmoothingLocalizer.LocalizedNameToEnumValue((string)this.fontSmoothingComboBox.SelectedItem); + } + + set + { + if (value != this.FontSmoothing) + { + this.fontSmoothingComboBox.SelectedItem = this.fontSmoothingLocalizer.EnumValueToLocalizedName(value); + } + } + } + + public FontInfo FontInfo + { + get + { + return new FontInfo(this.FontFamily, this.FontSize, this.FontStyle); + } + + set + { + this.FontFamily = value.FontFamily; + this.FontSize = value.Size; + this.FontStyle = value.FontStyle; + } + } + + /// + /// Gets or sets the font style i.e. bold, italic, and underline + /// + public FontStyle FontStyle + { + get + { + return this.fontStyle; + } + + set + { + if (this.fontStyle != value) + { + this.fontStyle = value; + SetFontStyleButtons(FontStyle); + this.OnFontInfoChanged(); + } + } + } + + /// + /// Gets or sets the size of the font. + /// + public float FontSize + { + get + { + bool invalid = false; + float number = oldSizeValue; + + try + { + number = float.Parse(fontSizeComboBox.Text); + } + + catch (FormatException) + { + invalid = true; + } + + catch (OverflowException) + { + invalid = true; + } + + // if the size is valid update the new size else return the last known good size. + if (!invalid) + { + oldSizeValue = number; + } + + return oldSizeValue; + } + + set + { + bool invalid = false; + float number = oldSizeValue; + + try + { + number = float.Parse(fontSizeComboBox.Text); + } + + catch (FormatException) + { + invalid = true; + } + + catch (OverflowException) + { + invalid = true; + } + + // if the size is valid update the new size else return the last known good size. + if (!invalid) + { + if (float.Parse(fontSizeComboBox.Text) != value) + { + fontSizeComboBox.Text = value.ToString(); + this.OnFontInfoChanged(); + } + } + } + } + + private FontFamily IndexToFontFamily(int i) + { + try + { + return new FontFamily((string)this.fontFamilyComboBox.Items[i]); + } + + catch (Exception) + { + return FontFamily.GenericSansSerif; + } + } + + private Font IndexToFont(int i, float size, FontStyle style) + { + using (FontFamily ff = IndexToFontFamily(i)) + { + return new FontInfo(ff, size, style).CreateFont(); + } + } + + /// + /// Gets or sets the font family. + /// + public FontFamily FontFamily + { + get + { + try + { + return new FontFamily((string)this.fontFamilyComboBox.SelectedItem); + } + + catch (ArgumentException) + { + return FontFamily.GenericSansSerif; + } + } + + set + { + string current = (string)this.fontFamilyComboBox.SelectedItem; + + if (current != value.Name) + { + int index = fontFamilyComboBox.Items.IndexOf(value.Name); + + if (index != -1) + { + fontFamilyComboBox.SelectedIndex = index; + this.OnFontInfoChanged(); + } + else + { + // Maybe they're just specifying a font we didn't add to the list yet? aka pre-list initialization + FontInfo oldFI = this.FontInfo; + + FontFamily newFF; + try + { + newFF = new FontFamily(value.Name); + } + + catch (Exception) + { + newFF = new FontFamily(System.Drawing.Text.GenericFontFamilies.SansSerif); + } + + FontInfo newFI = new FontInfo(newFF, oldFI.Size, oldFI.FontStyle); + + if (newFI.CanCreateFont()) + { + this.fontFamilyComboBox.Items.Add(value.Name); + this.fontFamilyComboBox.SelectedItem = value.Name; + } + else + { + throw new InvalidOperationException("FontFamily is not valid"); + } + } + } + } + } + + public TextAlignment FontAlignment + { + get + { + return this.alignment; + } + set + { + if (this.alignment != value) + { + this.alignment = value; + + // if the user sets the text alignment the buttons must be updated + if (this.alignment == TextAlignment.Left) + { + this.fontAlignLeftButton.Checked = true; + this.fontAlignCenterButton.Checked = false; + this.fontAlignRightButton.Checked = false; + } + else if (this.alignment == TextAlignment.Center) + { + this.fontAlignLeftButton.Checked = false; + this.fontAlignCenterButton.Checked = true; + this.fontAlignRightButton.Checked = false; + } + else if (this.alignment == TextAlignment.Right) + { + this.fontAlignLeftButton.Checked = false; + this.fontAlignCenterButton.Checked = false; + this.fontAlignRightButton.Checked = true; + } + else + { + throw new InvalidOperationException("Text alignment type is invalid"); + } + + this.OnTextAlignmentChanged(); + } + } + } + + private void FontFamilyComboBox_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e) + { + if (e.Index != -1) + { + string displayText = (string)this.fontFamilyComboBox.Items[e.Index]; + + SizeF stringSize = e.Graphics.MeasureString(displayText, arialFontBase); + int size = (int)stringSize.Width; + + // set up two areas to draw + const int leftMargin = 3; + Rectangle bounds = e.Bounds; + bounds.X += leftMargin; + bounds.Width -= leftMargin; + + Rectangle r = bounds; + + Rectangle rd = r; + rd.Width = rd.Left + size; + + Rectangle rt = r; + r.X = rd.Right; + + using (Font myFont = IndexToFont(e.Index, 10, FontStyle.Regular)) + { + StringFormat sf = (StringFormat)StringFormat.GenericTypographic.Clone(); + sf.LineAlignment = StringAlignment.Center; + sf.FormatFlags &= ~StringFormatFlags.LineLimit; + sf.FormatFlags |= StringFormatFlags.NoWrap; + + bool isSymbol = PaintDotNet.SystemLayer.Fonts.IsSymbolFont(myFont); + bool isSelected = ((e.State & DrawItemState.Selected) != 0); + Brush fillBrush; + Brush textBrush; + + if (isSelected) + { + fillBrush = highlightBrush; + textBrush = highlightTextBrush; + } + else + { + fillBrush = windowBrush; + textBrush = windowTextBrush; + } + + e.Graphics.FillRectangle(fillBrush, e.Bounds); + + if (isSymbol) + { + e.Graphics.DrawString(displayText, arialFontBase, textBrush, bounds, sf); + e.Graphics.DrawString(displayText, myFont, textBrush, r, sf); + } + else + { + e.Graphics.DrawString(displayText, myFont, textBrush, bounds, sf); + } + + sf.Dispose(); + sf = null; + } + } + + e.DrawFocusRectangle(); + } + + private void FontFamilyComboBox_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e) + { + // Work out what the text will be + string displayText = (string)this.fontFamilyComboBox.Items[e.Index]; + + // Get width & height of string + SizeF stringSize; + using (Font font = IndexToFont(e.Index, 10, FontStyle.Regular)) + { + stringSize = e.Graphics.MeasureString(displayText, font); + } + + // Account for top margin + stringSize.Height += UI.ScaleHeight(6); + + // set hight to text height + e.ItemHeight = (int)stringSize.Height; + int maxHeight = UI.ScaleHeight(20); + + if (e.ItemHeight > maxHeight) + { + e.ItemHeight = maxHeight; + } + + // set width to text width + e.ItemWidth = (int)stringSize.Width; + } + + private void FontSizeComboBox_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + try + { + bool invalid = false; + + try + { + float number = float.Parse(fontSizeComboBox.Text); + } + + catch (FormatException) + { + invalid = true; + } + + catch (OverflowException) + { + invalid = true; + } + + if (invalid) + { + this.fontSizeComboBox.BackColor = Color.Red; + this.fontSizeComboBox.ToolTipText = PdnResources.GetString("TextConfigWidget.Error.InvalidNumber"); + } + else + { + if ((float.Parse(fontSizeComboBox.Text) < minFontSize)) + { + // Set the error if the size is too small. + this.fontSizeComboBox.BackColor = Color.Red; + string format = PdnResources.GetString("TextConfigWidget.Error.TooSmall.Format"); + string text = string.Format(format, minFontSize); + this.fontSizeComboBox.ToolTipText = text; + } + else if ((float.Parse(fontSizeComboBox.Text) > maxFontSize)) + { + // Set the error if the size is too large. + this.fontSizeComboBox.BackColor = Color.Red; + string format = PdnResources.GetString("TextConfigWidget.Error.TooLarge.Format"); + string text = string.Format(format, maxFontSize); + this.fontSizeComboBox.ToolTipText = text; + } + else + { + // Clear the error, if any + this.fontSizeComboBox.ToolTipText = string.Empty; + this.fontSizeComboBox.BackColor = SystemColors.Window; + OnFontInfoChanged(); + } + } + } + + catch (FormatException) + { + e.Cancel = true; + } + } + + private void FontSizeComboBox_TextChanged(object sender, System.EventArgs e) + { + FontSizeComboBox_Validating(sender, new CancelEventArgs()); + } + + private void FontFamilyComboBox_SelectedIndexChanged(object sender, System.EventArgs e) + { + OnFontInfoChanged(); + } + + private void FontFamilyComboBox_GotFocus(object sender, EventArgs e) + { + if (!fontsComboBoxPopulated) + { + AsyncInitFontNames(); + + using (new WaitCursorChanger(this)) + { + staticFontNamesPopulatedEvent.WaitOne(); + + this.fontFamilyComboBox.ComboBox.BeginUpdate(); + List fontNames = staticFontNames; + + foreach (string name in fontNames) + { + if (!this.fontFamilyComboBox.Items.Contains(name)) + { + this.fontFamilyComboBox.Items.Add(name); + } + } + + this.fontFamilyComboBox.ComboBox.EndUpdate(); + } + + fontsComboBoxPopulated = true; + } + } + + public ToolBarConfigItems ToolBarConfigItems + { + get + { + return this.toolBarConfigItems; + } + + set + { + this.toolBarConfigItems = value; + + SuspendLayout(); + + bool showPen = ((value & ToolBarConfigItems.Pen) != ToolBarConfigItems.None); + this.penSeparator.Visible = showPen; + this.penSizeLabel.Visible = showPen; + this.penSizeDecButton.Visible = showPen; + this.penSizeComboBox.Visible = showPen; + this.penSizeIncButton.Visible = showPen; + + bool showPenCaps = ((value & ToolBarConfigItems.PenCaps) != ToolBarConfigItems.None); + this.penStyleLabel.Visible = showPenCaps; + this.penStartCapSplitButton.Visible = showPenCaps; + this.penDashStyleSplitButton.Visible = showPenCaps; + this.penEndCapSplitButton.Visible = showPenCaps; + + bool showBrush = ((value & ToolBarConfigItems.Brush) != ToolBarConfigItems.None); + this.brushSeparator.Visible = showBrush; + this.brushStyleLabel.Visible = showBrush; + this.brushStyleComboBox.Visible = showBrush; + + bool showShape = ((value & ToolBarConfigItems.ShapeType) != ToolBarConfigItems.None); + this.shapeSeparator.Visible = showShape; + this.shapeButton.Visible = showShape; + + bool showGradient = ((value & ToolBarConfigItems.Gradient) != ToolBarConfigItems.None); + this.gradientSeparator1.Visible = showGradient; + this.gradientLinearClampedButton.Visible = showGradient; + this.gradientLinearReflectedButton.Visible = showGradient; + this.gradientLinearDiamondButton.Visible = showGradient; + this.gradientRadialButton.Visible = showGradient; + this.gradientConicalButton.Visible = showGradient; + this.gradientSeparator2.Visible = showGradient; + this.gradientChannelsSplitButton.Visible = showGradient; + + bool showAA = ((value & ToolBarConfigItems.Antialiasing) != ToolBarConfigItems.None); + this.antiAliasingSplitButton.Visible = showAA; + + bool showAB = ((value & ToolBarConfigItems.AlphaBlending) != ToolBarConfigItems.None); + this.alphaBlendingSplitButton.Visible = showAB; + + bool showBlendingSep = ((value & (ToolBarConfigItems.AlphaBlending | ToolBarConfigItems.Antialiasing)) != ToolBarConfigItems.None); + this.blendingSeparator.Visible = showBlendingSep; + + bool showTolerance = ((value & ToolBarConfigItems.Tolerance) != ToolBarConfigItems.None); + this.toleranceSeparator.Visible = showTolerance; + this.toleranceLabel.Visible = showTolerance; + this.toleranceSliderStrip.Visible = showTolerance; + + bool showText = ((value & ToolBarConfigItems.Text) != ToolBarConfigItems.None); + this.fontSeparator.Visible = showText; + this.fontLabel.Visible = showText; + this.fontFamilyComboBox.Visible = showText; + this.fontSizeComboBox.Visible = showText; + this.fontSmoothingComboBox.Visible = showText; + this.fontStyleSeparator.Visible = showText; + this.fontBoldButton.Visible = showText; + this.fontItalicsButton.Visible = showText; + this.fontUnderlineButton.Visible = showText; + this.fontAlignSeparator.Visible = showText; + this.fontAlignLeftButton.Visible = showText; + this.fontAlignCenterButton.Visible = showText; + this.fontAlignRightButton.Visible = showText; + + if (showText && IsHandleCreated) + { + AsyncInitFontNames(); + } + + bool showResampling = ((value & ToolBarConfigItems.Resampling) != ToolBarConfigItems.None); + this.resamplingSeparator.Visible = showResampling; + this.resamplingLabel.Visible = showResampling; + this.resamplingComboBox.Visible = showResampling; + + bool showColorPicker = ((value & ToolBarConfigItems.ColorPickerBehavior) != ToolBarConfigItems.None); + this.colorPickerSeparator.Visible = showColorPicker; + this.colorPickerLabel.Visible = showColorPicker; + this.colorPickerComboBox.Visible = showColorPicker; + + bool showSelectionCombineMode = ((value & ToolBarConfigItems.SelectionCombineMode) != ToolBarConfigItems.None); + this.selectionCombineModeSeparator.Visible = showSelectionCombineMode; + this.selectionCombineModeLabel.Visible = showSelectionCombineMode; + this.selectionCombineModeSplitButton.Visible = showSelectionCombineMode; + + bool showFloodMode = ((value & ToolBarConfigItems.FloodMode) != ToolBarConfigItems.None); + this.floodModeSeparator.Visible = showFloodMode; + this.floodModeLabel.Visible = showFloodMode; + this.floodModeSplitButton.Visible = showFloodMode; + + bool showSelectionDrawMode = ((value & ToolBarConfigItems.SelectionDrawMode) != ToolBarConfigItems.None); + this.selectionDrawModeSeparator.Visible = showSelectionDrawMode; + this.selectionDrawModeModeLabel.Visible = showSelectionDrawMode; + this.selectionDrawModeSplitButton.Visible = showSelectionDrawMode; + this.selectionDrawModeWidthLabel.Visible = showSelectionDrawMode; + this.selectionDrawModeSwapButton.Visible = showSelectionDrawMode; + this.selectionDrawModeHeightLabel.Visible = showSelectionDrawMode; + RefreshSelectionDrawModeInfoVisibilities(); + + if (value == ToolBarConfigItems.None) + { + this.Visible = false; + } + else + { + this.Visible = true; + } + + ResumeLayout(); + PerformLayout(); + } + } + + private void ToleranceSlider_ToleranceChanged(object sender, EventArgs e) + { + OnToleranceChanged(); + } + + public void PerformToleranceChanged() + { + OnToleranceChanged(); + } + + public float Tolerance + { + get + { + return this.toleranceSlider.Tolerance; + } + + set + { + if (value != this.toleranceSlider.Tolerance) + { + this.toleranceSlider.Tolerance = value; + } + } + } + + public event EventHandler ToleranceChanged; + protected void OnToleranceChanged() + { + if (ToleranceChanged != null) + { + ToleranceChanged(this, EventArgs.Empty); + } + } + + public event EventHandler ColorPickerClickBehaviorChanged; + protected void OnColorPickerClickBehaviorChanged() + { + if (ColorPickerClickBehaviorChanged != null) + { + ColorPickerClickBehaviorChanged(this, EventArgs.Empty); + } + } + + public ColorPickerClickBehavior ColorPickerClickBehavior + { + get + { + string selectedItem = (string)this.colorPickerComboBox.SelectedItem; + ColorPickerClickBehavior cpcb = (ColorPickerClickBehavior)this.colorPickerBehaviorNames.LocalizedNameToEnumValue(selectedItem); + return cpcb; + } + + set + { + if (value != ColorPickerClickBehavior) + { + this.colorPickerComboBox.SelectedItem = this.colorPickerBehaviorNames.EnumValueToLocalizedName(value); + } + } + } + + public void PerformColorPickerClickBehaviorChanged() + { + OnColorPickerClickBehaviorChanged(); + } + + private void ColorPickerComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + OnColorPickerClickBehaviorChanged(); + } + + private void ResamplingComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + OnResamplingAlgorithmChanged(); + } + + public event EventHandler ResamplingAlgorithmChanged; + protected void OnResamplingAlgorithmChanged() + { + if (ResamplingAlgorithmChanged != null) + { + ResamplingAlgorithmChanged(this, EventArgs.Empty); + } + } + + public ResamplingAlgorithm ResamplingAlgorithm + { + get + { + string selectedItem = (string)this.resamplingComboBox.SelectedItem; + ResamplingAlgorithm ra = (ResamplingAlgorithm)this.resamplingAlgorithmNames.LocalizedNameToEnumValue(selectedItem); + return ra; + } + + set + { + if (value != ResamplingAlgorithm) + { + if (value != ResamplingAlgorithm.NearestNeighbor && value != ResamplingAlgorithm.Bilinear) + { + throw new InvalidEnumArgumentException(); + } + + this.resamplingComboBox.SelectedItem = this.resamplingAlgorithmNames.EnumValueToLocalizedName(value); + } + } + } + + public void PerformResamplingAlgorithmChanged() + { + OnResamplingAlgorithmChanged(); + } + + public event EventHandler SelectionCombineModeChanged; + + protected void OnSelectionCombineModeChanged() + { + if (SelectionCombineModeChanged != null) + { + SelectionCombineModeChanged(this, EventArgs.Empty); + } + } + + public CombineMode SelectionCombineMode + { + get + { + object tag = this.selectionCombineModeSplitButton.Tag; + CombineMode cm = (tag == null) ? CombineMode.Replace : (CombineMode)tag; + return cm; + } + + set + { + object tag = this.selectionCombineModeSplitButton.Tag; + CombineMode oldCm = (tag == null) ? CombineMode.Replace : (CombineMode)tag; + + if (tag == null || (tag != null && value != oldCm)) + { + this.selectionCombineModeSplitButton.Tag = value; + UI.SuspendControlPainting(this); + this.selectionCombineModeSplitButton.Image = GetSelectionCombineModeImage(value).Reference; + UI.ResumeControlPainting(this); + OnSelectionCombineModeChanged(); + Invalidate(true); + } + } + } + + public void PerformSelectionCombineModeChanged() + { + OnSelectionCombineModeChanged(); + } + + private ImageResource GetSelectionCombineModeImage(CombineMode cm) + { + return PdnResources.GetImageResource("Icons.ToolConfigStrip.SelectionCombineMode." + cm.ToString() + ".png"); + } + + private void SelectionCombineModeSplitButton_DropDownOpening(object sender, EventArgs e) + { + this.selectionCombineModeSplitButton.DropDownItems.Clear(); + + foreach (CombineMode cm in + new CombineMode[] + { + CombineMode.Replace, + CombineMode.Union, + CombineMode.Exclude, + CombineMode.Intersect, + CombineMode.Xor + }) + { + ToolStripMenuItem cmMI = new ToolStripMenuItem( + PdnResources.GetString("ToolConfigStrip.SelectionCombineModeSplitButton." + cm.ToString() + ".Text"), + GetSelectionCombineModeImage(cm).Reference, + delegate(object sender2, EventArgs e2) + { + ToolStripMenuItem asTSMI = (ToolStripMenuItem)sender2; + CombineMode newCM = (CombineMode)asTSMI.Tag; + this.SelectionCombineMode = newCM; + }); + + cmMI.Tag = cm; + cmMI.Checked = (cm == SelectionCombineMode); + + this.selectionCombineModeSplitButton.DropDownItems.Add(cmMI); + } + } + + private CombineMode CycleSelectionCombineMode(CombineMode mode) + { + CombineMode newMode; + + // Replace -> Union -> Exclude -> Intersect -> Xor -> Replace + + switch (mode) + { + default: + case CombineMode.Complement: + throw new InvalidEnumArgumentException(); + + case CombineMode.Exclude: + newMode = CombineMode.Intersect; + break; + + case CombineMode.Intersect: + newMode = CombineMode.Xor; + break; + + case CombineMode.Replace: + newMode = CombineMode.Union; + break; + + case CombineMode.Union: + newMode = CombineMode.Exclude; + break; + + case CombineMode.Xor: + newMode = CombineMode.Replace; + break; + } + + return newMode; + } + + public event EventHandler FloodModeChanged; + + protected void OnFloodModeChanged() + { + if (FloodModeChanged != null) + { + FloodModeChanged(this, EventArgs.Empty); + } + } + + public FloodMode FloodMode + { + get + { + object tag = this.floodModeSplitButton.Tag; + FloodMode fm = (tag == null) ? FloodMode.Local : (FloodMode)tag; + return fm; + } + + set + { + object tag = this.floodModeSplitButton.Tag; + FloodMode oldFm = (tag == null) ? FloodMode.Local : (FloodMode)tag; + + if (tag == null || (tag != null && value != oldFm)) + { + this.floodModeSplitButton.Tag = value; + this.floodModeSplitButton.Image = GetFloodModeImage(value).Reference; + OnFloodModeChanged(); + } + } + } + + public void PerformFloodModeChanged() + { + OnFloodModeChanged(); + } + + private ImageResource GetFloodModeImage(FloodMode cm) + { + return PdnResources.GetImageResource("Icons.ToolConfigStrip.FloodMode." + cm.ToString() + ".png"); + } + + private void FloodModeSplitButton_DropDownOpening(object sender, EventArgs e) + { + this.floodModeSplitButton.DropDownItems.Clear(); + + foreach (FloodMode fm in + new FloodMode[] + { + FloodMode.Local, + FloodMode.Global + }) + { + ToolStripMenuItem fmMI = new ToolStripMenuItem( + PdnResources.GetString("ToolConfigStrip.FloodModeSplitButton." + fm.ToString() + ".Text"), + GetFloodModeImage(fm).Reference, + delegate(object sender2, EventArgs e2) + { + ToolStripMenuItem asTSMI = (ToolStripMenuItem)sender2; + FloodMode newFM = (FloodMode)asTSMI.Tag; + this.FloodMode = newFM; + }); + + fmMI.Tag = fm; + fmMI.Checked = (fm == FloodMode); + + this.floodModeSplitButton.DropDownItems.Add(fmMI); + } + } + + private FloodMode CycleFloodMode(FloodMode mode) + { + FloodMode newMode; + + switch (mode) + { + default: + throw new InvalidEnumArgumentException(); + + case FloodMode.Global: + newMode = FloodMode.Local; + break; + + case FloodMode.Local: + newMode = FloodMode.Global; + break; + } + + return newMode; + } + + public event EventHandler SelectionDrawModeInfoChanged; + + protected void OnSelectionDrawModeInfoChanged() + { + if (SelectionDrawModeInfoChanged != null) + { + SelectionDrawModeInfoChanged(this, EventArgs.Empty); + } + } + + // syncs this.SelectionDrawModeInfo into the controls + private void SyncSelectionDrawModeInfoUI() + { + this.selectionDrawModeSplitButton.Text = GetSelectionDrawModeString(this.selectionDrawModeInfo.DrawMode); + this.selectionDrawModeSplitButton.Image = GetSelectionDrawModeImage(this.selectionDrawModeInfo.DrawMode); + + this.selectionDrawModeWidthTextBox.Text = this.selectionDrawModeInfo.Width.ToString(); + + this.selectionDrawModeHeightTextBox.Text = this.selectionDrawModeInfo.Height.ToString(); + + this.selectionDrawModeUnits.UnitsChanged -= SelectionDrawModeUnits_UnitsChanged; + this.selectionDrawModeUnits.Units = this.selectionDrawModeInfo.Units; + this.selectionDrawModeUnits.UnitsChanged += SelectionDrawModeUnits_UnitsChanged; + + RefreshSelectionDrawModeInfoVisibilities(); + } + + private void RefreshSelectionDrawModeInfoVisibilities() + { + if (this.selectionDrawModeInfo != null) + { + SuspendLayout(); + + bool anyVisible = (this.ToolBarConfigItems & ToolBarConfigItems.SelectionDrawMode) != ToolBarConfigItems.None; + + this.selectionDrawModeModeLabel.Visible = false; + + bool showWidthHeight = anyVisible & (this.selectionDrawModeInfo.DrawMode != SelectionDrawMode.Normal); + + this.selectionDrawModeWidthTextBox.Visible = showWidthHeight; + this.selectionDrawModeHeightTextBox.Visible = showWidthHeight; + this.selectionDrawModeWidthLabel.Visible = showWidthHeight; + this.selectionDrawModeHeightLabel.Visible = showWidthHeight; + this.selectionDrawModeSwapButton.Visible = showWidthHeight; + + this.selectionDrawModeUnits.Visible = anyVisible & (this.selectionDrawModeInfo.DrawMode == SelectionDrawMode.FixedSize); + + ResumeLayout(false); + PerformLayout(); + } + } + + public SelectionDrawModeInfo SelectionDrawModeInfo + { + get + { + return (this.selectionDrawModeInfo ?? SelectionDrawModeInfo.CreateDefault()).Clone(); + } + + set + { + if (this.selectionDrawModeInfo == null || !this.selectionDrawModeInfo.Equals(value)) + { + this.selectionDrawModeInfo = value.Clone(); + OnSelectionDrawModeInfoChanged(); + SyncSelectionDrawModeInfoUI(); + } + } + } + + public void PerformSelectionDrawModeInfoChanged() + { + OnSelectionDrawModeInfoChanged(); + } + + private string GetSelectionDrawModeString(SelectionDrawMode drawMode) + { + return PdnResources.GetString("ToolConfigStrip.SelectionDrawModeSplitButton." + drawMode.ToString() + ".Text"); + } + + private Image GetSelectionDrawModeImage(SelectionDrawMode drawMode) + { + return PdnResources.GetImageResource("Icons.ToolConfigStrip.SelectionDrawModeSplitButton." + drawMode.ToString() + ".png").Reference; + } + + private void SelectionDrawModeSplitButton_DropDownOpening(object sender, EventArgs e) + { + this.selectionDrawModeSplitButton.DropDownItems.Clear(); + + foreach (SelectionDrawMode sdm in + new SelectionDrawMode[] + { + SelectionDrawMode.Normal, + SelectionDrawMode.FixedRatio, + SelectionDrawMode.FixedSize + }) + { + ToolStripMenuItem sdmTSMI = new ToolStripMenuItem( + GetSelectionDrawModeString(sdm), + GetSelectionDrawModeImage(sdm), + delegate(object sender2, EventArgs e2) + { + ToolStripMenuItem asTSMI = (ToolStripMenuItem)sender2; + SelectionDrawMode newSDM = (SelectionDrawMode)asTSMI.Tag; + this.SelectionDrawModeInfo = this.SelectionDrawModeInfo.CloneWithNewDrawMode(newSDM); + }); + + sdmTSMI.Tag = sdm; + sdmTSMI.Checked = (sdm == this.SelectionDrawModeInfo.DrawMode); + + this.selectionDrawModeSplitButton.DropDownItems.Add(sdmTSMI); + } + } + + private SelectionDrawMode CycleSelectionDrawMode(SelectionDrawMode drawMode) + { + SelectionDrawMode newSDM; + + switch (drawMode) + { + case SelectionDrawMode.Normal: + newSDM = SelectionDrawMode.FixedRatio; + break; + + case SelectionDrawMode.FixedRatio: + newSDM = SelectionDrawMode.FixedSize; + break; + + case SelectionDrawMode.FixedSize: + newSDM = SelectionDrawMode.Normal; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return newSDM; + } + + private void SelectionDrawModeUnits_UnitsChanged(object sender, EventArgs e) + { + OnSelectionDrawModeUnitsChanging(); + this.SelectionDrawModeInfo = this.selectionDrawModeInfo.CloneWithNewUnits(this.selectionDrawModeUnits.Units); + OnSelectionDrawModeUnitsChanged(); + } + } +} diff --git a/src/ToolInfo.cs b/src/ToolInfo.cs new file mode 100644 index 0000000..e024a35 --- /dev/null +++ b/src/ToolInfo.cs @@ -0,0 +1,120 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace PaintDotNet +{ + internal class ToolInfo + { + private string name; + private string helpText; + private ImageResource image; + private bool skipIfActiveOnHotKey; + private char hotKey; + private Type toolType; + private ToolBarConfigItems toolBarConfigItems; + + public string Name + { + get + { + return this.name; + } + } + + public string HelpText + { + get + { + return this.helpText; + } + } + + public ImageResource Image + { + get + { + return this.image; + } + } + + public bool SkipIfActiveOnHotKey + { + get + { + return this.skipIfActiveOnHotKey; + } + } + + public char HotKey + { + get + { + return this.hotKey; + } + } + + public Type ToolType + { + get + { + return this.toolType; + } + } + + public ToolBarConfigItems ToolBarConfigItems + { + get + { + return this.toolBarConfigItems; + } + } + + public override bool Equals(object obj) + { + ToolInfo rhs = obj as ToolInfo; + + if (rhs == null) + { + return false; + } + + return (this.name == rhs.name) && + (this.helpText == rhs.helpText) && + (this.hotKey == rhs.hotKey) && + (this.skipIfActiveOnHotKey == rhs.skipIfActiveOnHotKey) && + (this.toolType == rhs.toolType); + } + + public override int GetHashCode() + { + return name.GetHashCode(); + } + + public ToolInfo( + string name, + string helpText, + ImageResource image, + char hotKey, + bool skipIfActiveOnHotKey, + ToolBarConfigItems toolBarConfigItems, + Type toolType) + { + this.name = name; + this.helpText = helpText; + this.image = image; + this.hotKey = hotKey; + this.skipIfActiveOnHotKey = skipIfActiveOnHotKey; + this.toolBarConfigItems = toolBarConfigItems; + this.toolType = toolType; + } + } +} diff --git a/src/ToolsControl.cs b/src/ToolsControl.cs new file mode 100644 index 0000000..bc5a595 --- /dev/null +++ b/src/ToolsControl.cs @@ -0,0 +1,227 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ToolsControl + : UserControl, + IToolChooser + { + private ToolStripEx toolStripEx; + private ImageList imageList; + private const int tbWidth = 2; // two buttons per line in the toolbars + private int ignoreToolClicked = 0; + private Control onePxSpacingLeft; + + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + public ToolsControl() + { + // This call is required by the Windows.Forms Form Designer. + InitializeComponent(); + } + + public event ToolClickedEventHandler ToolClicked; + protected virtual void OnToolClicked(Type toolType) + { + if (this.ignoreToolClicked <= 0) + { + if (ToolClicked != null) + { + ToolClicked(this, new ToolClickedEventArgs(toolType)); + } + } + } + + public void SetTools(ToolInfo[] toolInfos) + { + if (this.toolStripEx != null) + { + this.toolStripEx.Items.Clear(); + } + + this.imageList = new ImageList(); + this.imageList.ColorDepth = ColorDepth.Depth32Bit; + this.imageList.TransparentColor = Utility.TransparentKey; + + this.toolStripEx.ImageList = this.imageList; + + ToolStripItem[] buttons = new ToolStripItem[toolInfos.Length]; + string toolTipFormat = PdnResources.GetString("ToolsControl.ToolToolTip.Format"); + + for (int i = 0; i < toolInfos.Length; ++i) + { + ToolInfo toolInfo = toolInfos[i]; + ToolStripButton button = new ToolStripButton(); + + int imageIndex = imageList.Images.Add( + toolInfo.Image.Reference, + imageList.TransparentColor); + + button.ImageIndex = imageIndex; + button.Tag = toolInfo.ToolType; + button.ToolTipText = string.Format(toolTipFormat, toolInfo.Name, char.ToUpperInvariant(toolInfo.HotKey).ToString()); + buttons[i] = button; + } + + this.toolStripEx.Items.AddRange(buttons); + } + + public void SelectTool(Type toolType) + { + SelectTool(toolType, true); + } + + public void SelectTool(Type toolType, bool raiseEvent) + { + if (!raiseEvent) + { + ++this.ignoreToolClicked; + } + + try + { + foreach (ToolStripButton button in this.toolStripEx.Items) + { + if ((Type)button.Tag == toolType) + { + this.ToolStripEx_ItemClicked(this, new ToolStripItemClickedEventArgs(button)); + return; + } + } + + throw new ArgumentException("Tool type not found"); + } + + finally + { + if (!raiseEvent) + { + --this.ignoreToolClicked; + } + } + } + + protected override void OnLayout(LayoutEventArgs e) + { + int buttonWidth; + + if (this.toolStripEx.Items.Count > 0) + { + buttonWidth = this.toolStripEx.Items[0].Width; + } + else + { + buttonWidth = 0; + } + + this.toolStripEx.Width = + this.toolStripEx.Padding.Left + + (buttonWidth * tbWidth) + + (this.toolStripEx.Margin.Horizontal * tbWidth) + + this.toolStripEx.Padding.Right; + + this.toolStripEx.Height = this.toolStripEx.GetPreferredSize(this.toolStripEx.Size).Height; + + this.Width = this.toolStripEx.Width + this.onePxSpacingLeft.Width; + this.Height = this.toolStripEx.Height; + + base.OnLayout(e); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + + base.Dispose(disposing); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.toolStripEx = new ToolStripEx(); + this.onePxSpacingLeft = new Control(); + this.SuspendLayout(); + // + // toolStripEx + // + this.toolStripEx.Dock = System.Windows.Forms.DockStyle.Top; + this.toolStripEx.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.toolStripEx.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow; + this.toolStripEx.ItemClicked += new ToolStripItemClickedEventHandler(ToolStripEx_ItemClicked); + this.toolStripEx.Name = "toolStripEx"; + this.toolStripEx.AutoSize = true; + this.toolStripEx.RelinquishFocus += new EventHandler(ToolStripEx_RelinquishFocus); + // + // onePxSpacingLeft + // + this.onePxSpacingLeft.Dock = System.Windows.Forms.DockStyle.Left; + this.onePxSpacingLeft.Width = 1; + this.onePxSpacingLeft.Name = "onePxSpacingLeft"; + // + // MainToolBar + // + this.Controls.Add(this.toolStripEx); + this.Controls.Add(this.onePxSpacingLeft); + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = AutoScaleMode.Dpi; + this.Name = "MainToolBar"; + this.Size = new System.Drawing.Size(48, 328); + this.ResumeLayout(false); + } + + public event EventHandler RelinquishFocus; + private void OnRelinquishFocus() + { + if (RelinquishFocus != null) + { + RelinquishFocus(this, EventArgs.Empty); + } + } + + private void ToolStripEx_RelinquishFocus(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + + private void ToolStripEx_ItemClicked(object sender, ToolStripItemClickedEventArgs e) + { + foreach (ToolStripButton button in this.toolStripEx.Items) + { + button.Checked = (button == e.ClickedItem); + } + + OnToolClicked((Type)e.ClickedItem.Tag); + } + } +} + diff --git a/src/ToolsControl.resx b/src/ToolsControl.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/ToolsForm.cs b/src/ToolsForm.cs new file mode 100644 index 0000000..31d7909 --- /dev/null +++ b/src/ToolsForm.cs @@ -0,0 +1,103 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Collections; +using System.ComponentModel; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class ToolsForm + : FloatingToolForm + { + private ToolsControl toolsControl = null; + + public ToolsControl ToolsControl + { + get + { + return this.toolsControl; + } + } + + /// + /// Required designer variable. + /// + private System.ComponentModel.Container components = null; + + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + this.ClientSize = new Size(toolsControl.Width, toolsControl.Height); + } + + public ToolsForm() + { + // + // Required for Windows Form Designer support + // + InitializeComponent(); + + this.Text = PdnResources.GetString("MainToolBarForm.Text"); + } + + /// + /// Clean up any resources being used. + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (components != null) + { + components.Dispose(); + components = null; + } + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.toolsControl = new PaintDotNet.ToolsControl(); + this.SuspendLayout(); + // + // toolsControl + // + this.toolsControl.Location = new System.Drawing.Point(0, 0); + this.toolsControl.Name = "toolsControl"; + this.toolsControl.Size = new System.Drawing.Size(50, 88); + this.toolsControl.TabIndex = 0; + this.toolsControl.RelinquishFocus += new EventHandler(ToolsControl_RelinquishFocus); + // + // MainToolBarForm + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; + this.ClientSize = new System.Drawing.Size(50, 273); + this.Controls.Add(this.toolsControl); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Name = "ToolsForm"; + this.Controls.SetChildIndex(this.toolsControl, 0); + this.ResumeLayout(false); + } + #endregion + + private void ToolsControl_RelinquishFocus(object sender, EventArgs e) + { + OnRelinquishFocus(); + } + } +} diff --git a/src/ToolsForm.resx b/src/ToolsForm.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/TransferProgressDialog.cs b/src/TransferProgressDialog.cs new file mode 100644 index 0000000..c3369c5 --- /dev/null +++ b/src/TransferProgressDialog.cs @@ -0,0 +1,222 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class TransferProgressDialog + : PdnBaseForm + { + private HeaderLabel separator1; + private ProgressBar progressBar; + private Button cancelButton; + private Label itemText; + private Label operationProgress; + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + public string ItemText + { + get + { + return this.itemText.Text; + } + + set + { + this.itemText.Text = value; + } + } + + public string OperationProgress + { + get + { + return this.operationProgress.Text; + } + + set + { + this.operationProgress.Text = value; + } + } + + public event EventHandler CancelClicked; + + protected virtual void OnCancelClicked() + { + if (CancelClicked != null) + { + CancelClicked(this, EventArgs.Empty); + } + } + + public ProgressBar ProgressBar + { + get + { + return this.progressBar; + } + } + + public bool CancelEnabled + { + get + { + return this.cancelButton.Enabled; + } + + set + { + this.cancelButton.Enabled = value; + } + } + + public TransferProgressDialog() + { + PdnBaseForm.RegisterFormHotKey( + Keys.Escape, + delegate(Keys keys) + { + OnCancelClicked(); + return true; + }); + + InitializeComponent(); + } + + protected override void OnLoad(EventArgs e) + { + SystemLayer.UI.DisableCloseBox(this); + base.OnLoad(e); + } + + public override void LoadResources() + { + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + base.LoadResources(); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.cancelButton = new System.Windows.Forms.Button(); + this.itemText = new System.Windows.Forms.Label(); + this.separator1 = new PaintDotNet.HeaderLabel(); + this.operationProgress = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // progressBar + // + this.progressBar.Location = new System.Drawing.Point(10, 51); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(405, 19); + this.progressBar.TabIndex = 0; + // + // cancelButton + // + this.cancelButton.Location = new System.Drawing.Point(342, 91); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "cancelButton"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.FlatStyle = FlatStyle.System; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // itemText + // + this.itemText.AutoEllipsis = true; + this.itemText.Location = new System.Drawing.Point(8, 8); + this.itemText.Name = "itemText"; + this.itemText.Size = new System.Drawing.Size(404, 13); + this.itemText.TabIndex = 2; + this.itemText.Text = "itemText"; + // + // separator1 + // + this.separator1.Location = new System.Drawing.Point(9, 77); + this.separator1.Name = "separator1"; + this.separator1.RightMargin = 0; + this.separator1.Size = new System.Drawing.Size(406, 14); + this.separator1.TabIndex = 4; + this.separator1.TabStop = false; + // + // operationProgress + // + this.operationProgress.AutoEllipsis = true; + this.operationProgress.Location = new System.Drawing.Point(8, 28); + this.operationProgress.Name = "operationProgress"; + this.operationProgress.Size = new System.Drawing.Size(403, 13); + this.operationProgress.TabIndex = 5; + this.operationProgress.Text = "operationProgress"; + // + // TransferProgressDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(423, 121); + this.Controls.Add(this.operationProgress); + this.Controls.Add(this.progressBar); + this.Controls.Add(this.itemText); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.separator1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Location = new System.Drawing.Point(0, 0); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "TransferProgressDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "TransferProgressDialog"; + this.Controls.SetChildIndex(this.separator1, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.itemText, 0); + this.Controls.SetChildIndex(this.progressBar, 0); + this.Controls.SetChildIndex(this.operationProgress, 0); + this.ResumeLayout(false); + + } + #endregion + + private void CancelButton_Click(object sender, EventArgs e) + { + OnCancelClicked(); + } + } +} \ No newline at end of file diff --git a/src/UnsavedChangesDialog.cs b/src/UnsavedChangesDialog.cs new file mode 100644 index 0000000..575ac66 --- /dev/null +++ b/src/UnsavedChangesDialog.cs @@ -0,0 +1,329 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal class UnsavedChangesDialog + : PdnBaseForm + { + /// + /// Required designer variable. + /// + private IContainer components = null; + + private DocumentStrip documentStrip; + private HeaderLabel documentListHeader; + private System.Windows.Forms.HScrollBar hScrollBar; + private CommandButton saveButton; + private CommandButton dontSaveButton; + private CommandButton cancelButton; + private System.Windows.Forms.Label infoLabel; + + private DocumentWorkspace[] documents; + + public DocumentWorkspace[] Documents + { + get + { + return (DocumentWorkspace[])this.documents.Clone(); + } + + set + { + this.documents = (DocumentWorkspace[])value.Clone(); + this.documentStrip.ClearItems(); + + foreach (DocumentWorkspace dw in this.documents) + { + this.documentStrip.AddDocumentWorkspace(dw); + } + + this.hScrollBar.Maximum = this.documentStrip.ViewRectangle.Width; + this.hScrollBar.LargeChange = this.documentStrip.ClientSize.Width; + + if (this.documentStrip.ClientRectangle.Width > this.documentStrip.ViewRectangle.Width) + { + this.hScrollBar.Enabled = false; + } + else + { + this.hScrollBar.Enabled = true; + } + + ImageStrip.Item[] items = this.documentStrip.Items; + foreach (ImageStrip.Item item in items) + { + item.Checked = false; + } + } + } + + public DocumentWorkspace SelectedDocument + { + get + { + return this.documentStrip.SelectedDocument; + } + + set + { + this.documentStrip.SelectDocumentWorkspace(value); + } + } + + public UnsavedChangesDialog() + { + InitializeComponent(); + } + + /// + /// Clean up any resources being used. + /// + /// + /// true if managed resources should be disposed; otherwise, false. + /// + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + + base.Dispose(disposing); + } + + public override void LoadResources() + { + this.Text = PdnResources.GetString("UnsavedChangesDialog.Text"); + this.Icon = Utility.ImageToIcon(PdnResources.GetImageResource("Icons.WarningIcon.png").Reference, false); + + this.infoLabel.Text = PdnResources.GetString("UnsavedChangesDialog.InfoLabel.Text"); + this.documentListHeader.Text = PdnResources.GetString("UnsavedChangesDialog.DocumentListHeader.Text"); + + this.saveButton.ActionText = PdnResources.GetString("UnsavedChangesDialog.SaveButton.ActionText"); + this.saveButton.ExplanationText = PdnResources.GetString("UnsavedChangesDialog.SaveButton.ExplanationText"); + this.saveButton.ActionImage = PdnResources.GetImageResource("Icons.UnsavedChangesDialog.SaveButton.png").Reference; + + this.dontSaveButton.ActionText = PdnResources.GetString("UnsavedChangesDialog.DontSaveButton.ActionText"); + this.dontSaveButton.ExplanationText = PdnResources.GetString("UnsavedChangesDialog.DontSaveButton.ExplanationText"); + this.dontSaveButton.ActionImage = PdnResources.GetImageResource("Icons.MenuFileCloseIcon.png").Reference; + + this.cancelButton.ActionText = PdnResources.GetString("UnsavedChangesDialog.CancelButton.ActionText"); + this.cancelButton.ExplanationText = PdnResources.GetString("UnsavedChangesDialog.CancelButton.ExplanationText"); + this.cancelButton.ActionImage = PdnResources.GetImageResource("Icons.CancelIcon.png").Reference; + + base.LoadResources(); + } + + protected override void OnLayout(LayoutEventArgs levent) + { + int leftMargin = UI.ScaleWidth(8); + int rightMargin = UI.ScaleWidth(8); + int topMargin = UI.ScaleHeight(8); + int bottomMargin = UI.ScaleHeight(8); + int afterInfoLabelVMargin = UI.ScaleHeight(8); + int afterDocumentListHeaderVMargin = UI.ScaleHeight(8); + int afterDocumentListVMargin = UI.ScaleHeight(8); + int commandButtonVMargin = UI.ScaleHeight(0); + int insetWidth = ClientSize.Width - leftMargin - rightMargin; + + int y = topMargin; + + this.infoLabel.Location = new Point(leftMargin, y); + this.infoLabel.Width = insetWidth; + this.infoLabel.Height = this.infoLabel.GetPreferredSize(new Size(this.infoLabel.Width, 0)).Height; + y += this.infoLabel.Height + afterInfoLabelVMargin; + + this.documentListHeader.Location = new Point(leftMargin, y); + this.documentListHeader.Width = insetWidth; + y += this.documentListHeader.Height + afterDocumentListHeaderVMargin; + + this.documentStrip.Location = new Point(leftMargin, y); + this.documentStrip.Size = new Size(insetWidth, UI.ScaleHeight(72)); + this.hScrollBar.Location = new Point(leftMargin, this.documentStrip.Bottom); + this.hScrollBar.Width = insetWidth; + y += this.documentStrip.Height + hScrollBar.Height + afterDocumentListVMargin; + + this.saveButton.Location = new Point(leftMargin, y); + this.saveButton.Width = insetWidth; + this.saveButton.PerformLayout(); + y += this.saveButton.Height + commandButtonVMargin; + + this.dontSaveButton.Location = new Point(leftMargin, y); + this.dontSaveButton.Width = insetWidth; + this.dontSaveButton.PerformLayout(); + y += this.dontSaveButton.Height + commandButtonVMargin; + + this.cancelButton.Location = new Point(leftMargin, y); + this.cancelButton.Width = insetWidth; + this.cancelButton.PerformLayout(); + y += this.cancelButton.Height + bottomMargin; + + this.ClientSize = new Size(ClientSize.Width, y); + base.OnLayout(levent); + } + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.documentStrip = new PaintDotNet.DocumentStrip(); + this.documentListHeader = new PaintDotNet.HeaderLabel(); + this.hScrollBar = new System.Windows.Forms.HScrollBar(); + this.saveButton = new PaintDotNet.CommandButton(); + this.dontSaveButton = new PaintDotNet.CommandButton(); + this.cancelButton = new PaintDotNet.CommandButton(); + this.infoLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // documentStrip + // + this.documentStrip.BackColor = System.Drawing.SystemColors.ButtonHighlight; + this.documentStrip.DocumentClicked += DocumentList_DocumentClicked; + this.documentStrip.DrawDirtyOverlay = false; + this.documentStrip.EnsureSelectedIsVisible = false; + this.documentStrip.ManagedFocus = true; + this.documentStrip.Name = "documentList"; + this.documentStrip.ScrollOffset = 0; + this.documentStrip.ScrollOffsetChanged += new EventHandler(DocumentList_ScrollOffsetChanged); + this.documentStrip.ShowCloseButtons = false; + this.documentStrip.ShowScrollButtons = false; + this.documentStrip.TabIndex = 0; + this.documentStrip.ThumbnailUpdateLatency = 10; + // + // documentListHeader + // + this.documentListHeader.Name = "documentListHeader"; + this.documentListHeader.RightMargin = 0; + this.documentListHeader.TabIndex = 1; + this.documentListHeader.TabStop = false; + // + // hScrollBar + // + this.hScrollBar.Name = "hScrollBar"; + this.hScrollBar.TabIndex = 2; + this.hScrollBar.ValueChanged += new System.EventHandler(this.HScrollBar_ValueChanged); + // + // saveButton + // + this.saveButton.ActionImage = null; + this.saveButton.AutoSize = true; + this.saveButton.Name = "saveButton"; + this.saveButton.TabIndex = 4; + this.saveButton.Click += new System.EventHandler(this.SaveButton_Click); + // + // dontSaveButton + // + this.dontSaveButton.ActionImage = null; + this.dontSaveButton.AutoSize = true; + this.dontSaveButton.Name = "dontSaveButton"; + this.dontSaveButton.TabIndex = 5; + this.dontSaveButton.Click += new System.EventHandler(this.DontSaveButton_Click); + // + // cancelButton + // + this.cancelButton.ActionImage = null; + this.cancelButton.AutoSize = true; + this.cancelButton.Name = "cancelButton"; + this.cancelButton.TabIndex = 6; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // infoLabel + // + this.infoLabel.Name = "infoLabel"; + this.infoLabel.TabIndex = 7; + // + // UnsavedChangesDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(450, 100); + this.Controls.Add(this.infoLabel); + this.Controls.Add(this.documentListHeader); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.hScrollBar); + this.Controls.Add(this.dontSaveButton); + this.Controls.Add(this.documentStrip); + this.Controls.Add(this.saveButton); + this.AcceptButton = this.saveButton; + this.CancelButton = this.cancelButton; + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.Location = new System.Drawing.Point(0, 0); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UnsavedChangesDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Controls.SetChildIndex(this.saveButton, 0); + this.Controls.SetChildIndex(this.documentStrip, 0); + this.Controls.SetChildIndex(this.dontSaveButton, 0); + this.Controls.SetChildIndex(this.hScrollBar, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.documentListHeader, 0); + this.Controls.SetChildIndex(this.infoLabel, 0); + this.ResumeLayout(false); + this.PerformLayout(); + } + + public event EventHandler> DocumentClicked; + protected virtual void OnDocumentClicked(DocumentWorkspace dw) + { + if (DocumentClicked != null) + { + DocumentClicked(this, new EventArgs(dw)); + } + } + + private void DocumentList_DocumentClicked( + object sender, + EventArgs> e) + { + this.documentStrip.Update(); + OnDocumentClicked(e.Data.First); + } + + private void HScrollBar_ValueChanged(object sender, EventArgs e) + { + this.documentStrip.ScrollOffset = this.hScrollBar.Value; + } + + private void DocumentList_ScrollOffsetChanged(object sender, EventArgs e) + { + this.hScrollBar.Value = this.documentStrip.ScrollOffset; + } + + private void CancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + + private void DontSaveButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.No; + Close(); + } + + private void SaveButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Yes; + Close(); + } + } +} \ No newline at end of file diff --git a/src/UnsavedChangesDialog.resx b/src/UnsavedChangesDialog.resx new file mode 100644 index 0000000..e69de29 diff --git a/src/UpdateMonitor/AssemblyInfo.cs b/src/UpdateMonitor/AssemblyInfo.cs new file mode 100644 index 0000000..bea1b0c --- /dev/null +++ b/src/UpdateMonitor/AssemblyInfo.cs @@ -0,0 +1,25 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET Update Monitor")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] diff --git a/src/UpdateMonitor/Program.cs b/src/UpdateMonitor/Program.cs new file mode 100644 index 0000000..8595a71 --- /dev/null +++ b/src/UpdateMonitor/Program.cs @@ -0,0 +1,166 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; + +namespace PaintDotNet +{ + public static class UpdateMonitor + { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode); + + private const uint ERROR_SUCCESS = 0; + private const uint INFINITE = 0xffffffff; + private const uint WAIT_ABANDONED = 0x00000080; + private const uint WAIT_OBJECT_0 = 0; + private const uint WAIT_TIMEOUT = 0x00000102; + private const uint WAIT_FAILED = 0xffffffff; + private const uint STILL_ACTIVE = 259; + + public static int Main(string[] args) + { + try + { + int returnValue = MainImpl(args); + return returnValue; + } + + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + return 1; + } + } + + private static int MainImpl(string[] args) + { + Console.WriteLine("Command line is: " + Environment.CommandLine); + + uint dwResult = ERROR_SUCCESS; + bool bResult = true; + + if (args.Length != 1) + { + // Must specify a handle value + return 1; + } + + ulong uHandleValue = ulong.Parse(args[0], CultureInfo.InvariantCulture); + Console.WriteLine("Handle value is: " + uHandleValue); + + long handleValue = unchecked((long)uHandleValue); + IntPtr hProcess = new IntPtr(handleValue); + + // Wait for the given process to finish + Console.Write("Waiting for process to exit ... "); + dwResult = WaitForSingleObject(hProcess, INFINITE); + Console.WriteLine("done"); + + // Get its exit code + uint dwExitCode = 0; + Console.Write("Retrieving process exit code ... "); + bResult = GetExitCodeProcess(hProcess, out dwExitCode); + + if (!bResult) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error, "GetExitCodeProcess returned false, and error " + error); + } + + if (dwExitCode == STILL_ACTIVE) + { + throw new ApplicationException("process was still active even though WaitForSingleObject() completed"); + } + + if (dwExitCode != 0) + { + throw new ApplicationException("process did not complete successfully, its exit code was " + dwExitCode); + } + + Console.WriteLine("exit code was " + dwExitCode); + + // Retrieve paint.net's location from the registry + Console.Write(@"Retrieving install directory from registry, HKLM \ Software \ Paint.NET \ TARGETDIR ... "); + + const string subKeyName = @"SOFTWARE\Paint.NET"; + RegistryKey key = Registry.LocalMachine.OpenSubKey(subKeyName, false); + + if (key == null) + { + throw new ApplicationException("registry key HKLM\\" + subKeyName + " could not be opened: OpenSubKey returned null"); + } + + const string valueName = "TARGETDIR"; + object targetDirObj = key.GetValue(valueName, null, RegistryValueOptions.DoNotExpandEnvironmentNames); + + if (targetDirObj == null) + { + throw new ApplicationException(valueName + " value was retrieved from registry as null"); + } + + string targetDir = targetDirObj as string; + + if (targetDir == null) + { + throw new ApplicationException(valueName + " was not a string; its retrieved .NET type was " + targetDirObj.GetType().Name + ", and ToString() value was " + targetDirObj.ToString()); + } + + Console.WriteLine(targetDir); + + // Validate as dir name + Console.Write("Validating directory name ... "); + if (!Directory.Exists(targetDir)) + { + throw new ApplicationException("Directory.Exists(" + targetDir + ") returned false"); + } + + Console.WriteLine("done"); + + // Determine the exe that we will be launching + Console.Write("Building executable path to launch: "); + + const string pdnExe = "PaintDotNet.exe"; + string pdnExePath = Path.Combine(targetDir, pdnExe); + + Console.WriteLine(pdnExePath); + + // Validate as path/exe name + Console.Write("Validating path name ... "); + + if (!File.Exists(pdnExePath)) + { + throw new ApplicationException("File.Exists(" + pdnExePath + ") returned false"); + } + + // Launch it + Console.Write("Executing: "); + ProcessStartInfo psi = new ProcessStartInfo(pdnExePath); + psi.UseShellExecute = true; + psi.WorkingDirectory = targetDir; + + Process process = Process.Start(psi); + + Console.WriteLine("success. PID = " + process.Id); + process.Dispose(); + + return 0; + } + } +} diff --git a/src/UpdateMonitor/UpdateMonitor.csproj b/src/UpdateMonitor/UpdateMonitor.csproj new file mode 100644 index 0000000..c226d30 --- /dev/null +++ b/src/UpdateMonitor/UpdateMonitor.csproj @@ -0,0 +1,65 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {D1E5865F-9DC5-4504-A484-5CE8A8A79621} + WinExe + Properties + UpdateMonitor + UpdateMonitor + + + + + 2.0 + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + @rem Embed manifest +call "$(SolutionDir)Manifests\embedManifest.bat" "$(TargetPath)" "$(SolutionDir)Manifests\asInvoker.xml" +call "$(SolutionDir)Manifests\embedManifest.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" "$(SolutionDir)Manifests\asInvoker.xml" + + + \ No newline at end of file diff --git a/src/UpdateMonitor/app.config b/src/UpdateMonitor/app.config new file mode 100644 index 0000000..6c98a46 --- /dev/null +++ b/src/UpdateMonitor/app.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Updates/AbortedState.cs b/src/Updates/AbortedState.cs new file mode 100644 index 0000000..6a74cbb --- /dev/null +++ b/src/Updates/AbortedState.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class AbortedState + : UpdatesState + { + public override void OnEnteredState() + { + base.OnEnteredState(); + } + + public override void ProcessInput(object input, out State newState) + { + throw new Exception("The method or operation is not implemented."); + } + + public AbortedState() + : base(true, false, MarqueeStyle.None) + { + } + } +} diff --git a/src/Updates/CheckingState.cs b/src/Updates/CheckingState.cs new file mode 100644 index 0000000..fc6e78b --- /dev/null +++ b/src/Updates/CheckingState.cs @@ -0,0 +1,578 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class CheckingState + : UpdatesState + { + // versions.txt schema: + // ; This is a comment + // DownloadPageUrl=downloadPageUrl // This should link to the main download page + // StableVersions=version1,version2,...,versionN // A comma-separated list of all available stable versions available for download + // BetaVersions=version1,version2,...,versionN // A comma-separated list of all available beta/pre-release versions available for download + // + // version1_Name=name1 // Friendly name for a given version + // version1_NetFxVersion=netFxVersion1 // What version of .NET does this version require? + // For .NET 2.0, this should be specificed as 2.0.x, where x used to be the build number (50727) but is now ignored + // For .NET 3.5, this should be specificed as 3.5.x, where x is the required service pack level + // version1_InfoUrl=infoUrl1 // A URL that contains information about the given version + // version1_ZipUrlList="zipUrl1","zipUrl2",...,"zipUrlN" + // // A comma-delimited list of URL's for mirrors to download the updater. One will be chosen at random. + // version1_FullZipUrlList="zipFullUrl1","zipFullUrl2",...,"zipFullUrlN" + // // A comma-delimited list of URL's for mirrors to download the 'full' installer ('full' means it bundles the appropriate .NET installer + // ... + // versionN_Name=name1 // Friendly name for a given version + // versionN_NetFxVersion=netFxVersionN // What version of .NET does this version require? + // versionN_InfoUrl=infoUrlN // A URL that contains information about the given version + // versionN_ZipUrlList=zipUrlN // A comma-delimited list of URL's for mirrors to download the updater. One will be chosen at random. + // versionN_FullZipUrl=zipFullUrlN // A comma-delimited list of URL's for mirrors to download the 'full' installer ('full' means it bundles the appropriate .NET installer + // + // Example: + // ; Paint.NET versions download manifest + // DownloadPageUrl=http://www.getpaint.net/download.htm + // StableVersions=2.1.1958.27164 + // BetaVersions=2.5.2013.31044 + // + // 2.1.1958.27164_Name=Paint.NET v2.1b + // 2.1.1958.27164_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_1 + // 2.1.1958.27164_NetFxVersion=1.1.4322 + // 2.1.1958.27164_ZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_1b.zip + // 2.1.1958.27164_FullZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_1b_Full.zip + // + // 2.5.2013.31044_Name=Paint.NET v2.5 + // 2.5.2013.31044_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_5 + // 2.5.2013.31044_NetFxVersion=1.1.4322 + // 2.5.2013.31044_ZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_5.zip + // 2.5.2013.31044_FullZipUrlList=http://www.getpaint.net/zip/PaintDotNet_2_5_Full.zip + // + // 2.6.2113.23752_Name=Paint.NET v2.6 Beta 1 + // 2.6.2113.23752_InfoUrl=http://www.getpaint.net/roadmap.htm#v2_6 + // 2.6.2113.23752_NetFxVersion=2.0.50727 + // 2.6.2113.23752_ZipUrlList="http://www.getpaint.net/zip/PaintDotNet_2_6_Beta1.zip","http://www.someotherhost.com/files/PaintDotNet_2_6_Beta1.zip" + // 2.6.2113.23752_FullZipUrlList="http://www.getpaint.net/zip/PaintDotNet_2_6_Beta1_Full.zip","http://www.someotherhost.com/files/PaintDotNet_2_6_Beta1_Full.zip" + // + // Notes: + // A line may have a comment on it. Just start the line with an asterisk, '*' + // Versions must be formatted in a manner parseable by the System.Version class. + // BetaVersions may be an empty list: "BetaVersions=" + // versionN_InfoUrl may not be blank + // versionN_ZipUrl may not be blank + // versionN_ZipUrlSize must be greater than 0. + // If any error is detected while parsing, the entire schema will be declared as invalid and ignored. + // Everything is case-sensitive. + + private const string downloadPageUrlName = "DownloadPageUrl"; + private const string stableVersionsName = "StableVersions"; + private const string betaVersionsName = "BetaVersions"; + private const string nameNameFormat = "{0}_Name"; + private const string netFxVersionNameFormat = "{0}_NetFxVersion"; + private const string infoUrlNameFormat = "{0}_InfoUrl"; + private const string zipUrlListNameFormat = "{0}_ZipUrlList"; + private const string fullZipUrlListNameFormat = "{0}_FullZipUrlList"; + private const char commentChar = ';'; + + // {0} is schema version + // {1} is Windows revision (501 for XP, 502 for Server 2k3, 600 for Vista, 601 for Win7) + // {2} is platform (x86, x64) + // {3} is the locale (en, etc) + private const string versionManifestRelativeUrlFormat = "/updates/versions.{0}.{1}.{2}.{3}.txt"; + private const string versionManifestTestRelativeUrl = "/updates/versions.txt.test.txt"; + private const int schemaVersion = 5; + + private PdnVersionManifest manifest; + private int latestVersionIndex; + private Exception exception; + + private ManualResetEvent checkingEvent = new ManualResetEvent(false); + private ManualResetEvent abortEvent = new ManualResetEvent(false); + + private static string GetNeutralLocaleName(CultureInfo ci) + { + if (ci.IsNeutralCulture) + { + return ci.Name; + } + + if (ci.Parent == null) + { + return ci.Name; + } + + if (ci.Parent == ci) + { + return ci.Name; + } + + return GetNeutralLocaleName(ci.Parent); + } + + private static string VersionManifestUrl + { + get + { + Uri websiteUri = new Uri(InvariantStrings.WebsiteUrl); + string versionManifestUrl; + + if (PdnInfo.IsTestMode) + { + Uri versionManifestTestUri = new Uri(websiteUri, versionManifestTestRelativeUrl); + versionManifestUrl = versionManifestTestUri.ToString(); + } + else + { + string schemaVersionStr = schemaVersion.ToString(CultureInfo.InvariantCulture); + Version osVersion = Environment.OSVersion.Version; + ProcessorArchitecture platform = SystemLayer.Processor.Architecture; + OSType osType = SystemLayer.OS.Type; + + // If this is XP x64, we want to fudge the NT version to be 5.1 instead of 5.2 + // This helps us discern between XP x64 and Server 2003 x64 stats. + if (osVersion.Major == 5 && osVersion.Minor == 2 && platform == ProcessorArchitecture.X64 && osType == OSType.Workstation) + { + osVersion = new Version(5, 1, osVersion.Build, osVersion.Revision); + } + + int osVersionInt = (osVersion.Major * 100) + osVersion.Minor; + string osVersionStr = osVersionInt.ToString(CultureInfo.InvariantCulture); + string platformStr = platform.ToString().ToLower(); + string localeStr = GetNeutralLocaleName(PdnResources.Culture); + Uri versionManifestUrlFormatUri = new Uri(websiteUri, versionManifestRelativeUrlFormat); + string versionManifestUrlFormat = versionManifestUrlFormatUri.ToString(); + + versionManifestUrl = string.Format(versionManifestUrlFormat, schemaVersionStr, osVersionStr, platformStr, localeStr); + } + + return versionManifestUrl; + } + } + + private static string[] BreakIntoLines(string text) + { + StringReader sr = new StringReader(text); + List strings = new List(); + string line; + + while ((line = sr.ReadLine()) != null) + { + if (line.Length > 0 && line[0] != commentChar) + { + strings.Add(line); + } + } + + return strings.ToArray(); + } + + private static void LineToNameValue(string line, out string name, out string value) + { + int equalIndex = line.IndexOf('='); + + if (equalIndex == -1) + { + throw new FormatException("Line had no equal sign (=) present"); + } + + name = line.Substring(0, equalIndex); + + int valueLength = line.Length - equalIndex - 1; + + if (valueLength == 0) + { + value = string.Empty; + } + else + { + value = line.Substring(equalIndex + 1, line.Length - equalIndex - 1); + } + } + + private static NameValueCollection LinesToNameValues(string[] lines) + { + NameValueCollection nvc = new NameValueCollection(); + + foreach (string line in lines) + { + string name; + string value; + + LineToNameValue(line, out name, out value); + nvc.Add(name, value); + } + + return nvc; + } + + private static Version[] VersionStringToArray(string versions) + { + string[] versionStrings = versions.Split(','); + + // For the 'null' case... + if (versionStrings.Length == 0 || + (versionStrings.Length == 1 && versionStrings[0].Length == 0)) + { + return new Version[0]; + } + + Version[] versionList = new Version[versionStrings.Length]; + + for (int i = 0; i < versionStrings.Length; ++i) + { + versionList[i] = new Version(versionStrings[i]); + } + + return versionList; + } + + private static string[] BuildVersionValueMapping(NameValueCollection nameValues, Version[] versions, string secondaryKeyFormat) + { + string[] newValues = new string[versions.Length]; + + for (int i = 0; i < versions.Length; ++i) + { + string versionString = versions[i].ToString(); + string secondaryKey = string.Format(secondaryKeyFormat, versionString); + string secondaryValue = nameValues[secondaryKey]; + newValues[i] = secondaryValue; + } + + return newValues; + } + + private static void SplitUrlList(string urlList, List urlsOutput) + { + if (string.IsNullOrEmpty(urlList)) + { + return; + } + + string trimUrlList = urlList.Trim(); + string url; + int commaIndex; + + if (trimUrlList[0] == '"') + { + int endQuoteIndex = trimUrlList.IndexOf('"', 1); + commaIndex = trimUrlList.IndexOf(',', endQuoteIndex); + url = trimUrlList.Substring(1, endQuoteIndex - 1); + } + else + { + commaIndex = trimUrlList.IndexOf(','); + + if (commaIndex == -1) + { + url = trimUrlList; + } + else + { + url = trimUrlList.Substring(0, commaIndex); + } + } + + string urlTail; + if (commaIndex == -1) + { + urlTail = null; + } + else + { + urlTail = trimUrlList.Substring(commaIndex + 1); + } + + urlsOutput.Add(url); + SplitUrlList(urlTail, urlsOutput); + } + + /// + /// Downloads the latest updates manifest from the Paint.NET web server. + /// + /// The latest updates manifest, or null if there was an error in which case the exception argument will be non-null. + private static PdnVersionManifest GetUpdatesManifest(out Exception exception) + { + try + { + string versionsUrl = VersionManifestUrl; + + Uri versionsUri = new Uri(versionsUrl); + byte[] manifestBuffer = Utility.DownloadSmallFile(versionsUri); + string manifestText = System.Text.Encoding.UTF8.GetString(manifestBuffer); + string[] manifestLines = BreakIntoLines(manifestText); + NameValueCollection nameValues = LinesToNameValues(manifestLines); + + string downloadPageUrl = nameValues[downloadPageUrlName]; + + string stableVersionsStrings = nameValues[stableVersionsName]; + Version[] stableVersions = VersionStringToArray(stableVersionsStrings); + string[] stableNames = BuildVersionValueMapping(nameValues, stableVersions, nameNameFormat); + string[] stableNetFxVersions = BuildVersionValueMapping(nameValues, stableVersions, netFxVersionNameFormat); + string[] stableInfoUrls = BuildVersionValueMapping(nameValues, stableVersions, infoUrlNameFormat); + string[] stableZipUrls = BuildVersionValueMapping(nameValues, stableVersions, zipUrlListNameFormat); + string[] stableFullZipUrls = BuildVersionValueMapping(nameValues, stableVersions, fullZipUrlListNameFormat); + + string betaVersionsStrings = nameValues[betaVersionsName]; + Version[] betaVersions = VersionStringToArray(betaVersionsStrings); + string[] betaNames = BuildVersionValueMapping(nameValues, betaVersions, nameNameFormat); + string[] betaNetFxVersions = BuildVersionValueMapping(nameValues, betaVersions, netFxVersionNameFormat); + string[] betaInfoUrls = BuildVersionValueMapping(nameValues, betaVersions, infoUrlNameFormat); + string[] betaZipUrls = BuildVersionValueMapping(nameValues, betaVersions, zipUrlListNameFormat); + string[] betaFullZipUrls = BuildVersionValueMapping(nameValues, betaVersions, fullZipUrlListNameFormat); + + PdnVersionInfo[] versionInfos = new PdnVersionInfo[betaVersions.Length + stableVersions.Length]; + + int cursor = 0; + for (int i = 0; i < stableVersions.Length; ++i) + { + List zipUrlList = new List(); + SplitUrlList(stableZipUrls[i], zipUrlList); + + List fullZipUrlList = new List(); + SplitUrlList(stableFullZipUrls[i], fullZipUrlList); + + Version netFxVersion = new Version(stableNetFxVersions[i]); + + if (netFxVersion.Major == 2 && netFxVersion.Minor == 0) + { + netFxVersion = new Version(2, 0, 0); // discard the build # that is specified, since we use that for Service Pack level now + } + + PdnVersionInfo info = new PdnVersionInfo( + stableVersions[i], + stableNames[i], + netFxVersion.Major, + netFxVersion.Minor, + netFxVersion.Build, // service pack + stableInfoUrls[i], + zipUrlList.ToArray(), + fullZipUrlList.ToArray(), + true); + + versionInfos[cursor] = info; + ++cursor; + } + + for (int i = 0; i < betaVersions.Length; ++i) + { + List zipUrlList = new List(); + SplitUrlList(betaZipUrls[i], zipUrlList); + + List fullZipUrlList = new List(); + SplitUrlList(betaFullZipUrls[i], fullZipUrlList); + + Version netFxVersion = new Version(betaNetFxVersions[i]); + + if (netFxVersion.Major == 2 && netFxVersion.Minor == 0) + { + netFxVersion = new Version(2, 0, 0); // discard the build # that is specified, since we use that for Service Pack level now + } + + PdnVersionInfo info = new PdnVersionInfo( + betaVersions[i], + betaNames[i], + netFxVersion.Major, + netFxVersion.Minor, + netFxVersion.Build, // service pack + betaInfoUrls[i], + zipUrlList.ToArray(), + fullZipUrlList.ToArray(), + false); + + versionInfos[cursor] = info; + ++cursor; + } + + PdnVersionManifest manifest = new PdnVersionManifest(downloadPageUrl, versionInfos); + exception = null; + return manifest; + } + + catch (Exception ex) + { + exception = ex; + return null; + } + } + + private static void CheckForUpdates( + out PdnVersionManifest manifestResult, + out int latestVersionIndexResult, + out Exception exception) + { + exception = null; + PdnVersionManifest manifest = null; + manifestResult = null; + latestVersionIndexResult = -1; + + int retries = 2; + + while (retries > 0) + { + try + { + manifest = GetUpdatesManifest(out exception); + retries = 0; + } + + catch (Exception ex) + { + exception = ex; + --retries; + + if (retries == 0) + { + manifest = null; + } + } + } + + if (manifest != null) + { + int stableIndex = manifest.GetLatestStableVersionIndex(); + int betaIndex = manifest.GetLatestBetaVersionIndex(); + + // Check for betas as well? + bool checkForBetas = ("1" == Settings.SystemWide.GetString(SettingNames.AlsoCheckForBetas, "0")); + + // Figure out which version we want to compare against the current version + int latestIndex = stableIndex; + + if (checkForBetas) + { + // If they like betas, and if the beta is newer than the latest stable release, + // then offer it to them. + if (betaIndex != -1 && + (stableIndex == -1 || manifest.VersionInfos[betaIndex].Version >= manifest.VersionInfos[stableIndex].Version)) + { + latestIndex = betaIndex; + } + } + + // Now compare that version against the current version + if (latestIndex != -1) + { + if (PdnInfo.IsTestMode || + manifest.VersionInfos[latestIndex].Version > PdnInfo.GetVersion()) + { + manifestResult = manifest; + latestVersionIndexResult = latestIndex; + } + } + } + } + + public override bool CanAbort + { + get + { + return true; + } + } + + protected override void OnAbort() + { + this.abortEvent.Set(); + base.OnAbort(); + } + + private void DoCheckThreadProc(object ignored) + { + try + { + System.Threading.Thread.Sleep(1500); + CheckForUpdates(out this.manifest, out this.latestVersionIndex, out this.exception); + } + + finally + { + this.checkingEvent.Set(); + } + } + + public override void OnEnteredState() + { + this.checkingEvent.Reset(); + this.abortEvent.Reset(); + + ThreadPool.QueueUserWorkItem(new WaitCallback(DoCheckThreadProc)); + + WaitHandleArray events = new WaitHandleArray(2); + events[0] = this.checkingEvent; + events[1] = this.abortEvent; + int waitResult = events.WaitAny(); + + if (waitResult == 0 && manifest != null && latestVersionIndex != -1) + { + StateMachine.QueueInput(PrivateInput.GoToUpdateAvailable); + } + else if (waitResult == 1) + { + StateMachine.QueueInput(PrivateInput.GoToAborted); + } + else if (this.exception != null) + { + StateMachine.QueueInput(PrivateInput.GoToError); + } + else + { + StateMachine.QueueInput(PrivateInput.GoToDone); + } + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(PrivateInput.GoToUpdateAvailable)) + { + newState = new UpdateAvailableState(this.manifest.VersionInfos[this.latestVersionIndex]); + } + else if (input.Equals(PrivateInput.GoToError)) + { + string errorMessage; + + if (this.exception is WebException) + { + errorMessage = Utility.WebExceptionToErrorMessage((WebException)this.exception); + } + else + { + errorMessage = PdnResources.GetString("Updates.CheckingState.GenericError"); + } + + newState = new ErrorState(this.exception, errorMessage); + } + else if (input.Equals(PrivateInput.GoToDone)) + { + newState = new DoneState(); + } + else if (input.Equals(PrivateInput.GoToAborted)) + { + newState = new AbortedState(); + } + else + { + throw new ArgumentException(); + } + } + + public CheckingState() + : base(false, false, MarqueeStyle.Marquee) + { + } + } +} diff --git a/src/Updates/DoneState.cs b/src/Updates/DoneState.cs new file mode 100644 index 0000000..76dd64e --- /dev/null +++ b/src/Updates/DoneState.cs @@ -0,0 +1,33 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class DoneState + : UpdatesState + { + public override void OnEnteredState() + { + base.OnEnteredState(); + } + + public override void ProcessInput(object input, out State newState) + { + throw new Exception("The method or operation is not implemented."); + } + + public DoneState() + : base(true, false, MarqueeStyle.None) + { + } + } +} diff --git a/src/Updates/DownloadingState.cs b/src/Updates/DownloadingState.cs new file mode 100644 index 0000000..a074e2d --- /dev/null +++ b/src/Updates/DownloadingState.cs @@ -0,0 +1,168 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using System; +using System.IO; +using System.Net; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class DownloadingState + : UpdatesState, + INewVersionInfo + { + private PdnVersionInfo downloadMe; + private SiphonStream abortMeStream; + private Exception exception = null; + private string zipTempName; + + public PdnVersionInfo NewVersionInfo + { + get + { + return this.downloadMe; + } + } + + public override bool CanAbort + { + get + { + return true; + } + } + + protected override void OnAbort() + { + SiphonStream abortMe = this.abortMeStream; + + if (abortMe != null) + { + abortMe.Abort(new Exception()); + } + } + + public override void OnEnteredState() + { + this.zipTempName = Path.GetTempFileName() + ".zip"; + + try + { + bool getFull; + + if (SystemLayer.OS.IsDotNetVersionInstalled( + downloadMe.NetFxMajorVersion, + downloadMe.NetFxMinorVersion, + downloadMe.NetFxServicePack, + true)) + { + getFull = false; + } + else + { + getFull = true; + } + + OnProgress(0.0); + + FileStream zipFileWrite = new FileStream(zipTempName, FileMode.Create, FileAccess.Write, FileShare.Read); + + try + { + // we need to wrap the zipFileWrite in a SiphonStream so that we can + // Abort() it externally + SiphonStream monitorStream = new SiphonStream(zipFileWrite); + this.abortMeStream = monitorStream; + + ProgressEventHandler progressCallback = + delegate(object sender, ProgressEventArgs e) + { + OnProgress(e.Percent); + }; + + string url; + + url = downloadMe.ChooseDownloadUrl(getFull); + SystemLayer.Tracing.Ping("Chosen mirror url: " + url); + + Utility.DownloadFile(new Uri(url), monitorStream, progressCallback); + monitorStream.Flush(); + + this.abortMeStream = null; + monitorStream = null; + } + + finally + { + if (zipFileWrite != null) + { + zipFileWrite.Close(); + zipFileWrite = null; + } + } + + StateMachine.QueueInput(PrivateInput.GoToExtracting); + } + + catch (Exception ex) + { + this.exception = ex; + + if (this.AbortRequested) + { + StateMachine.QueueInput(PrivateInput.GoToAborted); + } + else + { + this.exception = ex; + StateMachine.QueueInput(PrivateInput.GoToError); + } + } + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(PrivateInput.GoToExtracting)) + { + newState = new ExtractingState(this.zipTempName, this.downloadMe); + } + else if (input.Equals(PrivateInput.GoToError)) + { + string errorMessage; + + if (this.exception is WebException) + { + errorMessage = Utility.WebExceptionToErrorMessage((WebException)this.exception); + } + else + { + errorMessage = PdnResources.GetString("Updates.DownloadingState.GenericError"); + } + + newState = new ErrorState(this.exception, errorMessage); + } + else if (input.Equals(PrivateInput.GoToAborted)) + { + newState = new AbortedState(); + } + else + { + throw new ArgumentException(); + } + } + + public DownloadingState(PdnVersionInfo downloadMe) + : base(false, false, MarqueeStyle.Smooth) + { + this.downloadMe = downloadMe; + } + } +} diff --git a/src/Updates/ErrorState.cs b/src/Updates/ErrorState.cs new file mode 100644 index 0000000..5bbe8d9 --- /dev/null +++ b/src/Updates/ErrorState.cs @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class ErrorState + : UpdatesState + { + public override string InfoText + { + get + { + string infoTextFormat = PdnResources.GetString("UpdatesDialog.InfoText.Text.ErrorState.Format"); + string infoText = string.Format(infoTextFormat, this.errorMessage); + return infoText; + } + } + + private Exception exception; + public Exception Exception + { + get + { + return this.exception; + } + } + + private string errorMessage; + public string ErrorMessage + { + get + { + return this.errorMessage; + } + } + + public override void OnEnteredState() + { + base.OnEnteredState(); + } + + public override void ProcessInput(object input, out State newState) + { + throw new Exception("The method or operation is not implemented."); + } + + public ErrorState(Exception exception, string errorMessage) + : base(true, false, MarqueeStyle.None) + { + this.exception = exception; + this.errorMessage = errorMessage; + } + } +} diff --git a/src/Updates/ExtractingState.cs b/src/Updates/ExtractingState.cs new file mode 100644 index 0000000..2d2ce86 --- /dev/null +++ b/src/Updates/ExtractingState.cs @@ -0,0 +1,221 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.SystemLayer; +using System; +using System.Globalization; +using System.IO; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class ExtractingState + : UpdatesState, + INewVersionInfo + { + private Exception exception; + private string extractMe; + private string installerPath; + private PdnVersionInfo newVersionInfo; + private SiphonStream abortMeStream = null; + + public PdnVersionInfo NewVersionInfo + { + get + { + return this.newVersionInfo; + } + } + + public override bool CanAbort + { + get + { + return true; + } + } + + protected override void OnAbort() + { + SiphonStream abortMe = this.abortMeStream; + + if (abortMe != null) + { + abortMe.Abort(new Exception()); + } + + base.OnAbort(); + } + + public override void OnEnteredState() + { + try + { + OnEnteredStateImpl(); + } + + catch (Exception ex) + { + this.exception = ex; + StateMachine.QueueInput(PrivateInput.GoToError); + } + } + + public void OnEnteredStateImpl() + { + FileStream zipFileRead = new FileStream(this.extractMe, FileMode.Open, FileAccess.Read, FileShare.Read); + FileStream exeFileWrite = null; + + try + { + ICSharpCode.SharpZipLib.Zip.ZipInputStream zipStream = + new ICSharpCode.SharpZipLib.Zip.ZipInputStream(zipFileRead); + + // Search for the first .msi file in the exe, and extract it + ICSharpCode.SharpZipLib.Zip.ZipEntry zipEntry; + bool foundExe = false; + + while (true) + { + zipEntry = zipStream.GetNextEntry(); + + if (zipEntry == null) + { + break; + } + + if (!zipEntry.IsDirectory && + string.Compare(".exe", Path.GetExtension(zipEntry.Name), true, CultureInfo.InvariantCulture) == 0) + { + foundExe = true; + break; + } + } + + if (!foundExe) + { + this.exception = new FileNotFoundException(); + StateMachine.QueueInput(PrivateInput.GoToError); + } + else + { + int maxBytes = (int)zipEntry.Size; + int bytesSoFar = 0; + + this.installerPath = Path.Combine(Path.GetDirectoryName(this.extractMe), zipEntry.Name); + exeFileWrite = new FileStream(this.installerPath, FileMode.Create, FileAccess.Write, FileShare.Read); + SiphonStream siphonStream2 = new SiphonStream(exeFileWrite, 4096); + + this.abortMeStream = siphonStream2; + + IOEventHandler ioFinishedDelegate = + delegate(object sender, IOEventArgs e) + { + bytesSoFar += e.Count; + double percent = 100.0 * ((double)bytesSoFar / (double)maxBytes); + OnProgress(percent); + }; + + OnProgress(0.0); + + if (maxBytes > 0) + { + siphonStream2.IOFinished += ioFinishedDelegate; + } + + Utility.CopyStream(zipStream, siphonStream2); + + if (maxBytes > 0) + { + siphonStream2.IOFinished -= ioFinishedDelegate; + } + + this.abortMeStream = null; + siphonStream2 = null; + exeFileWrite.Close(); + exeFileWrite = null; + zipStream.Close(); + zipStream = null; + + StateMachine.QueueInput(PrivateInput.GoToReadyToInstall); + } + } + + catch (Exception ex) + { + if (this.AbortRequested) + { + StateMachine.QueueInput(PrivateInput.GoToAborted); + } + else + { + this.exception = ex; + StateMachine.QueueInput(PrivateInput.GoToError); + } + } + + finally + { + if (exeFileWrite != null) + { + exeFileWrite.Close(); + exeFileWrite = null; + } + + if (zipFileRead != null) + { + zipFileRead.Close(); + zipFileRead = null; + } + + if (this.exception != null || this.AbortRequested) + { + if (this.installerPath != null) + { + bool result = FileSystem.TryDeleteFile(this.installerPath); + } + } + + if (this.extractMe != null) + { + bool result = FileSystem.TryDeleteFile(this.extractMe); + } + } + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(PrivateInput.GoToReadyToInstall)) + { + newState = new ReadyToInstallState(this.installerPath, this.newVersionInfo); + } + else if (input.Equals(PrivateInput.GoToError)) + { + string errorMessage = PdnResources.GetString("Updates.ExtractingState.GenericError"); + newState = new ErrorState(this.exception, errorMessage); + } + else if (input.Equals(PrivateInput.GoToAborted)) + { + newState = new AbortedState(); + } + else + { + throw new ArgumentException(); + } + } + + public ExtractingState(string extractMe, PdnVersionInfo newVersionInfo) + : base(false, false, MarqueeStyle.Smooth) + { + this.extractMe = extractMe; + this.newVersionInfo = newVersionInfo; + } + } +} diff --git a/src/Updates/INewVersionInfo.cs b/src/Updates/INewVersionInfo.cs new file mode 100644 index 0000000..6fca04b --- /dev/null +++ b/src/Updates/INewVersionInfo.cs @@ -0,0 +1,21 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Updates +{ + internal interface INewVersionInfo + { + PdnVersionInfo NewVersionInfo + { + get; + } + } +} diff --git a/src/Updates/InstallingState.cs b/src/Updates/InstallingState.cs new file mode 100644 index 0000000..ef49da8 --- /dev/null +++ b/src/Updates/InstallingState.cs @@ -0,0 +1,127 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class InstallingState + : UpdatesState + { + private Exception exception; + private string installerPath; + private bool finishing = false; + private bool haveFinished = false; + + public override void OnEnteredState() + { + try + { + OnEnteredStateImpl(); + } + + catch (Exception ex) + { + this.exception = ex; + StateMachine.QueueInput(PrivateInput.GoToError); + } + } + + private void OnEnteredStateImpl() + { + string installerExt = Path.GetExtension(this.installerPath); + bool extIsExe = (string.Compare(".exe", Path.GetExtension(installerExt), true) == 0); + + if (!extIsExe) + { + throw new InvalidOperationException("installerPath does not end in .exe: " + installerPath); + } + + // Save the %TEMP% filename to the settings repository so that it will + // be deleted the next time Paint.NET run + string fileName = Path.GetFileName(installerPath); + } + + public void Finish(AppWorkspace appWorkspace) + { + // Assumes we are running in the main UI thread + + if (this.finishing) + { + return; + } + + try + { + if (this.haveFinished) + { + throw new ApplicationException("already called Finish()"); + } + + this.finishing = true; + this.haveFinished = true; + + // Verify the update's signature + bool verified = Security.VerifySignedFile(StateMachine.UIContext, this.installerPath, true, false); + CloseAllWorkspacesAction cawa = new CloseAllWorkspacesAction(); + appWorkspace.PerformAction(cawa); + + if (verified && !cawa.Cancelled) + { + // we're in the clear, launch the update! + Settings.CurrentUser.SetString(SettingNames.UpdateMsiFileName, this.installerPath); + + if (0 == string.Compare(Path.GetExtension(this.installerPath), ".exe", true)) + { + const string arguments = "/skipConfig"; + Shell.Execute(appWorkspace, this.installerPath, arguments, ExecutePrivilege.RequireAdmin, ExecuteWaitType.RelaunchPdnOnExit); + Startup.CloseApplication(); + } + else + { + } + } + else + { + bool result = FileSystem.TryDeleteFile(this.installerPath); + } + } + + finally + { + this.finishing = false; + } + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(UpdatesAction.Continue)) + { + newState = new DoneState(); + } + else + { + throw new ArgumentException(); + } + } + + public InstallingState(string installerPath) + : base(false, false, MarqueeStyle.None) + { + this.installerPath = installerPath; + } + } +} diff --git a/src/Updates/MarqueeStyle.cs b/src/Updates/MarqueeStyle.cs new file mode 100644 index 0000000..7cc718b --- /dev/null +++ b/src/Updates/MarqueeStyle.cs @@ -0,0 +1,20 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Updates +{ + internal enum MarqueeStyle + { + None, + Smooth, + Marquee + } +} \ No newline at end of file diff --git a/src/Updates/PrivateInput.cs b/src/Updates/PrivateInput.cs new file mode 100644 index 0000000..be73e6f --- /dev/null +++ b/src/Updates/PrivateInput.cs @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Updates +{ + internal enum PrivateInput + { + GoToReadyToCheck, + GoToChecking, + GoToUpdateAvailable, + GoToDownloading, + GoToExtracting, + GoToReadyToInstall, + GoToInstalling, + GoToDone, + GoToError, + GoToAborted + } +} diff --git a/src/Updates/ReadyToCheckState.cs b/src/Updates/ReadyToCheckState.cs new file mode 100644 index 0000000..2cefacd --- /dev/null +++ b/src/Updates/ReadyToCheckState.cs @@ -0,0 +1,39 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class ReadyToCheckState + : UpdatesState + { + public override void OnEnteredState() + { + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(UpdatesAction.Continue)) + { + newState = new CheckingState(); + } + else + { + throw new ArgumentException(); + } + } + + public ReadyToCheckState() + : base(false, true, MarqueeStyle.None) + { + } + } +} diff --git a/src/Updates/ReadyToInstallState.cs b/src/Updates/ReadyToInstallState.cs new file mode 100644 index 0000000..8dfe969 --- /dev/null +++ b/src/Updates/ReadyToInstallState.cs @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class ReadyToInstallState + : UpdatesState, + INewVersionInfo + { + private PdnVersionInfo newVersionInfo; + private string installerPath; + + public PdnVersionInfo NewVersionInfo + { + get + { + return this.newVersionInfo; + } + } + + public override void OnEnteredState() + { + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(UpdatesAction.Continue)) + { + newState = new InstallingState(this.installerPath); + } + else if (input.Equals(UpdatesAction.Cancel)) + { + newState = new DoneState(); + } + else + { + throw new ArgumentException(); + } + } + + public ReadyToInstallState(string installerPath, PdnVersionInfo newVersionInfo) + : base(false, true, MarqueeStyle.None) + { + this.installerPath = installerPath; + this.newVersionInfo = newVersionInfo; + } + } +} diff --git a/src/Updates/StartupState.cs b/src/Updates/StartupState.cs new file mode 100644 index 0000000..fa82c1e --- /dev/null +++ b/src/Updates/StartupState.cs @@ -0,0 +1,215 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class StartupState + : UpdatesState + { + // Beta and alpha builds should check every day + // Final builds should check every 5 days + public static int UpdateCheckIntervalDays + { + get + { + if (PdnInfo.IsFinalBuild) + { + return 5; + } + else + { + return 1; + } + } + } + + // If the build is final and less than 1 week old, then do NOT auto check for updates, no matter what. + // This may help alleviate any release-day flooding + // Pre-release builds have no such minimum time before checking. + public static int MinBuildAgeForUpdateChecking + { + get + { + if (PdnInfo.IsFinalBuild) + { + return 7; + } + else + { + return 0; + } + } + } + + // If the build is over 2 years old, then cease checking for updates. + // Either we've stopped putting out new builds, or the user doesn't want to update, + // or the user hardly ever uses the app anyway. + // Check Now... will still continue to function. + public const int MaxBuildAgeForUpdateChecking = 2 * 365; + + private static void DeleteUpdateMsi() + { + // If we just installed an update, then delete it! Save some hard drive space. + string msiDeleteMeFull = Settings.CurrentUser.GetString(SettingNames.UpdateMsiFileName, null); + string msiDeleteMe = Path.GetFileName(msiDeleteMeFull); // make sure someone can't put "..\..\..\..\windows\system32\cmd.exe" or something + string msiDeleteMeExt = Path.GetExtension(msiDeleteMe); + + string setupTempDir = Environment.ExpandEnvironmentVariables(@"%TEMP%\PdnSetup"); + + // Delete the update monitor exe if possible + // Delete the update monitor exe if possible + foreach (var fileName in new string[] { "UpdateMonitor.exe", "UpdateMonitor.exe.config" }) + { + try + { + FileSystem.TryDeleteFile(setupTempDir, fileName); + } + + catch (Exception) + { + // discard any error + } + } + + if (msiDeleteMe != null && + (string.Compare(".msi", msiDeleteMeExt, true, CultureInfo.InvariantCulture) == 0 || + string.Compare(".exe", msiDeleteMeExt, true, CultureInfo.InvariantCulture) == 0)) + { + string tempDir = Environment.ExpandEnvironmentVariables("%TEMP%"); + string msiPath = Path.Combine(tempDir, msiDeleteMe); + int retryCount = 3; + + while (retryCount > 0) + { + if (FileSystem.TryDeleteFile(msiPath)) + { + break; + } + + Thread.Sleep(500); + --retryCount; + } + + Settings.CurrentUser.TryDelete(SettingNames.UpdateMsiFileName); + } + + // Try to remove the dir from the temp folder + if (Directory.Exists(setupTempDir)) + { + FileSystem.TryDeleteDirectory(setupTempDir); + } + } + + /// + /// Determines if it is time to check for updates. + /// + /// true if we should check for updates, false if it is not yet time to do so. + /// + /// This method takes in to consideration whether update checking is enabled, and if it + /// has been long enough since the last time we checked for updates. + /// + public static bool ShouldCheckForUpdates() + { + bool shouldCheckForUpdates; + bool autoCheckForUpdates = ("1" == Settings.SystemWide.GetString(SettingNames.AutoCheckForUpdates, "0")); + + TimeSpan minAge = new TimeSpan(MinBuildAgeForUpdateChecking, 0, 0, 0); + TimeSpan maxAge = new TimeSpan(MaxBuildAgeForUpdateChecking, 0, 0, 0); + + TimeSpan buildAge = (DateTime.Now - PdnInfo.BuildTime); + + if (buildAge < minAge || buildAge > maxAge) + { + shouldCheckForUpdates = false; + } + else if (autoCheckForUpdates) + { + try + { + string lastUpdateCheckTimeTicksString = Settings.CurrentUser.GetString(SettingNames.LastUpdateCheckTimeTicks, null); + + if (lastUpdateCheckTimeTicksString == null) + { + shouldCheckForUpdates = true; + } + else + { + long lastUpdateCheckTimeTicks = long.Parse(lastUpdateCheckTimeTicksString); + DateTime lastUpdateCheckTime = new DateTime(lastUpdateCheckTimeTicks); + + TimeSpan timeSinceLastCheck = DateTime.Now - lastUpdateCheckTime; + + shouldCheckForUpdates = (timeSinceLastCheck > new TimeSpan(UpdateCheckIntervalDays, 0, 0, 0)); + } + } + + catch + { + shouldCheckForUpdates = true; + } + } + else + { + shouldCheckForUpdates = false; + } + + return shouldCheckForUpdates; + } + + public static void PingLastUpdateCheckTime() + { + Settings.CurrentUser.SetString(SettingNames.LastUpdateCheckTimeTicks, DateTime.Now.Ticks.ToString()); + } + + public override void OnEnteredState() + { + DeleteUpdateMsi(); + + if ((Security.IsAdministrator || Security.CanElevateToAdministrator) && + PdnInfo.StartupTest == StartupTestType.None && + ShouldCheckForUpdates()) + { + PingLastUpdateCheckTime(); + StateMachine.QueueInput(PrivateInput.GoToChecking); + } + else + { + StateMachine.QueueInput(UpdatesAction.Continue); + } + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(UpdatesAction.Continue)) + { + newState = new ReadyToCheckState(); + } + else if (input.Equals(PrivateInput.GoToChecking)) + { + newState = new CheckingState(); + } + else + { + throw new ArgumentException(); + } + } + + public StartupState() + : base(false, false, MarqueeStyle.Marquee) + { + } + } +} diff --git a/src/Updates/UpdateAvailableState.cs b/src/Updates/UpdateAvailableState.cs new file mode 100644 index 0000000..5a1c0f0 --- /dev/null +++ b/src/Updates/UpdateAvailableState.cs @@ -0,0 +1,55 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class UpdateAvailableState + : UpdatesState, + INewVersionInfo + { + private PdnVersionInfo newVersionInfo; + + public PdnVersionInfo NewVersionInfo + { + get + { + return this.newVersionInfo; + } + } + + public override void OnEnteredState() + { + } + + public override void ProcessInput(object input, out State newState) + { + if (input.Equals(UpdatesAction.Continue)) + { + newState = new DownloadingState(newVersionInfo); + } + else if (input.Equals(UpdatesAction.Cancel)) + { + newState = new DoneState(); + } + else + { + throw new ArgumentException(); + } + } + + public UpdateAvailableState(PdnVersionInfo newVersionInfo) + : base(false, true, MarqueeStyle.None) + { + this.newVersionInfo = newVersionInfo; + } + } +} diff --git a/src/Updates/UpdatesAction.cs b/src/Updates/UpdatesAction.cs new file mode 100644 index 0000000..f925834 --- /dev/null +++ b/src/Updates/UpdatesAction.cs @@ -0,0 +1,19 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet.Updates +{ + internal enum UpdatesAction + { + Continue, + Cancel + } +} diff --git a/src/Updates/UpdatesDialog.cs b/src/Updates/UpdatesDialog.cs new file mode 100644 index 0000000..3716f6e --- /dev/null +++ b/src/Updates/UpdatesDialog.cs @@ -0,0 +1,444 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class UpdatesDialog + : PdnBaseForm + { + private Button closeButton; + private Button optionsButton; + private Button continueButton; + private ProgressBar progressBar; + private Label infoText; + private LinkLabel moreInfoLink; + private Uri moreInfoTarget; + private Label versionNameLabel; + private HeaderLabel headerLabel; + private bool closeOnDoneState = false; + private Label newVersionLabel; + private Label progressLabel; + + private StateMachineExecutor updatesStateMachine; + public StateMachineExecutor UpdatesStateMachine + { + get + { + return this.updatesStateMachine; + } + + set + { + if (this.updatesStateMachine != null) + { + this.updatesStateMachine.StateBegin -= UpdatesStateMachine_StateBegin; + this.updatesStateMachine.StateMachineBegin -= UpdatesStateMachine_StateMachineBegin; + this.updatesStateMachine.StateMachineFinished -= UpdatesStateMachine_StateMachineFinished; + this.updatesStateMachine.StateProgress -= UpdatesStateMachine_StateProgress; + this.updatesStateMachine.StateWaitingForInput -= UpdatesStateMachine_StateWaitingForInput; + } + + this.updatesStateMachine = value; + + if (this.updatesStateMachine != null) + { + this.updatesStateMachine.StateBegin += UpdatesStateMachine_StateBegin; + this.updatesStateMachine.StateMachineBegin += UpdatesStateMachine_StateMachineBegin; + this.updatesStateMachine.StateMachineFinished += UpdatesStateMachine_StateMachineFinished; + this.updatesStateMachine.StateProgress += UpdatesStateMachine_StateProgress; + this.updatesStateMachine.StateWaitingForInput += UpdatesStateMachine_StateWaitingForInput; + } + + UpdateDynamicUI(); + } + } + + private void UpdatesStateMachine_StateWaitingForInput(object sender, EventArgs e) + { + this.continueButton.Enabled = true; + UpdateDynamicUI(); + } + + private void UpdatesStateMachine_StateProgress(object sender, ProgressEventArgs e) + { + int newValue = Utility.Clamp((int)e.Percent, this.progressBar.Minimum, this.progressBar.Maximum); + this.progressBar.Value = newValue; + string progressLabelFormat = PdnResources.GetString("UpdatesDialog.ProgressLabel.Text.Format"); + string progressLabelText = string.Format(progressLabelFormat, newValue.ToString()); + this.progressLabel.Text = progressLabelText; + + UpdateDynamicUI(); + } + + private void UpdatesStateMachine_StateMachineFinished(object sender, EventArgs e) + { + UpdateDynamicUI(); + } + + private void UpdatesStateMachine_StateMachineBegin(object sender, EventArgs e) + { + UpdateDynamicUI(); + } + + private void UpdatesStateMachine_StateBegin(object sender, EventArgs e) + { + this.progressBar.Value = 0; + UpdateDynamicUI(); + + if (e.Data is Updates.DoneState && this.closeOnDoneState) + { + this.DialogResult = DialogResult.OK; + Close(); + } + else if (e.Data is Updates.ReadyToCheckState) + { + this.updatesStateMachine.ProcessInput(UpdatesAction.Continue); + } + else if (e.Data is Updates.ReadyToInstallState) + { + ClientSize = new Size(ClientSize.Width, this.continueButton.Bottom + UI.ScaleHeight(7)); + + this.closeButton.Enabled = false; + this.closeButton.Visible = false; + this.optionsButton.Enabled = false; + this.optionsButton.Visible = false; + this.headerLabel.Visible = false; + + this.continueButton.Location = new Point( + ClientSize.Width - this.continueButton.Width - UI.ScaleWidth(7), + ClientSize.Height - this.continueButton.Height - UI.ScaleHeight(8)); + } + else if (e.Data is Updates.AbortedState) + { + this.DialogResult = DialogResult.Abort; + Close(); + } + } + + private void UpdateDynamicUI() + { + this.Text = PdnResources.GetString("UpdatesDialog.Text"); + string closeButtonText = PdnResources.GetString("UpdatesDialog.CloseButton.Text"); + this.optionsButton.Text = PdnResources.GetString("UpdatesDialog.OptionsButton.Text"); + this.moreInfoLink.Text = PdnResources.GetString("UpdatesDialog.MoreInfoLink.Text"); + this.newVersionLabel.Text = PdnResources.GetString("UpdatesDialog.NewVersionLabel.Text"); + + if (this.updatesStateMachine == null || this.updatesStateMachine.CurrentState == null) + { + this.infoText.Text = string.Empty; + this.continueButton.Text = string.Empty; + this.continueButton.Enabled = false; + this.continueButton.Visible = false; + this.moreInfoLink.Visible = false; + this.moreInfoLink.Enabled = false; + this.versionNameLabel.Visible = false; + this.versionNameLabel.Enabled = false; + } + else + { + UpdatesState currentState = (UpdatesState)this.updatesStateMachine.CurrentState; + + this.infoText.Text = currentState.InfoText; + this.continueButton.Text = currentState.ContinueButtonText; + this.continueButton.Visible = currentState.ContinueButtonVisible; + this.continueButton.Enabled = currentState.ContinueButtonVisible; + this.progressBar.Style = (currentState.MarqueeStyle == MarqueeStyle.Marquee) ? ProgressBarStyle.Marquee : ProgressBarStyle.Continuous; + this.progressBar.Visible = (currentState.MarqueeStyle != MarqueeStyle.None); + this.progressLabel.Visible = this.progressBar.Visible; + + if (this.continueButton.Enabled || currentState is ErrorState || currentState is DoneState) + { + closeButtonText = PdnResources.GetString("UpdatesDialog.CloseButton.Text"); + } + else + { + closeButtonText = PdnResources.GetString("Form.CancelButton.Text"); + } + + if (currentState is ErrorState) + { + Size size = new Size(this.infoText.Width, 1); + Size preferredSize = this.infoText.GetPreferredSize(size); + this.infoText.Size = preferredSize; + } + + INewVersionInfo asInvi = currentState as INewVersionInfo; + + if (asInvi != null) + { + this.versionNameLabel.Text = asInvi.NewVersionInfo.FriendlyName; + this.versionNameLabel.Visible = true; + this.versionNameLabel.Enabled = true; + this.moreInfoTarget = new Uri(asInvi.NewVersionInfo.InfoUrl); + this.moreInfoLink.Visible = true; + this.moreInfoLink.Enabled = true; + + this.newVersionLabel.Visible = true; + this.newVersionLabel.Font = new Font(this.newVersionLabel.Font, this.newVersionLabel.Font.Style | FontStyle.Bold); + this.versionNameLabel.Left = this.newVersionLabel.Right; + this.moreInfoLink.Left = this.versionNameLabel.Left; + } + else + { + this.newVersionLabel.Visible = false; + this.versionNameLabel.Visible = false; + this.versionNameLabel.Enabled = false; + this.moreInfoLink.Visible = false; + this.moreInfoLink.Enabled = false; + } + } + + this.closeButton.Text = closeButtonText; + + Update(); + } + + public UpdatesDialog() + { + InitializeComponent(); + + Image iconImage = PdnResources.GetImageResource("Icons.MenuHelpCheckForUpdatesIcon.png").Reference; + this.Icon = Utility.ImageToIcon(iconImage, Utility.TransparentKey); + + if (Security.IsAdministrator) + { + this.optionsButton.Enabled = true; + } + else if (Security.CanElevateToAdministrator) + { + this.optionsButton.Enabled = true; + } + else + { + this.optionsButton.Enabled = false; + } + + this.optionsButton.FlatStyle = FlatStyle.System; + SystemLayer.UI.EnableShield(this.optionsButton, true); + } + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.closeButton = new System.Windows.Forms.Button(); + this.optionsButton = new System.Windows.Forms.Button(); + this.continueButton = new System.Windows.Forms.Button(); + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.infoText = new System.Windows.Forms.Label(); + this.moreInfoLink = new System.Windows.Forms.LinkLabel(); + this.versionNameLabel = new System.Windows.Forms.Label(); + this.headerLabel = new PaintDotNet.HeaderLabel(); + this.newVersionLabel = new System.Windows.Forms.Label(); + this.progressLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // closeButton + // + this.closeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.closeButton.AutoSize = true; + this.closeButton.Location = new System.Drawing.Point(262, 143); + this.closeButton.Name = "closeButton"; + this.closeButton.Size = new System.Drawing.Size(75, 23); + this.closeButton.TabIndex = 0; + this.closeButton.Text = "_close"; + this.closeButton.UseVisualStyleBackColor = true; + this.closeButton.FlatStyle = FlatStyle.System; + this.closeButton.Click += new System.EventHandler(this.CloseButton_Click); + // + // optionsButton + // + this.optionsButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.optionsButton.AutoSize = true; + this.optionsButton.Click += new EventHandler(OptionsButton_Click); + this.optionsButton.Location = new System.Drawing.Point(166, 143); + this.optionsButton.Name = "optionsButton"; + this.optionsButton.Size = new System.Drawing.Size(91, 23); + this.optionsButton.TabIndex = 1; + this.optionsButton.Text = "_options..."; + this.optionsButton.FlatStyle = FlatStyle.System; + this.optionsButton.UseVisualStyleBackColor = true; + // + // continueButton + // + this.continueButton.AutoSize = true; + this.continueButton.Location = new System.Drawing.Point(7, 100); + this.continueButton.Name = "continueButton"; + this.continueButton.Size = new System.Drawing.Size(75, 23); + this.continueButton.TabIndex = 3; + this.continueButton.Text = "_continue"; + this.continueButton.UseVisualStyleBackColor = true; + this.continueButton.FlatStyle = FlatStyle.System; + this.continueButton.Click += new System.EventHandler(this.ContinueButton_Click); + // + // progressBar + // + this.progressBar.Location = new System.Drawing.Point(9, 103); + this.progressBar.MarqueeAnimationSpeed = 40; + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(294, 18); + this.progressBar.TabIndex = 4; + // + // infoText + // + this.infoText.Location = new System.Drawing.Point(7, 7); + this.infoText.Name = "infoText"; + this.infoText.Size = new System.Drawing.Size(329, 45); + this.infoText.TabIndex = 2; + this.infoText.Text = ".blahblahblah"; + // + // moreInfoLink + // + this.moreInfoLink.AutoSize = true; + this.moreInfoLink.Location = new System.Drawing.Point(7, 75); + this.moreInfoLink.Name = "moreInfoLink"; + this.moreInfoLink.Size = new System.Drawing.Size(66, 13); + this.moreInfoLink.TabIndex = 5; + this.moreInfoLink.TabStop = true; + this.moreInfoLink.Text = "_more Info..."; + this.moreInfoLink.Click += new System.EventHandler(this.MoreInfoLink_Click); + // + // versionNameLabel + // + this.versionNameLabel.AutoSize = true; + this.versionNameLabel.Location = new System.Drawing.Point(88, 57); + this.versionNameLabel.Name = "versionNameLabel"; + this.versionNameLabel.Size = new System.Drawing.Size(84, 13); + this.versionNameLabel.TabIndex = 6; + this.versionNameLabel.Text = ".paint.net vX.YZ"; + // + // headerLabel + // + this.headerLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.headerLabel.Location = new System.Drawing.Point(9, 126); + this.headerLabel.Name = "headerLabel"; + this.headerLabel.RightMargin = 0; + this.headerLabel.Size = new System.Drawing.Size(327, 15); + this.headerLabel.TabIndex = 0; + this.headerLabel.TabStop = false; + // + // newVersionLabel + // + this.newVersionLabel.AutoSize = true; + this.newVersionLabel.Location = new System.Drawing.Point(7, 57); + this.newVersionLabel.Name = "newVersionLabel"; + this.newVersionLabel.Size = new System.Drawing.Size(70, 13); + this.newVersionLabel.TabIndex = 7; + this.newVersionLabel.Text = ".new version:"; + // + // progressLabel + // + this.progressLabel.AutoSize = true; + this.progressLabel.Location = new System.Drawing.Point(310, 105); + this.progressLabel.Name = "progressLabel"; + this.progressLabel.Size = new System.Drawing.Size(0, 13); + this.progressLabel.TabIndex = 8; + this.progressLabel.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // UpdatesDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.closeButton; + this.ClientSize = new System.Drawing.Size(343, 172); + this.Controls.Add(this.progressLabel); + this.Controls.Add(this.newVersionLabel); + this.Controls.Add(this.headerLabel); + this.Controls.Add(this.versionNameLabel); + this.Controls.Add(this.moreInfoLink); + this.Controls.Add(this.continueButton); + this.Controls.Add(this.infoText); + this.Controls.Add(this.optionsButton); + this.Controls.Add(this.closeButton); + this.Controls.Add(this.progressBar); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UpdatesDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private void OptionsButton_Click(object sender, EventArgs e) + { + UpdatesOptionsDialog.ShowUpdateOptionsDialog(this, true); + } + + private void MoreInfoLink_Click(object sender, EventArgs e) + { + PdnInfo.OpenUrl(this, this.moreInfoTarget.ToString()); + } + + private void ContinueButton_Click(object sender, EventArgs e) + { + if (this.updatesStateMachine.CurrentState is Updates.ReadyToInstallState) + { + // whomever showed the dialog is responsible for "continuing" the state machine at this point + // in order to properly handle closing the application. We will not actually pass it the + // UpdatesAction.Continue input. + this.DialogResult = DialogResult.Yes; + Hide(); + Close(); + } + else + { + this.updatesStateMachine.ProcessInput(Updates.UpdatesAction.Continue); + this.continueButton.Enabled = false; + } + } + + private void CloseButton_Click(object sender, EventArgs e) + { + if (this.updatesStateMachine != null) + { + this.updatesStateMachine.Abort(); + this.updatesStateMachine = null; + this.closeButton.Enabled = false; + } + + Close(); + } + } +} diff --git a/src/Updates/UpdatesOptionsDialog.cs b/src/Updates/UpdatesOptionsDialog.cs new file mode 100644 index 0000000..2a4540b --- /dev/null +++ b/src/Updates/UpdatesOptionsDialog.cs @@ -0,0 +1,261 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class UpdatesOptionsDialog + : PdnBaseForm + { + public const string CommandLineParameter = "/updateOptions"; + + private Button saveButton; + private CheckBox autoCheckBox; + private CheckBox betaCheckBox; + private Button cancelButton; + private HeaderLabel headerLabel1; + private Label allUsersNoticeLabel; + + public static void ShowUpdateOptionsDialog(IWin32Window owner) + { + ShowUpdateOptionsDialog(owner, false); + } + + public static void ShowUpdateOptionsDialog(IWin32Window owner, bool allowNewInstance) + { + if (Security.IsAdministrator) + { + UpdatesOptionsDialog dialog = new UpdatesOptionsDialog(); + + if (owner == null) + { + dialog.ShowInTaskbar = true; + } + + dialog.ShowDialog(owner); + } + else if (Security.CanElevateToAdministrator && allowNewInstance) + { + Startup.StartNewInstance(owner, true, new string[1] { CommandLineParameter }); + } + else + { + Utility.ShowNonAdminErrorBox(owner); + } + } + + private UpdatesOptionsDialog() + { + InitializeComponent(); + } + + protected override void OnLoad(EventArgs e) + { + LoadSettings(); + LoadResources(); + base.OnLoad(e); + } + + public override void LoadResources() + { + this.Text = PdnResources.GetString("UpdatesOptionsDialog.Text"); + + Image iconImage = PdnResources.GetImageResource("Icons.SettingsIcon.png").Reference; + this.Icon = Utility.ImageToIcon(iconImage, Utility.TransparentKey, false); + + this.saveButton.Text = PdnResources.GetString("UpdatesOptionsDialog.SaveButton.Text"); + this.autoCheckBox.Text = PdnResources.GetString("UpdatesOptionsDialog.AutoCheckBox.Text"); + this.betaCheckBox.Text = PdnResources.GetString("UpdatesOptionsDialog.BetaCheckBox.Text"); + this.allUsersNoticeLabel.Text = PdnResources.GetString("UpdatesOptionsDialog.AllUsersNoticeLabel.Text"); + this.cancelButton.Text = PdnResources.GetString("Form.CancelButton.Text"); + + base.LoadResources(); + } + + private void LoadSettings() + { + string autoCheckString = Settings.SystemWide.GetString(SettingNames.AutoCheckForUpdates, "0"); + bool autoCheck = (autoCheckString == "1"); + this.autoCheckBox.Checked = autoCheck; + + string betaCheckString = Settings.SystemWide.GetString(SettingNames.AlsoCheckForBetas, "0"); + bool betaCheck = (betaCheckString == "1"); + this.betaCheckBox.Checked = betaCheck; + this.betaCheckBox.Enabled = this.autoCheckBox.Checked; + } + + private void SaveSettings() + { + string autoCheckString = autoCheckBox.Checked ? "1" : "0"; + Settings.SystemWide.SetString(SettingNames.AutoCheckForUpdates, autoCheckString); + + string betaCheckString = betaCheckBox.Checked ? "1" : "0"; + Settings.SystemWide.SetString(SettingNames.AlsoCheckForBetas, betaCheckString); + } + + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.saveButton = new System.Windows.Forms.Button(); + this.autoCheckBox = new System.Windows.Forms.CheckBox(); + this.betaCheckBox = new System.Windows.Forms.CheckBox(); + this.allUsersNoticeLabel = new System.Windows.Forms.Label(); + this.cancelButton = new System.Windows.Forms.Button(); + this.headerLabel1 = new PaintDotNet.HeaderLabel(); + this.SuspendLayout(); + // + // saveButton + // + this.saveButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.saveButton.Location = new System.Drawing.Point(236, 95); + this.saveButton.Name = "saveButton"; + this.saveButton.Size = new System.Drawing.Size(75, 23); + this.saveButton.TabIndex = 0; + this.saveButton.Text = ".save"; + this.saveButton.UseVisualStyleBackColor = true; + this.saveButton.FlatStyle = FlatStyle.System; + this.saveButton.Click += new System.EventHandler(this.SaveButton_Click); + // + // autoCheckBox + // + this.autoCheckBox.AutoSize = true; + this.autoCheckBox.Location = new System.Drawing.Point(8, 9); + this.autoCheckBox.Name = "autoCheckBox"; + this.autoCheckBox.Size = new System.Drawing.Size(80, 17); + this.autoCheckBox.TabIndex = 1; + this.autoCheckBox.Text = "checkBox1"; + this.autoCheckBox.UseVisualStyleBackColor = true; + this.autoCheckBox.FlatStyle = FlatStyle.System; + this.autoCheckBox.CheckedChanged += new System.EventHandler(this.AutoCheckBox_CheckedChanged); + // + // betaCheckBox + // + this.betaCheckBox.AutoSize = true; + this.betaCheckBox.Location = new System.Drawing.Point(26, 33); + this.betaCheckBox.Name = "betaCheckBox"; + this.betaCheckBox.Size = new System.Drawing.Size(80, 17); + this.betaCheckBox.TabIndex = 2; + this.betaCheckBox.Text = "checkBox1"; + this.betaCheckBox.FlatStyle = FlatStyle.System; + this.betaCheckBox.UseVisualStyleBackColor = true; + // + // allUsersNoticeLabel + // + this.allUsersNoticeLabel.AutoSize = true; + this.allUsersNoticeLabel.Location = new System.Drawing.Point(7, 63); + this.allUsersNoticeLabel.Name = "allUsersNoticeLabel"; + this.allUsersNoticeLabel.Size = new System.Drawing.Size(78, 13); + this.allUsersNoticeLabel.TabIndex = 4; + this.allUsersNoticeLabel.Text = ".allUsersNotice"; + // + // cancelButton + // + this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(316, 95); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 5; + this.cancelButton.Text = ".cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.FlatStyle = FlatStyle.System; + this.cancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // headerLabel1 + // + this.headerLabel1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.headerLabel1.Location = new System.Drawing.Point(7, 80); + this.headerLabel1.Name = "headerLabel1"; + this.headerLabel1.RightMargin = 0; + this.headerLabel1.Size = new System.Drawing.Size(384, 14); + this.headerLabel1.TabIndex = 6; + this.headerLabel1.TabStop = false; + // + // UpdatesOptionsDialog + // + this.AcceptButton = this.saveButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.cancelButton; + this.ClientSize = new System.Drawing.Size(398, 125); + this.Controls.Add(this.headerLabel1); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.betaCheckBox); + this.Controls.Add(this.autoCheckBox); + this.Controls.Add(this.saveButton); + this.Controls.Add(this.allUsersNoticeLabel); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UpdatesOptionsDialog"; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "UpdatesOptionsDialog"; + this.Controls.SetChildIndex(this.allUsersNoticeLabel, 0); + this.Controls.SetChildIndex(this.saveButton, 0); + this.Controls.SetChildIndex(this.autoCheckBox, 0); + this.Controls.SetChildIndex(this.betaCheckBox, 0); + this.Controls.SetChildIndex(this.cancelButton, 0); + this.Controls.SetChildIndex(this.headerLabel1, 0); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private void AutoCheckBox_CheckedChanged(object sender, EventArgs e) + { + this.betaCheckBox.Enabled = this.autoCheckBox.Checked; + } + + private void SaveButton_Click(object sender, EventArgs e) + { + SaveSettings(); + DialogResult = DialogResult.OK; + Close(); + } + + private void CancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + } +} \ No newline at end of file diff --git a/src/Updates/UpdatesState.cs b/src/Updates/UpdatesState.cs new file mode 100644 index 0000000..d862f24 --- /dev/null +++ b/src/Updates/UpdatesState.cs @@ -0,0 +1,75 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal abstract class UpdatesState + : State + { + private bool continueButtonVisible; + private MarqueeStyle marqueeStyle; + + public new UpdatesStateMachine StateMachine + { + get + { + return (UpdatesStateMachine)base.StateMachine; + } + } + + public virtual string InfoText + { + get + { + string infoTextStringName = "UpdatesDialog.InfoText.Text." + this.GetType().Name; + string infoText = PdnResources.GetString(infoTextStringName); + return infoText; + } + } + + public string ContinueButtonText + { + get + { + string continueButtonTextStringName = "UpdatesDialog.ContinueButton.Text." + this.GetType().Name; + string continueButtonText = PdnResources.GetString(continueButtonTextStringName); + return continueButtonText; + } + } + + public bool ContinueButtonVisible + { + get + { + return this.continueButtonVisible; + } + } + + public MarqueeStyle MarqueeStyle + { + get + { + return this.marqueeStyle; + } + } + + public UpdatesState(bool isFinalState, bool continueButtonVisible, MarqueeStyle marqueeStyle) + : base(isFinalState) + { + this.continueButtonVisible = continueButtonVisible; + this.marqueeStyle = marqueeStyle; + SystemLayer.Tracing.LogFeature("UpdatesState(" + GetType().Name + ")"); + } + } +} diff --git a/src/Updates/UpdatesStateMachine.cs b/src/Updates/UpdatesStateMachine.cs new file mode 100644 index 0000000..fef5133 --- /dev/null +++ b/src/Updates/UpdatesStateMachine.cs @@ -0,0 +1,37 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet.Updates +{ + internal class UpdatesStateMachine + : StateMachine + { + private Control uiContext; + public Control UIContext + { + get + { + return this.uiContext; + } + + set + { + this.uiContext = value; + } + } + + public UpdatesStateMachine() + : base(new StartupState(), new object[] { UpdatesAction.Continue, UpdatesAction.Cancel }) + { + } + } +} diff --git a/src/ViewConfigStrip.cs b/src/ViewConfigStrip.cs new file mode 100644 index 0000000..d50720a --- /dev/null +++ b/src/ViewConfigStrip.cs @@ -0,0 +1,495 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet +{ + internal sealed class ViewConfigStrip + : ToolStripEx + { + private string windowText; + private string percentageFormat; + private ToolStripSeparator separator0; + private ScaleFactor scaleFactor; + private ToolStripButton zoomOutButton; + private ToolStripButton zoomInButton; + private ToolStripComboBox zoomComboBox; + private ToolStripSeparator separator1; + private ToolStripButton gridButton; + private ToolStripButton rulersButton; + private ToolStripLabel unitsLabel; + private UnitsComboBoxStrip unitsComboBox; + + private int scaleFactorRecursionDepth = 0; + private int suspendEvents = 0; + private int ignoreZoomChanges = 0; + + public void SuspendEvents() + { + ++this.suspendEvents; + } + + public void ResumeEvents() + { + --this.suspendEvents; + } + + public void BeginZoomChanges() + { + ++this.ignoreZoomChanges; + } + + public void EndZoomChanges() + { + --this.ignoreZoomChanges; + } + + private ZoomBasis zoomBasis; + public ZoomBasis ZoomBasis + { + get + { + return this.zoomBasis; + } + + set + { + if (this.zoomBasis != value) + { + this.zoomBasis = value; + OnZoomBasisChanged(); + } + } + } + + public bool DrawGrid + { + get + { + return gridButton.Checked; + } + + set + { + if (gridButton.Checked != value) + { + gridButton.Checked = value; + this.OnDrawGridChanged(); + } + } + } + + public bool RulersEnabled + { + get + { + return rulersButton.Checked; + } + + set + { + if (rulersButton.Checked != value) + { + rulersButton.Checked = value; + this.OnRulersEnabledChanged(); + } + } + } + + public MeasurementUnit Units + { + get + { + return this.unitsComboBox.Units; + } + + set + { + this.unitsComboBox.Units = value; + } + } + + public ScaleFactor ScaleFactor + { + get + { + return this.scaleFactor; + } + + set + { + if (this.scaleFactor.Ratio != value.Ratio) + { + this.scaleFactor = value; + ++this.scaleFactorRecursionDepth; + + // Prevent infinite recursion that was reported by one person. + // This may cause the scale factor to settle on a less than + // desirable value, but this is obviously more desirable than + // a StackOverflow crash. + if (this.scaleFactorRecursionDepth < 100) + { + OnZoomScaleChanged(); + } + + --this.scaleFactorRecursionDepth; + } + } + } + + public ViewConfigStrip() + { + this.SuspendLayout(); + InitializeComponent(); + + this.windowText = EnumLocalizer.EnumValueToLocalizedName(typeof(ZoomBasis), ZoomBasis.FitToWindow); + this.percentageFormat = PdnResources.GetString("ZoomConfigWidget.Percentage.Format"); + + double[] zoomValues = ScaleFactor.PresetValues; + + this.zoomComboBox.ComboBox.SuspendLayout(); + + string percent100 = null; // ScaleFactor.PresetValues guarantees that 1.0, or "100%" is in the list, but the compiler can't be shown this so we must assign a value here + for (int i = zoomValues.Length - 1; i >= 0; --i) + { + string zoomValueString = (zoomValues[i] * 100.0).ToString(); + string zoomItemString = string.Format(this.percentageFormat, zoomValueString); + + if (zoomValues[i] == 1.0) + { + percent100 = zoomItemString; + } + + this.zoomComboBox.Items.Add(zoomItemString); + } + + this.zoomComboBox.Items.Add(this.windowText); + this.zoomComboBox.ComboBox.ResumeLayout(false); + this.zoomComboBox.Size = new Size(UI.ScaleWidth(this.zoomComboBox.Width), zoomComboBox.Height); + + this.unitsLabel.Text = PdnResources.GetString("WorkspaceOptionsConfigWidget.UnitsLabel.Text"); + + this.zoomComboBox.Text = percent100; + this.ScaleFactor = ScaleFactor.OneToOne; + + this.zoomOutButton.Image = PdnResources.GetImageResource("Icons.MenuViewZoomOutIcon.png").Reference; + this.zoomInButton.Image = PdnResources.GetImageResource("Icons.MenuViewZoomInIcon.png").Reference; + this.gridButton.Image = PdnResources.GetImageResource("Icons.MenuViewGridIcon.png").Reference; + this.rulersButton.Image = PdnResources.GetImageResource("Icons.MenuViewRulersIcon.png").Reference; + + this.zoomOutButton.ToolTipText = PdnResources.GetString("ZoomConfigWidget.ZoomOutButton.ToolTipText"); + this.zoomInButton.ToolTipText = PdnResources.GetString("ZoomConfigWidget.ZoomInButton.ToolTipText"); + this.gridButton.ToolTipText = PdnResources.GetString("WorkspaceOptionsConfigWidget.DrawGridToggleButton.ToolTipText"); + this.rulersButton.ToolTipText = PdnResources.GetString("WorkspaceOptionsConfigWidget.RulersToggleButton.ToolTipText"); + + this.unitsComboBox.Size = new Size(UI.ScaleWidth(this.unitsComboBox.Width), unitsComboBox.Height); + + this.zoomBasis = ZoomBasis.ScaleFactor; + ScaleFactor = ScaleFactor.OneToOne; + + this.ResumeLayout(false); + } + + private void InitializeComponent() + { + this.separator0 = new ToolStripSeparator(); + this.zoomOutButton = new ToolStripButton(); + this.zoomComboBox = new ToolStripComboBox(); + this.zoomInButton = new ToolStripButton(); + this.separator1 = new ToolStripSeparator(); + this.gridButton = new ToolStripButton(); + this.rulersButton = new ToolStripButton(); + this.unitsLabel = new ToolStripLabel(); + this.unitsComboBox = new UnitsComboBoxStrip(); + this.SuspendLayout(); + // + // separator0 + // + this.separator0.Name = "separator0"; + // + // zoomComboBox + // + this.zoomComboBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.ZoomComboBox_KeyPress); + this.zoomComboBox.Validating += new System.ComponentModel.CancelEventHandler(this.ZoomComboBox_Validating); + this.zoomComboBox.SelectedIndexChanged += new System.EventHandler(this.ZoomComboBox_SelectedIndexChanged); + this.zoomComboBox.Size = new Size(75, this.zoomComboBox.Height); + this.zoomComboBox.MaxDropDownItems = 99; + // + // unitsComboBox + // + this.unitsComboBox.UnitsChanged += new EventHandler(UnitsComboBox_UnitsChanged); + this.unitsComboBox.LowercaseStrings = false; + this.unitsComboBox.UnitsDisplayType = UnitsDisplayType.Plural; + this.unitsComboBox.Units = MeasurementUnit.Pixel; + this.unitsComboBox.Size = new Size(90, this.unitsComboBox.Height); + // + // ViewConfigStrip + // + this.Items.Add(this.separator0); + this.Items.Add(this.zoomOutButton); + this.Items.Add(this.zoomComboBox); + this.Items.Add(this.zoomInButton); + this.Items.Add(this.separator1); + this.Items.Add(this.gridButton); + this.Items.Add(this.rulersButton); + this.Items.Add(this.unitsLabel); + this.Items.Add(this.unitsComboBox); + this.ResumeLayout(false); + } + + private void UnitsComboBox_UnitsChanged(object sender, EventArgs e) + { + this.OnUnitsChanged(); + } + + private void SetZoomText() + { + if (this.ignoreZoomChanges == 0) + { + this.zoomComboBox.BackColor = SystemColors.Window; + string newText = zoomComboBox.Text; + + switch (zoomBasis) + { + case ZoomBasis.FitToWindow: + newText = this.windowText; + break; + + case ZoomBasis.ScaleFactor: + newText = scaleFactor.ToString(); + break; + } + + if (zoomComboBox.Text != newText) + { + zoomComboBox.Text = newText; + zoomComboBox.ComboBox.Update(); + } + } + } + + public event EventHandler DrawGridChanged; + private void OnDrawGridChanged() + { + if (DrawGridChanged != null) + { + DrawGridChanged(this, EventArgs.Empty); + } + } + + public event EventHandler RulersEnabledChanged; + private void OnRulersEnabledChanged() + { + if (RulersEnabledChanged != null) + { + RulersEnabledChanged(this, EventArgs.Empty); + } + } + + public event EventHandler UnitsChanged; + private void OnUnitsChanged() + { + if (UnitsChanged != null) + { + UnitsChanged(this, EventArgs.Empty); + } + } + + public event EventHandler ZoomScaleChanged; + private void OnZoomScaleChanged() + { + if (zoomBasis == ZoomBasis.ScaleFactor) + { + SetZoomText(); + + if (ZoomScaleChanged != null) + { + ZoomScaleChanged(this, EventArgs.Empty); + } + } + } + + public event EventHandler ZoomIn; + private void OnZoomIn() + { + if (ZoomIn != null) + { + ZoomIn(this, EventArgs.Empty); + } + } + + public event EventHandler ZoomOut; + private void OnZoomOut() + { + if (ZoomOut != null) + { + ZoomOut(this, EventArgs.Empty); + } + } + + public void PerformZoomBasisChanged() + { + OnZoomBasisChanged(); + } + + public event EventHandler ZoomBasisChanged; + private void OnZoomBasisChanged() + { + SetZoomText(); + + if (ZoomBasisChanged != null) + { + ZoomBasisChanged(this, EventArgs.Empty); + } + } + + public void PerformZoomScaleChanged() + { + OnZoomScaleChanged(); + } + + private void ZoomComboBox_Validating(object sender, System.ComponentModel.CancelEventArgs e) + { + try + { + int val = 1; + e.Cancel = false; + + if (zoomComboBox.Text == this.windowText) + { + ZoomBasis = ZoomBasis.FitToWindow; + } + else + { + try + { + string text = zoomComboBox.Text; + + if (text.Length == 0) + { + e.Cancel = true; + } + else + { + if (text[text.Length - 1] == '%') + { + text = text.Substring(0, text.Length - 1); + } + else if (text[0] == '%') + { + text = text.Substring(1); + } + + val = (int)Math.Round(double.Parse(text)); + ZoomBasis = ZoomBasis.ScaleFactor; + } + } + + catch (FormatException) + { + e.Cancel = true; + } + + catch (OverflowException) + { + e.Cancel = true; + } + + if (e.Cancel) + { + this.zoomComboBox.BackColor = Color.Red; + this.zoomComboBox.ToolTipText = PdnResources.GetString("ZoomConfigWidget.Error.InvalidNumber"); + } + else + { + if (val < 1) + { + e.Cancel = true; + this.zoomComboBox.BackColor = Color.Red; + this.zoomComboBox.ToolTipText = PdnResources.GetString("ZoomConfigWidget.Error.TooSmall"); + } + else if (val > 3200) + { + e.Cancel = true; + this.zoomComboBox.BackColor = Color.Red; + this.zoomComboBox.ToolTipText = PdnResources.GetString("ZoomConfigWidget.Error.TooLarge"); + } + else + { + // Clear the error + e.Cancel = false; + this.zoomComboBox.ToolTipText = string.Empty; + this.zoomComboBox.BackColor = SystemColors.Window; + ScaleFactor = new ScaleFactor(val, 100); + SuspendEvents(); + ZoomBasis = ZoomBasis.ScaleFactor; + ResumeEvents(); + } + } + } + } + + catch (FormatException) + { + } + } + + private void ZoomComboBox_SelectedIndexChanged(object sender, EventArgs e) + { + if (this.suspendEvents == 0) + { + ZoomComboBox_Validating(sender, new CancelEventArgs(false)); + } + } + + private void ZoomComboBox_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) + { + if (e.KeyChar == '\n' || e.KeyChar == '\r') + { + ZoomComboBox_Validating(sender, new CancelEventArgs(false)); + zoomComboBox.Select(0, zoomComboBox.Text.Length); + } + } + + protected override void OnItemClicked(ToolStripItemClickedEventArgs e) + { + + if (e.ClickedItem == this.zoomInButton) + { + Tracing.LogFeature("ViewConfigStrip(ZoomIn)"); + OnZoomIn(); + } + else if (e.ClickedItem == this.zoomOutButton) + { + Tracing.LogFeature("ViewConfigStrip(ZoomOut)"); + OnZoomOut(); + } + else if (e.ClickedItem == this.rulersButton) + { + Tracing.LogFeature("ViewConfigStrip(Rulers)"); + this.rulersButton.Checked = !this.rulersButton.Checked; + OnRulersEnabledChanged(); + } + else if (e.ClickedItem == this.gridButton) + { + Tracing.LogFeature("ViewConfigStrip(Grid)"); + this.gridButton.Checked = !this.gridButton.Checked; + this.OnDrawGridChanged(); + } + + base.OnItemClicked(e); + } + } +} diff --git a/src/WhichUserColor.cs b/src/WhichUserColor.cs new file mode 100644 index 0000000..faa3ff4 --- /dev/null +++ b/src/WhichUserColor.cs @@ -0,0 +1,22 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; + +namespace PaintDotNet +{ + /// + /// An enumeration to allow selection between Primary and Secondary + /// + internal enum WhichUserColor + { + Primary, + Secondary + } +} diff --git a/src/WiaProxy32/Properties/AssemblyInfo.cs b/src/WiaProxy32/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6431b18 --- /dev/null +++ b/src/WiaProxy32/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: CLSCompliant(false)] +[assembly: ComVisible(false)] +[assembly: AssemblyTitle("Paint.NET WIA 32-bit Proxy")] +[assembly: AssemblyDescription("Image and photo editing software written in C#.")] +[assembly: AssemblyCompany("dotPDN LLC")] +[assembly: AssemblyProduct("Paint.NET")] +[assembly: AssemblyCopyright("Copyright © 2008 dotPDN LLC, Rick Brewster, Tom Jackson, and past contributors. Portions Copyright © Microsoft Corporation. All Rights Reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("3.36.*")] +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] +[assembly: StringFreezing()] +[assembly: DefaultDependency(LoadHint.Always)] diff --git a/src/WiaProxy32/WiaProxy32.cs b/src/WiaProxy32/WiaProxy32.cs new file mode 100644 index 0000000..27ec201 --- /dev/null +++ b/src/WiaProxy32/WiaProxy32.cs @@ -0,0 +1,225 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// NOTE: This exe MUST be built to target the x86 CPU platform, NOT x64 or 'Any'. + +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Text; + +namespace WiaProxy32 +{ + class WiaProxy32 + { + const string helpText = @"WiaProxy32 for Paint.NET + +Purpose: +Paint.NET uses the Windows Image Acquisition Automation Layer, which is +contained in wiaaut.dll. This DLL requires Windows XP SP1 or newer. However, +try as we might we could not find a 64-bit version of this DLL. So, in order +to get printing and scanning for 64-bit Paint.NET, we have implemented this +out-of-process proxy for wiaaut.dll that implements the functionality +required by the PaintDotNet.SystemLayer.ScanningAndPrinting class. + +Command line: + wiaproxy32 [IsComponentAvailable 1 | + CanPrint 1 | + CanScan 1 | + Print | + Scan ] + +IsComponentAvailable, CanPrint, CanScan + These implement the properties for the ScanningAndPrinting class. Return + codes are -1 for error, 0 for false and 1 for true. For these, you must + specify a second parameter. In the syntax above, it is listed as '1'. + +Print + Presents the printing user interface. The image to be printed is + . Return code is -1 for error, or 0. + +Scan + Presents the scanning user interface. The is scanned or acquired and put + into the given filename. Return codes are -1 for error, or one of the + values in the ScanResult enumeration."; + + static int Main(string[] args) + { + int exitCode; + + try + { + exitCode = MainImpl(args); + } + + catch + { + exitCode = -1; + } + + return exitCode; + } + + static int MainImpl(string[] args) + { + if (args.Length != 2) + { + Console.WriteLine(helpText); + return 0; + } + + Console.WriteLine(args[0] + " " + args[1]); + + int exitCode; + + switch (args[0]) + { + case "IsComponentAvailable": + exitCode = IsComponentAvailable ? 1 : 0; + break; + + case "CanPrint": + exitCode = CanPrint ? 1 : 0; + break; + + case "CanScan": + exitCode = CanScan ? 1 : 0; + break; + + case "Print": + Print(args[1]); + exitCode = 0; + break; + + case "Scan": + exitCode = (int)Scan(args[1]); + break; + + default: + exitCode = -1; + break; + } + + return exitCode; + } + + private static bool IsComponentAvailable + { + get + { + return IsWia2Available(); + } + } + + private static bool CanPrint + { + get + { + return IsWia2Available(); + } + } + + private static bool CanScan + { + get + { + if (IsWia2Available()) + { + WIA.DeviceManagerClass dmc = new WIA.DeviceManagerClass(); + + if (dmc.DeviceInfos.Count > 0) + { + return true; + } + } + + return false; + } + } + + private static void Print(string fileName) + { + WIA.VectorClass vector = new WIA.VectorClass(); + object tempName_o = (object)fileName; + vector.Add(ref tempName_o, 0); + object vector_o = (object)vector; + WIA.CommonDialogClass cdc = new WIA.CommonDialogClass(); + cdc.ShowPhotoPrintingWizard(ref vector_o); + } + + private static ScanResult Scan(string fileName) + { + ScanResult result; + + WIA.CommonDialogClass cdc = new WIA.CommonDialogClass(); + WIA.ImageFile imageFile = null; + + try + { + imageFile = cdc.ShowAcquireImage(WIA.WiaDeviceType.UnspecifiedDeviceType, + WIA.WiaImageIntent.UnspecifiedIntent, + WIA.WiaImageBias.MaximizeQuality, + "{00000000-0000-0000-0000-000000000000}", + true, + true, + false); + } + + catch (System.Runtime.InteropServices.COMException) + { + result = ScanResult.DeviceBusy; + imageFile = null; + } + + if (imageFile != null) + { + imageFile.SaveFile(fileName); + result = ScanResult.Success; + } + else + { + result = ScanResult.UserCancelled; + } + + return result; + } + + // Have to split this in to two functions because the WIA DLL is resolved + // at the time a function is entered that depends on it (IsWia2AvailableImpl). + // This way we can avoid a runtime error and maintain the semantics of + // IsWia2Available(). + + private static bool IsWia2Available() + { + try + { + return IsWia2AvailableImpl(); + } + + catch + { + return false; + } + } + + private static bool IsWia2AvailableImpl() + { + try + { + WIA.DeviceManagerClass dmc = new WIA.DeviceManagerClass(); + return true; + } + + catch + { + return false; + } + } + } +} diff --git a/src/WiaProxy32/WiaProxy32.csproj b/src/WiaProxy32/WiaProxy32.csproj new file mode 100644 index 0000000..74542eb --- /dev/null +++ b/src/WiaProxy32/WiaProxy32.csproj @@ -0,0 +1,85 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1} + Exe + Properties + WiaProxy32 + WiaProxy32 + + + 2.0 + + + + + true + bin\Debug\ + DEBUG;TRACE + full + x86 + true + GlobalSuppressions.cs + prompt + true + false + + + bin\Release\ + TRACE + true + pdbonly + x86 + true + GlobalSuppressions.cs + prompt + true + true + false + + + + False + ..\Interop.WIA\Interop.WIA.dll + + + + + + + + + + {0B173113-1F9B-4939-A62F-A176336F13AC} + Resources + + + {80572820-93A5-4278-A513-D902BEA2639C} + SystemLayer + + + + + + + + + @rem Embed manifest +call "$(SolutionDir)Manifests\embedManifest.bat" "$(TargetPath)" "$(SolutionDir)Manifests\asInvoker.xml" +call "$(SolutionDir)Manifests\embedManifest.bat" "$(ProjectDir)obj\$(PlatformName)\$(ConfigurationName)\$(TargetFileName)" "$(SolutionDir)Manifests\asInvoker.xml" + +@rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(PlatformName)\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + + + \ No newline at end of file diff --git a/src/WiaProxy32/app.config b/src/WiaProxy32/app.config new file mode 100644 index 0000000..6c98a46 --- /dev/null +++ b/src/WiaProxy32/app.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/WorkspaceWidgets.cs b/src/WorkspaceWidgets.cs new file mode 100644 index 0000000..7e3be76 --- /dev/null +++ b/src/WorkspaceWidgets.cs @@ -0,0 +1,181 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Windows.Forms; + +namespace PaintDotNet +{ + /// + /// This class is used to hold references to many of the UI elements + /// that are privately encapsulated in various places. + /// This allows other program elements to access these objects while + /// allowing these items to move around, and without breaking OO best + /// practices. + /// + internal class WorkspaceWidgets + { + private AppWorkspace workspace; + + private DocumentStrip documentStrip; + public DocumentStrip DocumentStrip + { + get + { + return this.documentStrip; + } + + set + { + this.documentStrip = value; + } + } + + private ViewConfigStrip viewConfigStrip; + public ViewConfigStrip ViewConfigStrip + { + get + { + return this.viewConfigStrip; + } + + set + { + this.viewConfigStrip = value; + } + } + + private ToolConfigStrip toolConfigStrip; + public ToolConfigStrip ToolConfigStrip + { + get + { + return this.toolConfigStrip; + } + + set + { + this.toolConfigStrip = value; + } + } + + private CommonActionsStrip commonActionsStrip; + public CommonActionsStrip CommonActionsStrip + { + get + { + return this.commonActionsStrip; + } + + set + { + this.commonActionsStrip = value; + } + } + + private ToolsForm toolsForm; + public ToolsForm ToolsForm + { + get + { + return this.toolsForm; + } + + set + { + this.toolsForm = value; + } + } + + public ToolsControl ToolsControl + { + get + { + return this.toolsForm.ToolsControl; + } + } + + private LayerForm layerForm; + public LayerForm LayerForm + { + get + { + return layerForm; + } + + set + { + layerForm = value; + } + } + + public LayerControl LayerControl + { + get + { + return this.layerForm.LayerControl; + } + } + + private HistoryForm historyForm; + public HistoryForm HistoryForm + { + get + { + return this.historyForm; + } + + set + { + this.historyForm = value; + } + } + + public HistoryControl HistoryControl + { + get + { + return this.historyForm.HistoryControl; + } + } + + private ColorsForm colorsForm; + public ColorsForm ColorsForm + { + get + { + return this.colorsForm; + } + + set + { + this.colorsForm = value; + } + } + + private IStatusBarProgress statusBarProgress; + public IStatusBarProgress StatusBarProgress + { + get + { + return this.statusBarProgress; + } + + set + { + this.statusBarProgress = value; + } + } + + public WorkspaceWidgets(AppWorkspace workspace) + { + this.workspace = workspace; + } + } +} diff --git a/src/ZoomBasis.cs b/src/ZoomBasis.cs new file mode 100644 index 0000000..19038ee --- /dev/null +++ b/src/ZoomBasis.cs @@ -0,0 +1,17 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +namespace PaintDotNet +{ + internal enum ZoomBasis + { + FitToWindow, + ScaleFactor + } +} \ No newline at end of file diff --git a/src/app.config b/src/app.config new file mode 100644 index 0000000..6c98a46 --- /dev/null +++ b/src/app.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/count.bat b/src/count.bat new file mode 100644 index 0000000..9fd9930 --- /dev/null +++ b/src/count.bat @@ -0,0 +1,9 @@ +@rem Use this to count the number of lines of code in Paint.NET +@rem First number in the last row is the number of lines of code. +@rem This counts all our .cs, .c, .cpp, .h files +pushd .. +dir /b /s /a-d *.cs *.c *.cpp *.h > _list.txt +src\BuildTools\wc @_list.txt +del _list.txt +popd +pause diff --git a/src/paintdotnet.csproj b/src/paintdotnet.csproj new file mode 100644 index 0000000..051d347 --- /dev/null +++ b/src/paintdotnet.csproj @@ -0,0 +1,631 @@ + + + Local + 9.0.30729 + 2.0 + {34920450-488E-41DF-8565-AA22C2A56902} + Debug + AnyCPU + Resources\icons\PaintDotNet.ico + + + PaintDotNet + + + JScript + Grid + IE50 + false + WinExe + PaintDotNet + Always + + + + + + + 2.0 + + + bin\Debug\ + true + 301989888 + true + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + true + 4 + full + prompt + false + x86 + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + bin\Release\ + true + 301989888 + false + + + TRACE + + + true + 512 + false + + + true + false + false + true + 4 + pdbonly + prompt + AnyCPU + false + true + -Microsoft.Design#CA1012;-Microsoft.Design#CA2210;-Microsoft.Design#CA1040;-Microsoft.Design#CA1005;-Microsoft.Design#CA1020;-Microsoft.Design#CA1021;-Microsoft.Design#CA1010;-Microsoft.Design#CA1011;-Microsoft.Design#CA1009;-Microsoft.Design#CA1050;-Microsoft.Design#CA1026;-Microsoft.Design#CA1019;-Microsoft.Design#CA1031;-Microsoft.Design#CA1047;-Microsoft.Design#CA1000;-Microsoft.Design#CA1048;-Microsoft.Design#CA1051;-Microsoft.Design#CA1002;-Microsoft.Design#CA1061;-Microsoft.Design#CA1006;-Microsoft.Design#CA1046;-Microsoft.Design#CA1045;-Microsoft.Design#CA1038;-Microsoft.Design#CA1008;-Microsoft.Design#CA1028;-Microsoft.Design#CA1004;-Microsoft.Design#CA1035;-Microsoft.Design#CA1063;-Microsoft.Design#CA1032;-Microsoft.Design#CA1023;-Microsoft.Design#CA1033;-Microsoft.Design#CA1039;-Microsoft.Design#CA1016;-Microsoft.Design#CA1014;-Microsoft.Design#CA1017;-Microsoft.Design#CA1018;-Microsoft.Design#CA1027;-Microsoft.Design#CA1059;-Microsoft.Design#CA1060;-Microsoft.Design#CA1034;-Microsoft.Design#CA1013;-Microsoft.Design#CA1036;-Microsoft.Design#CA1044;-Microsoft.Design#CA1041;-Microsoft.Design#CA1025;-Microsoft.Design#CA1052;-Microsoft.Design#CA1053;-Microsoft.Design#CA1057;-Microsoft.Design#CA1058;-Microsoft.Design#CA1001;-Microsoft.Design#CA1049;-Microsoft.Design#CA1054;-Microsoft.Design#CA1056;-Microsoft.Design#CA1055;-Microsoft.Design#CA1030;-Microsoft.Design#CA1003;-Microsoft.Design#CA1007;-Microsoft.Design#CA1043;-Microsoft.Design#CA1024;-Microsoft.Design#CA1062;-Microsoft.Globalization#CA1301;-Microsoft.Globalization#CA1302;-Microsoft.Globalization#CA1303;-Microsoft.Globalization#CA1306;-Microsoft.Globalization#CA1304;-Microsoft.Globalization#CA1305;-Microsoft.Globalization#CA1300;-Microsoft.Interoperability#CA1403;-Microsoft.Interoperability#CA1406;-Microsoft.Interoperability#CA1413;-Microsoft.Interoperability#CA1402;-Microsoft.Interoperability#CA1407;-Microsoft.Interoperability#CA1404;-Microsoft.Interoperability#CA1410;-Microsoft.Interoperability#CA1411;-Microsoft.Interoperability#CA1405;-Microsoft.Interoperability#CA1409;-Microsoft.Interoperability#CA1415;-Microsoft.Interoperability#CA1408;-Microsoft.Interoperability#CA1414;-Microsoft.Interoperability#CA1412;-Microsoft.Maintainability#CA1502;-Microsoft.Maintainability#CA1501;-Microsoft.Mobility#CA1600;-Microsoft.Mobility#CA1601;-Microsoft.Naming#CA1718;-Microsoft.Naming#CA1720;-Microsoft.Naming#CA1700;-Microsoft.Naming#CA1712;-Microsoft.Naming#CA1713;-Microsoft.Naming#CA1709;-Microsoft.Naming#CA1708;-Microsoft.Naming#CA1715;-Microsoft.Naming#CA1710;-Microsoft.Naming#CA1707;-Microsoft.Naming#CA1722;-Microsoft.Naming#CA1711;-Microsoft.Naming#CA1716;-Microsoft.Naming#CA1705;-Microsoft.Naming#CA1725;-Microsoft.Naming#CA1719;-Microsoft.Naming#CA1721;-Microsoft.Naming#CA1706;-Microsoft.Naming#CA1724;-Microsoft.Naming#CA1726;-Microsoft.Performance#CA1809;-Microsoft.Performance#CA1811;-Microsoft.Performance#CA1812;-Microsoft.Performance#CA1807;-Microsoft.Performance#CA1813;-Microsoft.Performance#CA1823;-Microsoft.Performance#CA1816;-Microsoft.Performance#CA1817;-Microsoft.Performance#CA1800;-Microsoft.Performance#CA1818;-Microsoft.Performance#CA1805;-Microsoft.Performance#CA1810;-Microsoft.Performance#CA1822;-Microsoft.Performance#CA1815;-Microsoft.Performance#CA1814;-Microsoft.Performance#CA1819;-Microsoft.Performance#CA1804;-Microsoft.Performance#CA1820;-Microsoft.Performance#CA1802;-Microsoft.Portability#CA1900;-Microsoft.Reliability#CA2000;-Microsoft.Reliability#CA2002;-Microsoft.Reliability#CA2003;-Microsoft.Reliability#CA2004;-Microsoft.Reliability#CA2006;-Microsoft.Security#CA2116;-Microsoft.Security#CA2117;-Microsoft.Security#CA2105;-Microsoft.Security#CA2115;-Microsoft.Security#CA2104;-Microsoft.Security#CA2122;-Microsoft.Security#CA2114;-Microsoft.Security#CA2123;-Microsoft.Security#CA2111;-Microsoft.Security#CA2108;-Microsoft.Security#CA2107;-Microsoft.Security#CA2103;-Microsoft.Security#CA2100;-Microsoft.Security#CA2118;-Microsoft.Security#CA2109;-Microsoft.Security#CA2119;-Microsoft.Security#CA2106;-Microsoft.Security#CA2112;-Microsoft.Security#CA2110;-Microsoft.Security#CA2120;-Microsoft.Security#CA2101;-Microsoft.Security#CA2121;-Microsoft.Security#CA2126;-Microsoft.Security#CA2124;-Microsoft.Usage#CA2209;-Microsoft.Usage#CA2236;-Microsoft.Usage#CA2227;-Microsoft.Usage#CA2213;-Microsoft.Usage#CA2216;-Microsoft.Usage#CA2215;-Microsoft.Usage#CA2214;-Microsoft.Usage#CA2222;-Microsoft.Usage#CA2202;-Microsoft.Usage#CA1806;-Microsoft.Usage#CA2217;-Microsoft.Usage#CA2212;-Microsoft.Usage#CA2219;-Microsoft.Usage#CA2201;-Microsoft.Usage#CA2228;-Microsoft.Usage#CA2221;-Microsoft.Usage#CA2220;-Microsoft.Usage#CA2240;-Microsoft.Usage#CA2229;-Microsoft.Usage#CA2238;-Microsoft.Usage#CA2207;-Microsoft.Usage#CA2208;-Microsoft.Usage#CA2235;-Microsoft.Usage#CA2237;-Microsoft.Usage#CA2232;-Microsoft.Usage#CA2223;-Microsoft.Usage#CA2211;-Microsoft.Usage#CA2233;-Microsoft.Usage#CA2225;-Microsoft.Usage#CA2226;-Microsoft.Usage#CA2231;-Microsoft.Usage#CA2224;-Microsoft.Usage#CA2218;-Microsoft.Usage#CA2234;-Microsoft.Usage#CA2241;-Microsoft.Usage#CA2239;-Microsoft.Usage#CA2200;-Microsoft.Usage#CA1801;-Microsoft.Usage#CA2205;-Microsoft.Usage#CA2230 + + + + False + SharpZipLib\Bin\ICSharpCode.SharpZipLib.dll + + + System + + + + System.Drawing + + + System.Windows.Forms + + + + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + Base + + + Data + {66681BB0-955D-451D-A466-94C045B1CF4A} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + Effects + {2E4E8805-00F7-4B18-A967-C23994BBCE75} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + Core + {1EADE568-A866-4DD4-9898-0A151E3F0E26} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + Resources + {0B173113-1F9B-4939-A62F-A176336F13AC} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + SystemLayer + {80572820-93A5-4278-A513-D902BEA2639C} + {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + + + Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UserControl + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Form + + + Form + + + UserControl + + + Code + + + Code + + + + UserControl + + + Form + + + UserControl + + + Code + + + Component + + + Component + + + Code + + + UserControl + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + Component + + + + + Form + + + + + + Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + + + + + + + + + + + + + + + + Form + + + Form + + + + + Code + + + UserControl + + + Component + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Form + + + Code + + + Code + + + Code + + + Code + + + Component + + + Form + + + Code + + + UserControl + + + UserControl + + + UserControl + + + Form + + + Form + + + UserControl + + + Form + + + Code + + + Code + + + + + + Form + + + Component + + + Component + + + Component + + + Code + + + Code + + + Code + + + Form + + + Form + + + Code + + + Code + + + Form + + + Code + + + Code + + + Code + + + Form + + + Code + + + Code + + + Code + + + Component + + + Code + + + + Component + + + Code + + + Code + + + Code + + + Code + + + Code + + + Component + + + + + + HistoryForm.cs + Designer + + + LayerForm.cs + Designer + + + + + AboutDialog.cs + Designer + + + + Designer + ChooseToolDefaultsDialog.cs + + + ColorsForm.cs + Designer + + + Designer + SavePaletteDialog.cs + + + Designer + UnsavedChangesDialog.cs + + + + + + + + + + @rem Embed manifest +call "$(SolutionDir)Manifests\embedManifest.bat" "$(TargetPath)" "$(SolutionDir)Manifests\asInvoker.xml" +call "$(SolutionDir)Manifests\embedManifest.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" "$(SolutionDir)Manifests\asInvoker.xml" + +@rem Sign +rem call "$(SolutionDir)signfile.bat" "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)" +rem call "$(SolutionDir)signfile.bat" "$(TargetPath)" + +@rem copy Interop.WIA.dll over +copy "$(SolutionDir)\Interop.WIA\Interop.WIA.dll" "$(TargetDir)" + +@rem Copy DDS over +mkdir "$(TargetDir)\FileTypes" + +copy "$(SolutionDir)\DdsFileType\DdsFileType\bin\$(ConfigurationName)\DdsFileType.dll" "$(TargetDir)\FileTypes" + +copy "$(SolutionDir)\DdsFileType\Squish\Squish_x86\$(ConfigurationName)\Squish_x86.dll" "$(TargetDir)" +copy "$(SolutionDir)\DdsFileType\Squish\Squish_x86_SSE2\$(ConfigurationName)\Squish_x86_SSE2.dll" "$(TargetDir)" +copy "$(SolutionDir)\DdsFileType\Squish\Squish_x64\$(ConfigurationName)\Squish_x64.dll" "$(TargetDir)" + +@rem copy SystemLayer.Native.*.dll over +rem copy "$(SolutionDir)\SystemLayer.Native\SystemLayer.Native.x64\$(ConfigurationName)\PaintDotNet.SystemLayer.Native.x64.dll" "$(TargetDir)" +rem copy "$(SolutionDir)\SystemLayer.Native\SystemLayer.Native.x86\$(ConfigurationName)\PaintDotNet.SystemLayer.Native.x86.dll" "$(TargetDir)" + +@rem copy ShellExtension_*.dll over +copy "$(SolutionDir)\ShellExtension\ShellExtension_x64\$(ConfigurationName)\ShellExtension_x64.dll" "$(TargetDir)" +copy "$(SolutionDir)\ShellExtension\ShellExtension_x86\$(ConfigurationName)\ShellExtension_x86.dll" "$(TargetDir)" + +@rem copy WiaProxy32.exe over +copy "$(SolutionDir)\WiaProxy32\bin\$(ConfigurationName)\WiaProxy32.exe" "$(TargetDir)" + +@rem copy English strings over +copy "$(SolutionDir)\Strings\$(ConfigurationName)\PaintDotNet.Strings.3.resources" "$(TargetDir)" + +@rem copy non-English strings (we won't copy the other file/image resource overrides) +copy "$(SolutionDir)\Resources.mui\*.resources" "$(TargetDir)" + +:done +rem call "$(ProjectDir)prejit.bat" "$(TargetPath)" + + \ No newline at end of file diff --git a/src/paintdotnet.sln b/src/paintdotnet.sln new file mode 100644 index 0000000..e51c0d1 --- /dev/null +++ b/src/paintdotnet.sln @@ -0,0 +1,280 @@ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "paintdotnet", "paintdotnet.csproj", "{34920450-488E-41DF-8565-AA22C2A56902}" + ProjectSection(ProjectDependencies) = postProject + {2E4E8805-00F7-4B18-A967-C23994BBCE75} = {2E4E8805-00F7-4B18-A967-C23994BBCE75} + {EF345808-905D-4738-BC3F-0D0CB3960C4A} = {EF345808-905D-4738-BC3F-0D0CB3960C4A} + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE} = {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE} + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} = {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} + {5B4C722D-8469-4B5F-9158-730B75AF68C1} = {5B4C722D-8469-4B5F-9158-730B75AF68C1} + {5B4C722D-8469-4B5F-9158-730B75AF68C2} = {5B4C722D-8469-4B5F-9158-730B75AF68C2} + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1} = {446DDC4A-1CC8-4F55-BDF2-21BB387574D1} + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} = {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} + {236A7657-45D6-46FF-A221-8E78FA447547} = {236A7657-45D6-46FF-A221-8E78FA447547} + {D1E5865F-9DC5-4504-A484-5CE8A8A79621} = {D1E5865F-9DC5-4504-A484-5CE8A8A79621} + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884} = {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884} + {5ED3F986-A73E-44C5-AC1A-BD101472CB01} = {5ED3F986-A73E-44C5-AC1A-BD101472CB01} + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} = {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81} = {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81} + EndProjectSection +EndProject +Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Setup MSI", "Setup\Setup.vdproj", "{082CE919-258F-49DE-8DB7-7E5C9DD79501}" + ProjectSection(ProjectDependencies) = postProject + {2E4E8805-00F7-4B18-A967-C23994BBCE75} = {2E4E8805-00F7-4B18-A967-C23994BBCE75} + {0B173113-1F9B-4939-A62F-A176336F13AC} = {0B173113-1F9B-4939-A62F-A176336F13AC} + {80572820-93A5-4278-A513-D902BEA2639C} = {80572820-93A5-4278-A513-D902BEA2639C} + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} = {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} = {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} + {1EADE568-A866-4DD4-9898-0A151E3F0E26} = {1EADE568-A866-4DD4-9898-0A151E3F0E26} + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} = {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} + {66681BB0-955D-451D-A466-94C045B1CF4A} = {66681BB0-955D-451D-A466-94C045B1CF4A} + {E37739E9-1A6D-4252-81CD-148379AD0832} = {E37739E9-1A6D-4252-81CD-148379AD0832} + EndProjectSection +EndProject +Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "WIAAutSDK MergeModule", "WIAAutSDK\MergeModule\WIAAutSDK MergeModule.vdproj", "{6376F236-ADF7-4E71-BA6B-9969E566BC88}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Setup Config", "Setup-Config\Setup-Config.vcproj", "{40A8C52E-F2F8-49D7-A825-A0411BD7306A}" + ProjectSection(ProjectDependencies) = postProject + {2E4E8805-00F7-4B18-A967-C23994BBCE75} = {2E4E8805-00F7-4B18-A967-C23994BBCE75} + {EF345808-905D-4738-BC3F-0D0CB3960C4A} = {EF345808-905D-4738-BC3F-0D0CB3960C4A} + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE} = {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE} + {0B173113-1F9B-4939-A62F-A176336F13AC} = {0B173113-1F9B-4939-A62F-A176336F13AC} + {082CE919-258F-49DE-8DB7-7E5C9DD79501} = {082CE919-258F-49DE-8DB7-7E5C9DD79501} + {80572820-93A5-4278-A513-D902BEA2639C} = {80572820-93A5-4278-A513-D902BEA2639C} + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} = {05C4C721-F8E3-42E1-B817-9D165DA1A9FB} + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} = {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC} + {5B4C722D-8469-4B5F-9158-730B75AF68C1} = {5B4C722D-8469-4B5F-9158-730B75AF68C1} + {5B4C722D-8469-4B5F-9158-730B75AF68C2} = {5B4C722D-8469-4B5F-9158-730B75AF68C2} + {6376F236-ADF7-4E71-BA6B-9969E566BC88} = {6376F236-ADF7-4E71-BA6B-9969E566BC88} + {FD728047-99F5-4CAF-957D-54194AA8D0F0} = {FD728047-99F5-4CAF-957D-54194AA8D0F0} + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1} = {446DDC4A-1CC8-4F55-BDF2-21BB387574D1} + {34920450-488E-41DF-8565-AA22C2A56902} = {34920450-488E-41DF-8565-AA22C2A56902} + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} = {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} + {236A7657-45D6-46FF-A221-8E78FA447547} = {236A7657-45D6-46FF-A221-8E78FA447547} + {D1E5865F-9DC5-4504-A484-5CE8A8A79621} = {D1E5865F-9DC5-4504-A484-5CE8A8A79621} + {1EADE568-A866-4DD4-9898-0A151E3F0E26} = {1EADE568-A866-4DD4-9898-0A151E3F0E26} + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884} = {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884} + {5ED3F986-A73E-44C5-AC1A-BD101472CB01} = {5ED3F986-A73E-44C5-AC1A-BD101472CB01} + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} = {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} + {66681BB0-955D-451D-A466-94C045B1CF4A} = {66681BB0-955D-451D-A466-94C045B1CF4A} + {9A2FADDE-15A5-4904-96D3-8ECD113B715C} = {9A2FADDE-15A5-4904-96D3-8ECD113B715C} + {E37739E9-1A6D-4252-81CD-148379AD0832} = {E37739E9-1A6D-4252-81CD-148379AD0832} + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81} = {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{1EADE568-A866-4DD4-9898-0A151E3F0E26}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Effects", "Effects\Effects.csproj", "{2E4E8805-00F7-4B18-A967-C23994BBCE75}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StylusReader", "StylusReader\StylusReader.csproj", "{E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SystemLayer", "SystemLayer\SystemLayer.csproj", "{80572820-93A5-4278-A513-D902BEA2639C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data", "Data\Data.csproj", "{66681BB0-955D-451D-A466-94C045B1CF4A}" + ProjectSection(ProjectDependencies) = postProject + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} = {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetupNgen", "SetupNgen\SetupNgen.csproj", "{E37739E9-1A6D-4252-81CD-148379AD0832}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resources", "Resources\Resources.csproj", "{0B173113-1F9B-4939-A62F-A176336F13AC}" + ProjectSection(ProjectDependencies) = postProject + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} = {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Strings", "Strings\Strings.vcproj", "{AE3E7C54-B864-4C89-9CC1-89DBE2042E02}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetupFrontEnd", "SetupFrontEnd\SetupFrontEnd.csproj", "{9A2FADDE-15A5-4904-96D3-8ECD113B715C}" + ProjectSection(ProjectDependencies) = postProject + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} = {AE3E7C54-B864-4C89-9CC1-89DBE2042E02} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SetupShim", "SetupShim\SetupShim.vcproj", "{FD728047-99F5-4CAF-957D-54194AA8D0F0}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShellExtension_x86", "ShellExtension\ShellExtension_x86\ShellExtension_x86.vcproj", "{236A7657-45D6-46FF-A221-8E78FA447547}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShellExtension_x64", "ShellExtension\ShellExtension_x64\ShellExtension_x64.vcproj", "{36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WiaProxy32", "WiaProxy32\WiaProxy32.csproj", "{446DDC4A-1CC8-4F55-BDF2-21BB387574D1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GeneratedCode", "GeneratedCode\GeneratedCode.vcproj", "{6D06E18A-6A4A-49FF-BB33-2C722D051BE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdnRepair", "PdnRepair\PdnRepair.csproj", "{D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateMonitor", "UpdateMonitor\UpdateMonitor.csproj", "{D1E5865F-9DC5-4504-A484-5CE8A8A79621}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resources.mui", "Resources.mui\Resources.mui.csproj", "{5ED3F986-A73E-44C5-AC1A-BD101472CB01}" + ProjectSection(ProjectDependencies) = postProject + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} = {6D06E18A-6A4A-49FF-BB33-2C722D051BE8} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Base", "Base\Base.csproj", "{05C4C721-F8E3-42E1-B817-9D165DA1A9FB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Squish_x64", "DdsFileType\Squish\Squish_x64\Squish_x64.vcproj", "{EF345808-905D-4738-BC3F-0D0CB3960C4A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Squish_x86", "DdsFileType\Squish\Squish_x86\Squish_x86.vcproj", "{5B4C722D-8469-4B5F-9158-730B75AF68C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DdsFileType", "DdsFileType\DdsFileType\DdsFileType.csproj", "{8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}" + ProjectSection(ProjectDependencies) = postProject + {EF345808-905D-4738-BC3F-0D0CB3960C4A} = {EF345808-905D-4738-BC3F-0D0CB3960C4A} + {5B4C722D-8469-4B5F-9158-730B75AF68C1} = {5B4C722D-8469-4B5F-9158-730B75AF68C1} + {5B4C722D-8469-4B5F-9158-730B75AF68C2} = {5B4C722D-8469-4B5F-9158-730B75AF68C2} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Squish_x86_SSE2", "DdsFileType\Squish\Squish_x86_SSE2\Squish_x86_SSE2.vcproj", "{5B4C722D-8469-4B5F-9158-730B75AF68C2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release and Package|Any CPU = Release and Package|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {34920450-488E-41DF-8565-AA22C2A56902}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34920450-488E-41DF-8565-AA22C2A56902}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34920450-488E-41DF-8565-AA22C2A56902}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {34920450-488E-41DF-8565-AA22C2A56902}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {34920450-488E-41DF-8565-AA22C2A56902}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34920450-488E-41DF-8565-AA22C2A56902}.Release|Any CPU.Build.0 = Release|Any CPU + {082CE919-258F-49DE-8DB7-7E5C9DD79501}.Debug|Any CPU.ActiveCfg = Debug + {082CE919-258F-49DE-8DB7-7E5C9DD79501}.Release and Package|Any CPU.ActiveCfg = Release + {082CE919-258F-49DE-8DB7-7E5C9DD79501}.Release and Package|Any CPU.Build.0 = Release + {082CE919-258F-49DE-8DB7-7E5C9DD79501}.Release|Any CPU.ActiveCfg = Release + {6376F236-ADF7-4E71-BA6B-9969E566BC88}.Debug|Any CPU.ActiveCfg = Debug + {6376F236-ADF7-4E71-BA6B-9969E566BC88}.Release and Package|Any CPU.ActiveCfg = Release + {6376F236-ADF7-4E71-BA6B-9969E566BC88}.Release and Package|Any CPU.Build.0 = Release + {6376F236-ADF7-4E71-BA6B-9969E566BC88}.Release|Any CPU.ActiveCfg = Release + {40A8C52E-F2F8-49D7-A825-A0411BD7306A}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {40A8C52E-F2F8-49D7-A825-A0411BD7306A}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {40A8C52E-F2F8-49D7-A825-A0411BD7306A}.Release and Package|Any CPU.Build.0 = Release|Win32 + {40A8C52E-F2F8-49D7-A825-A0411BD7306A}.Release|Any CPU.ActiveCfg = Release|Win32 + {1EADE568-A866-4DD4-9898-0A151E3F0E26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EADE568-A866-4DD4-9898-0A151E3F0E26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EADE568-A866-4DD4-9898-0A151E3F0E26}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {1EADE568-A866-4DD4-9898-0A151E3F0E26}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {1EADE568-A866-4DD4-9898-0A151E3F0E26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EADE568-A866-4DD4-9898-0A151E3F0E26}.Release|Any CPU.Build.0 = Release|Any CPU + {2E4E8805-00F7-4B18-A967-C23994BBCE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E4E8805-00F7-4B18-A967-C23994BBCE75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E4E8805-00F7-4B18-A967-C23994BBCE75}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {2E4E8805-00F7-4B18-A967-C23994BBCE75}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {2E4E8805-00F7-4B18-A967-C23994BBCE75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E4E8805-00F7-4B18-A967-C23994BBCE75}.Release|Any CPU.Build.0 = Release|Any CPU + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2E8B028-9FF8-4AF1-86BF-F20568EAB0EC}.Release|Any CPU.Build.0 = Release|Any CPU + {80572820-93A5-4278-A513-D902BEA2639C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80572820-93A5-4278-A513-D902BEA2639C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80572820-93A5-4278-A513-D902BEA2639C}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {80572820-93A5-4278-A513-D902BEA2639C}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {80572820-93A5-4278-A513-D902BEA2639C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80572820-93A5-4278-A513-D902BEA2639C}.Release|Any CPU.Build.0 = Release|Any CPU + {66681BB0-955D-451D-A466-94C045B1CF4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66681BB0-955D-451D-A466-94C045B1CF4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66681BB0-955D-451D-A466-94C045B1CF4A}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {66681BB0-955D-451D-A466-94C045B1CF4A}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {66681BB0-955D-451D-A466-94C045B1CF4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66681BB0-955D-451D-A466-94C045B1CF4A}.Release|Any CPU.Build.0 = Release|Any CPU + {E37739E9-1A6D-4252-81CD-148379AD0832}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E37739E9-1A6D-4252-81CD-148379AD0832}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E37739E9-1A6D-4252-81CD-148379AD0832}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {E37739E9-1A6D-4252-81CD-148379AD0832}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {E37739E9-1A6D-4252-81CD-148379AD0832}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E37739E9-1A6D-4252-81CD-148379AD0832}.Release|Any CPU.Build.0 = Release|Any CPU + {0B173113-1F9B-4939-A62F-A176336F13AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B173113-1F9B-4939-A62F-A176336F13AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B173113-1F9B-4939-A62F-A176336F13AC}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {0B173113-1F9B-4939-A62F-A176336F13AC}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {0B173113-1F9B-4939-A62F-A176336F13AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B173113-1F9B-4939-A62F-A176336F13AC}.Release|Any CPU.Build.0 = Release|Any CPU + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02}.Debug|Any CPU.Build.0 = Debug|Win32 + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02}.Release and Package|Any CPU.Build.0 = Release|Win32 + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02}.Release|Any CPU.ActiveCfg = Release|Win32 + {AE3E7C54-B864-4C89-9CC1-89DBE2042E02}.Release|Any CPU.Build.0 = Release|Win32 + {9A2FADDE-15A5-4904-96D3-8ECD113B715C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A2FADDE-15A5-4904-96D3-8ECD113B715C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A2FADDE-15A5-4904-96D3-8ECD113B715C}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {9A2FADDE-15A5-4904-96D3-8ECD113B715C}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {9A2FADDE-15A5-4904-96D3-8ECD113B715C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A2FADDE-15A5-4904-96D3-8ECD113B715C}.Release|Any CPU.Build.0 = Release|Any CPU + {FD728047-99F5-4CAF-957D-54194AA8D0F0}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {FD728047-99F5-4CAF-957D-54194AA8D0F0}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {FD728047-99F5-4CAF-957D-54194AA8D0F0}.Release and Package|Any CPU.Build.0 = Release|Win32 + {FD728047-99F5-4CAF-957D-54194AA8D0F0}.Release|Any CPU.ActiveCfg = Release|Win32 + {236A7657-45D6-46FF-A221-8E78FA447547}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {236A7657-45D6-46FF-A221-8E78FA447547}.Debug|Any CPU.Build.0 = Debug|Win32 + {236A7657-45D6-46FF-A221-8E78FA447547}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {236A7657-45D6-46FF-A221-8E78FA447547}.Release and Package|Any CPU.Build.0 = Release|Win32 + {236A7657-45D6-46FF-A221-8E78FA447547}.Release|Any CPU.ActiveCfg = Release|Win32 + {236A7657-45D6-46FF-A221-8E78FA447547}.Release|Any CPU.Build.0 = Release|Win32 + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}.Debug|Any CPU.ActiveCfg = Debug|x64 + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}.Debug|Any CPU.Build.0 = Debug|x64 + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}.Release and Package|Any CPU.ActiveCfg = Release|x64 + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}.Release and Package|Any CPU.Build.0 = Release|x64 + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}.Release|Any CPU.ActiveCfg = Release|x64 + {36F6FD0E-C4A7-45B9-9B7C-7DFC6DE667FE}.Release|Any CPU.Build.0 = Release|x64 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1}.Debug|Any CPU.ActiveCfg = Debug|x86 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1}.Debug|Any CPU.Build.0 = Debug|x86 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1}.Release and Package|Any CPU.ActiveCfg = Release|x86 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1}.Release and Package|Any CPU.Build.0 = Release|x86 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1}.Release|Any CPU.ActiveCfg = Release|x86 + {446DDC4A-1CC8-4F55-BDF2-21BB387574D1}.Release|Any CPU.Build.0 = Release|x86 + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8}.Debug|Any CPU.Build.0 = Debug|Win32 + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8}.Release and Package|Any CPU.Build.0 = Release|Win32 + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8}.Release|Any CPU.ActiveCfg = Release|Win32 + {6D06E18A-6A4A-49FF-BB33-2C722D051BE8}.Release|Any CPU.Build.0 = Release|Win32 + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {D4BEAD6A-0A65-4FAC-9ECE-B6A71EA5A884}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1E5865F-9DC5-4504-A484-5CE8A8A79621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1E5865F-9DC5-4504-A484-5CE8A8A79621}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1E5865F-9DC5-4504-A484-5CE8A8A79621}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {D1E5865F-9DC5-4504-A484-5CE8A8A79621}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {D1E5865F-9DC5-4504-A484-5CE8A8A79621}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1E5865F-9DC5-4504-A484-5CE8A8A79621}.Release|Any CPU.Build.0 = Release|Any CPU + {5ED3F986-A73E-44C5-AC1A-BD101472CB01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5ED3F986-A73E-44C5-AC1A-BD101472CB01}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {5ED3F986-A73E-44C5-AC1A-BD101472CB01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C4C721-F8E3-42E1-B817-9D165DA1A9FB}.Release|Any CPU.Build.0 = Release|Any CPU + {EF345808-905D-4738-BC3F-0D0CB3960C4A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {EF345808-905D-4738-BC3F-0D0CB3960C4A}.Debug|Any CPU.Build.0 = Debug|x64 + {EF345808-905D-4738-BC3F-0D0CB3960C4A}.Release and Package|Any CPU.ActiveCfg = Release|x64 + {EF345808-905D-4738-BC3F-0D0CB3960C4A}.Release and Package|Any CPU.Build.0 = Release|x64 + {EF345808-905D-4738-BC3F-0D0CB3960C4A}.Release|Any CPU.ActiveCfg = Release|x64 + {EF345808-905D-4738-BC3F-0D0CB3960C4A}.Release|Any CPU.Build.0 = Release|x64 + {5B4C722D-8469-4B5F-9158-730B75AF68C1}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C1}.Debug|Any CPU.Build.0 = Debug|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C1}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C1}.Release and Package|Any CPU.Build.0 = Release|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C1}.Release|Any CPU.ActiveCfg = Release|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C1}.Release|Any CPU.Build.0 = Release|Win32 + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release and Package|Any CPU.ActiveCfg = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release and Package|Any CPU.Build.0 = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CA4EBFD-46EA-437B-AD52-685C3E0BEC81}.Release|Any CPU.Build.0 = Release|Any CPU + {5B4C722D-8469-4B5F-9158-730B75AF68C2}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C2}.Debug|Any CPU.Build.0 = Debug|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C2}.Release and Package|Any CPU.ActiveCfg = Release|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C2}.Release and Package|Any CPU.Build.0 = Release|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C2}.Release|Any CPU.ActiveCfg = Release|Win32 + {5B4C722D-8469-4B5F-9158-730B75AF68C2}.Release|Any CPU.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/prejit.bat b/src/prejit.bat new file mode 100644 index 0000000..fbec792 --- /dev/null +++ b/src/prejit.bat @@ -0,0 +1,4 @@ +C:\windows\Microsoft.NET\framework\v2.0.50727\ngen install "%1" +C:\windows\Microsoft.NET\framework\v2.0.50727\ngen install "%1" /debug +C:\windows\Microsoft.NET\framework\v2.0.50727\ngen install "%1" /debug /profile +C:\windows\Microsoft.NET\framework\v2.0.50727\ngen install "%1" /profile diff --git a/src/readme.txt b/src/readme.txt new file mode 100644 index 0000000..21f2c39 --- /dev/null +++ b/src/readme.txt @@ -0,0 +1,46 @@ +Paint.NET Source Code "Read Me" +------------------------------- +There are a few important things to know about the Paint.NET source code +release. Think of this as a "Frequently Asked Questions" ... please read it! + +* The source code is not supported in any way. It is released primarily so that + others may study and learn from it, and so that plugin developers can have + something to work with. + +* There is no design document, or other documentation of any kind. + +* The installer is not included in the source code release. Its source code is + private and *not* available upon request. We have our reasons for doing this, + so please respect that and do not inquire further. + +* The non-English RESX files (string resources) are not included in the source + code release. + +* The forms in the project do not work in the Visual Studio designer. We do + not use the designer, so this is not a bug nor is it something that will be + "fixed." + +* The Paint.NET development team does not accept unsolicited contributions. + Please do not send us code or patches. + +Prerequisites +------------- +1. Windows XP, Windows Vista, Windows Server 2003/2008 + +2. Visual Studio .NET 2008 Professional Edition + You should install the C++ x64 compiler as well, which may not come installed + by default. The Express editions might work, but we don't know for sure since + we don't use them. + +Instructions +------------ +For normal development work, use 'Debug' configuration. When you are working in +this mode, you should make sure that the /skipRepairAttempt command line +parameter is present in the Debug tab of the 'paintdotnet' project's properties. +Otherwise, Paint.NET will see that some files are missing and attempt to repair +itself. Not all of these files are necessary when doing development or debugging. + +Also, you will need to make sure that mt.exe and signtool.exe are in a +directory that is in your PATH. These are available as part of the Windows SDK +which can be found at Microsoft's website. Usually it's sufficient to copy +these to %SYSTEMROOT%, which is usually C:\Windows. diff --git a/src/signfile.bat b/src/signfile.bat new file mode 100644 index 0000000..2706eea --- /dev/null +++ b/src/signfile.bat @@ -0,0 +1,5 @@ +@rem echo off +if "%SIGNPDN%" == "1" ( + signtool sign %PDNPFXARG% /d "Paint.NET" /du "http://www.getpaint.net/" /t http://timestamp.comodoca.com/authenticode /v "%1" +) + diff --git a/src/tools/CloneStampTool.cs b/src/tools/CloneStampTool.cs new file mode 100644 index 0000000..8c568a3 --- /dev/null +++ b/src/tools/CloneStampTool.cs @@ -0,0 +1,594 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.HistoryMementos; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; +using System.Collections; + +namespace PaintDotNet.Tools +{ + /// + /// Ctrl left-click to select an origin, left click to place it + /// + internal class CloneStampTool + : Tool + { + private class StaticData + { + public Point takeFrom; + public Point lastMoved; + public bool updateSrcPreview; + public WeakReference wr; + } + + private new StaticData GetStaticData() + { + object staticData = base.GetStaticData(); + + if (staticData == null) + { + staticData = new StaticData(); + base.SetStaticData(staticData); + } + + return (StaticData)staticData; + } + + private BitmapLayer takeFromLayer; + + private bool switchedTo = false; + private Rectangle undoRegion = Rectangle.Empty; + private PdnRegion savedRegion; + private RenderArgs ra; + private bool mouseUp = true; + private Vector historyRects; + private bool antialiasing; + private PdnRegion clipRegion; + + private BrushPreviewRenderer rendererDst; + private BrushPreviewRenderer rendererSrc; + + // private bool added by MK for "clone source" cursor transition + private bool mouseDownSettingCloneSource; + + private Cursor cursorMouseDown, cursorMouseUp, cursorMouseDownSetSource; + + private bool IsShiftDown() + { + return ModifierKeys == Keys.Shift; + } + + private bool IsCtrlDown() + { + return ModifierKeys == Keys.Control; + } + + /// + /// Button down mouse left. Returns true if only the left mouse button is depressed. + /// + /// + /// + private bool IsMouseLeftDown(MouseEventArgs e) + { + return e.Button == MouseButtons.Left; + } + + /// + /// Button down mouse right. Returns true if only the right mouse is depressed. + /// + /// + /// + private bool IsMouseRightDown(MouseEventArgs e) + { + return e.Button == MouseButtons.Right; + } + + protected override void OnMouseEnter() + { + this.rendererDst.Visible = true; + base.OnMouseEnter(); + } + + protected override void OnMouseLeave() + { + this.rendererDst.Visible = false; + base.OnMouseLeave(); + } + + public CloneStampTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.CloneStampToolIcon.png"), + PdnResources.GetString("CloneStampTool.Name"), + PdnResources.GetString("CloneStampTool.HelpText"), + 'l', + false, + ToolBarConfigItems.Pen | ToolBarConfigItems.Antialiasing) + { + } + + protected override void OnPulse() + { + double time = (double)new SystemLayer.Timing().GetTickCount(); + double sin = Math.Sin(time / 300.0); + int alpha = (int)Math.Ceiling((((sin + 1.0) / 2.0) * 224.0) + 31.0); + this.rendererSrc.BrushAlpha = alpha; + base.OnPulse(); + } + + protected override void OnActivate() + { + base.OnActivate(); + + cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur")); + cursorMouseDownSetSource = new Cursor(PdnResources.GetResourceStream("Cursors.CloneStampToolCursorSetSource.cur")); + cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.CloneStampToolCursor.cur")); + this.Cursor = cursorMouseUp; + + this.rendererDst = new BrushPreviewRenderer(this.RendererList); + this.RendererList.Add(this.rendererDst, false); + + this.rendererSrc = new BrushPreviewRenderer(this.RendererList); + this.rendererSrc.BrushLocation = GetStaticData().takeFrom; + this.rendererSrc.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + this.rendererSrc.Visible = (GetStaticData().takeFrom != Point.Empty); + this.RendererList.Add(this.rendererSrc, false); + + if (ActiveLayer != null) + { + switchedTo = true; + historyRects = new Vector(); + + if (GetStaticData().wr != null && GetStaticData().wr.IsAlive) + { + takeFromLayer = (BitmapLayer)GetStaticData().wr.Target; + } + else + { + takeFromLayer = null; + } + } + + AppEnvironment.PenInfoChanged += new EventHandler(Environment_PenInfoChanged); + } + + protected override void OnDeactivate() + { + if (!this.mouseUp) + { + StaticData sd = GetStaticData(); + Point lastXY = Point.Empty; + + if (sd != null) + { + lastXY = sd.lastMoved; + } + + OnMouseUp(new MouseEventArgs(MouseButtons.Left, 0, lastXY.X, lastXY.Y, 0)); + } + + AppEnvironment.PenInfoChanged -= new EventHandler(Environment_PenInfoChanged); + + this.RendererList.Remove(this.rendererDst); + this.rendererDst.Dispose(); + this.rendererDst = null; + + this.RendererList.Remove(this.rendererSrc); + this.rendererSrc.Dispose(); + this.rendererSrc = null; + + if (cursorMouseDown != null) + { + cursorMouseDown.Dispose(); + cursorMouseDown = null; + } + + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + if (cursorMouseDownSetSource != null) + { + cursorMouseDownSetSource.Dispose(); + cursorMouseDownSetSource = null; + } + + base.OnDeactivate(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (IsCtrlDown() && mouseUp) + { + Cursor = cursorMouseDownSetSource; + mouseDownSettingCloneSource = true; + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + // this isn't likely the best way to check to see if + // the CTRL key has been let up. If it's not, version + // 2.1 can address the discrepancy. + if (!IsCtrlDown() && mouseDownSettingCloneSource) + { + Cursor = cursorMouseUp; + mouseDownSettingCloneSource = false; + } + + base.OnKeyUp(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + mouseUp = true; + + if (!mouseDownSettingCloneSource) + { + Cursor = cursorMouseUp; + } + + if (IsMouseLeftDown(e)) + { + this.rendererDst.Visible = true; + + if (savedRegion != null) + { + //RestoreRegion(this.savedRegion); + ActiveLayer.Invalidate(this.savedRegion.GetBoundsInt()); + savedRegion.Dispose(); + savedRegion = null; + Update(); + } + + if (GetStaticData().takeFrom == Point.Empty || GetStaticData().lastMoved == Point.Empty) + { + return; + } + + if (historyRects.Count > 0) + { + PdnRegion saveMeRegion; + + Rectangle[] rectsRO; + int rectsROLength; + this.historyRects.GetArrayReadOnly(out rectsRO, out rectsROLength); + saveMeRegion = Utility.RectanglesToRegion(rectsRO, 0, rectsROLength); + + PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(saveMeRegion); + SaveRegion(simplifiedRegion, simplifiedRegion.GetBoundsInt()); + + historyRects = new Vector(); + + HistoryMemento ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, ActiveLayerIndex, + simplifiedRegion, this.ScratchSurface); + + HistoryStack.PushNewMemento(ha); + this.ClearSavedMemory(); + } + } + } + + private unsafe void DrawACircle(PointF pt, Surface srfSrc, Surface srfDst, Point difference, Rectangle rect) + { + float bw = AppEnvironment.PenInfo.Width / 2; + float envAlpha = AppEnvironment.PrimaryColor.A / 255.0f; + + rect.Intersect(new Rectangle(difference, srfSrc.Size)); + rect.Intersect(srfDst.Bounds); + + if (rect.Width == 0 || rect.Height == 0) + { + return; + } + + // envAlpha = envAlpha^4 + envAlpha *= envAlpha; + envAlpha *= envAlpha; + + for (int y = rect.Top; y < rect.Bottom; y++) + { + ColorBgra *srcRow = srfSrc.GetRowAddressUnchecked(y - difference.Y); + ColorBgra *dstRow = srfDst.GetRowAddressUnchecked(y); + + for (int x = rect.Left; x < rect.Right; x++) + { + ColorBgra *srcPtr = unchecked(srcRow + x - difference.X); + ColorBgra *dstPtr = unchecked(dstRow + x); + float distFromRing = 0.5f + bw - Utility.Distance(pt, new PointF(x, y)); + + if (distFromRing > 0) + { + float alpha = antialiasing ? Utility.Clamp(distFromRing * envAlpha, 0, 1) : 1; + alpha *= srcPtr->A / 255.0f; + dstPtr->A = (byte)(255 - (255 - dstPtr->A) * (1 - alpha)); + + if (0 == (alpha + (1 - alpha) * dstPtr->A / 255)) + { + dstPtr->Bgra = 0; + } + else + { + dstPtr->R = (byte)((srcPtr->R * alpha + dstPtr->R * (1 - alpha) * dstPtr->A / 255) / (alpha + (1 - alpha) * dstPtr->A / 255)); + dstPtr->G = (byte)((srcPtr->G * alpha + dstPtr->G * (1 - alpha) * dstPtr->A / 255) / (alpha + (1 - alpha) * dstPtr->A / 255)); + dstPtr->B = (byte)((srcPtr->B * alpha + dstPtr->B * (1 - alpha) * dstPtr->A / 255) / (alpha + (1 - alpha) * dstPtr->A / 255)); + } + } + } + } + + rect.Inflate(1, 1); + Document.Invalidate(rect); + } + + private void DrawCloneLine(Point currentMouse, Point lastMoved, Point lastTakeFrom, Surface surfaceSource, Surface surfaceDest) + { + Rectangle[] rectSelRegions; + Rectangle rectBrushArea; + int penWidth = (int)AppEnvironment.PenInfo.Width; + int ceilingPenWidth = (int)Math.Ceiling((double)penWidth); + + if (mouseUp || switchedTo) + { + lastMoved = currentMouse; + lastTakeFrom = GetStaticData().takeFrom; + mouseUp = false; + switchedTo = false; + } + + Point difference = new Point(currentMouse.X - GetStaticData().takeFrom.X, currentMouse.Y - GetStaticData().takeFrom.Y); + Point direction = new Point(currentMouse.X - lastMoved.X, currentMouse.Y - lastMoved.Y); + float length = Utility.Magnitude(direction); + float bw = 1 + AppEnvironment.PenInfo.Width / 2; + + rectSelRegions = this.clipRegion.GetRegionScansReadOnlyInt(); + + Rectangle rect = Utility.PointsToRectangle(lastMoved, currentMouse); + rect.Inflate(penWidth / 2 + 1, penWidth / 2 + 1); + rect.Intersect(new Rectangle(difference, surfaceSource.Size)); + rect.Intersect(surfaceDest.Bounds); + + if (rect.Width == 0 || rect.Height == 0) + { + return; + } + + SaveRegion(null, rect); + historyRects.Add(rect); + + // Follow the line to draw the clone... line + float fInc; + + try + { + fInc = (float)Math.Sqrt(bw) / length; + } + + catch (DivideByZeroException) + { + // See bug #1796 + return; + } + + for (float f = 0; f < 1; f += fInc) + { + // Do intersects with each of the rectangles in a selection + foreach (Rectangle rectSel in rectSelRegions) + { + PointF p = new PointF(currentMouse.X * (1 - f) + f * lastMoved.X, + currentMouse.Y * (1 - f) + f * lastMoved.Y); + + rectBrushArea = new Rectangle((int)(p.X - bw), (int)(p.Y - bw), (int)(bw * 2 + 1), (int)(bw * 2 + 1)); + + Rectangle rectBrushArea2 = new Rectangle( + rectBrushArea.X - difference.X, + rectBrushArea.Y - difference.Y, + rectBrushArea.Width, + rectBrushArea.Height); + + if (rectBrushArea.IntersectsWith(rectSel)) + { + rectBrushArea.Intersect(rectSel); + SaveRegion(null, rectBrushArea); + SaveRegion(null, rectBrushArea2); + DrawACircle(p, surfaceSource, surfaceDest, difference, rectBrushArea); + } + } + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + this.rendererDst.BrushLocation = new Point(e.X, e.Y); + this.rendererDst.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + + if (!(ActiveLayer is BitmapLayer) || (takeFromLayer == null)) + { + return; + } + + if (GetStaticData().updateSrcPreview) + { + Point currentMouse = new Point(e.X, e.Y); + Point difference = new Point(currentMouse.X - GetStaticData().lastMoved.X, currentMouse.Y - GetStaticData().lastMoved.Y); + this.rendererSrc.BrushLocation = new Point(GetStaticData().takeFrom.X + difference.X, GetStaticData().takeFrom.Y + difference.Y);; + this.rendererSrc.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + } + + if (IsMouseLeftDown(e) && + (GetStaticData().takeFrom != Point.Empty) && + !IsCtrlDown()) + { + Point currentMouse = new Point(e.X, e.Y); + Point lastTakeFrom = Point.Empty; + + lastTakeFrom = GetStaticData().takeFrom; + if (GetStaticData().lastMoved != Point.Empty) + { + Point difference = new Point(currentMouse.X - GetStaticData().lastMoved.X, currentMouse.Y - GetStaticData().lastMoved.Y); + GetStaticData().takeFrom = new Point(GetStaticData().takeFrom.X + difference.X, GetStaticData().takeFrom.Y + difference.Y); + } + else + { + GetStaticData().lastMoved = currentMouse; + } + + int penWidth = (int)AppEnvironment.PenInfo.Width; + Rectangle rect; + + if (penWidth != 1) + { + rect = new Rectangle(new Point(GetStaticData().takeFrom.X - penWidth / 2, GetStaticData().takeFrom.Y - penWidth / 2), new Size(penWidth + 1, penWidth + 1)); + } + else + { + rect = new Rectangle(new Point(GetStaticData().takeFrom.X - penWidth, GetStaticData().takeFrom.Y - penWidth), new Size(1 + (2 * penWidth), 1 + (2 * penWidth))); + } + + Rectangle boundRect = new Rectangle(GetStaticData().takeFrom, new Size(1, 1)); + + // If the takeFrom area escapes the boundary + if (!ActiveLayer.Bounds.Contains(boundRect)) + { + GetStaticData().lastMoved = currentMouse; + lastTakeFrom = GetStaticData().takeFrom; + } + + if (this.savedRegion != null) + { + ActiveLayer.Invalidate(savedRegion.GetBoundsInt()); + this.savedRegion.Dispose(); + this.savedRegion = null; + } + + rect.Intersect(takeFromLayer.Surface.Bounds); + + if (rect.Width == 0 || rect.Height == 0) + { + return; + } + + this.savedRegion = new PdnRegion(rect); + SaveRegion(this.savedRegion, rect); + + // Draw that clone line + Surface takeFromSurface; + if (object.ReferenceEquals(takeFromLayer, ActiveLayer)) + { + takeFromSurface = this.ScratchSurface; + } + else + { + takeFromSurface = takeFromLayer.Surface; + } + + if (this.clipRegion == null) + { + this.clipRegion = Selection.CreateRegion(); + } + + DrawCloneLine(currentMouse, GetStaticData().lastMoved, lastTakeFrom, + takeFromSurface, ((BitmapLayer)ActiveLayer).Surface); + + this.rendererSrc.BrushLocation = GetStaticData().takeFrom; + + ActiveLayer.Invalidate(rect); + Update(); + + GetStaticData().lastMoved = currentMouse; + } + } + + protected override void OnSelectionChanged() + { + if (this.clipRegion != null) + { + this.clipRegion.Dispose(); + this.clipRegion = null; + } + + base.OnSelectionChanged(); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (!(ActiveLayer is BitmapLayer)) + { + return; + } + + Cursor = cursorMouseDown; + + if (IsMouseLeftDown(e)) + { + this.rendererDst.Visible = false; + + if (IsCtrlDown()) + { + GetStaticData().takeFrom = new Point(e.X, e.Y); + + this.rendererSrc.BrushLocation = new Point(e.X, e.Y); + this.rendererSrc.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + this.rendererSrc.Visible = true; + GetStaticData().updateSrcPreview = false; + + GetStaticData().wr = new WeakReference(((BitmapLayer)ActiveLayer)); + takeFromLayer = (BitmapLayer)(GetStaticData().wr.Target); + GetStaticData().lastMoved = Point.Empty; + ra = new RenderArgs(((BitmapLayer)ActiveLayer).Surface); + } + else + { + GetStaticData().updateSrcPreview = true; + + // Determine if there is something to work if, if there isn't return + if (GetStaticData().takeFrom == Point.Empty) + { + } + else if (!GetStaticData().wr.IsAlive || takeFromLayer == null) + { + GetStaticData().takeFrom = Point.Empty; + GetStaticData().lastMoved = Point.Empty; + } + // Make sure the layer is still there! + else if (takeFromLayer != null && !Document.Layers.Contains(takeFromLayer)) + { + GetStaticData().takeFrom = Point.Empty; + GetStaticData().lastMoved = Point.Empty; + } + else + { + this.antialiasing = AppEnvironment.AntiAliasing; + this.ra = new RenderArgs(((BitmapLayer)ActiveLayer).Surface); + this.ra.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + OnMouseMove(e); + } + } + } + } + + private void Environment_PenInfoChanged(object sender, EventArgs e) + { + this.rendererSrc.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + this.rendererDst.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + } + } +} diff --git a/src/tools/ColorPickerTool.cs b/src/tools/ColorPickerTool.cs new file mode 100644 index 0000000..d3e6dec --- /dev/null +++ b/src/tools/ColorPickerTool.cs @@ -0,0 +1,120 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class ColorPickerTool : Tool + { + private bool mouseDown; + private Cursor colorPickerToolCursor; + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (mouseDown) + { + PickColor(e); + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + mouseDown = true; + + PickColor(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + mouseDown = false; + + switch (AppEnvironment.ColorPickerClickBehavior) + { + case ColorPickerClickBehavior.NoToolSwitch: + break; + + case ColorPickerClickBehavior.SwitchToLastTool: + DocumentWorkspace.SetToolFromType(DocumentWorkspace.PreviousActiveToolType); + break; + + case ColorPickerClickBehavior.SwitchToPencilTool: + DocumentWorkspace.SetToolFromType(typeof(PencilTool)); + break; + + default: + throw new System.ComponentModel.InvalidEnumArgumentException(); + } + } + + private ColorBgra LiftColor(int x, int y) + { + ColorBgra newColor; + newColor = ((BitmapLayer)ActiveLayer).Surface[x, y]; + return newColor; + } + + private void PickColor(MouseEventArgs e) + { + if (!Document.Bounds.Contains(e.X, e.Y)) + { + return; + } + + ColorBgra color; + color = LiftColor(e.X, e.Y); + + if ((e.Button & MouseButtons.Left) == MouseButtons.Left) + { + this.AppEnvironment.PrimaryColor = color; + } + else if ((e.Button & MouseButtons.Right) == MouseButtons.Right) + { + this.AppEnvironment.SecondaryColor = color; + } + } + + protected override void OnActivate() + { + this.colorPickerToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.ColorPickerToolCursor.cur")); + this.Cursor = this.colorPickerToolCursor; + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (this.colorPickerToolCursor != null) + { + this.colorPickerToolCursor.Dispose(); + this.colorPickerToolCursor = null; + } + + base.OnDeactivate(); + } + + public ColorPickerTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.ColorPickerToolIcon.png"), + PdnResources.GetString("ColorPickerTool.Name"), + PdnResources.GetString("ColorPickerTool.HelpText"), + 'k', + true, + ToolBarConfigItems.ColorPickerBehavior) + { + // initialize any state information you need + mouseDown = false; + } + } +} \ No newline at end of file diff --git a/src/tools/EllipseSelectTool.cs b/src/tools/EllipseSelectTool.cs new file mode 100644 index 0000000..ca45e95 --- /dev/null +++ b/src/tools/EllipseSelectTool.cs @@ -0,0 +1,106 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class EllipseSelectTool + : SelectionTool + { + protected override List TrimShapePath(List tracePoints) + { + List array = new List(); + + if (tracePoints.Count > 0) + { + array.Add(tracePoints[0]); + + if (tracePoints.Count > 1) + { + array.Add(tracePoints[tracePoints.Count - 1]); + } + } + + return array; + } + + protected override List CreateShape(List tracePoints) + { + Point a = tracePoints[0]; + Point b = tracePoints[tracePoints.Count - 1]; + Point dir = new Point(b.X - a.X, b.Y - a.Y); + float len = (float)Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y); + + RectangleF rectF; + + if ((ModifierKeys & Keys.Shift) != 0) + { + PointF center = new PointF((float)(a.X + b.X) / 2.0f, (float)(a.Y + b.Y) / 2.0f); + float radius = len / 2; + rectF = Rectangle.Truncate(Utility.RectangleFromCenter(center, radius)); + } + else + { + rectF = Utility.PointsToRectangle(a, b); + } + + Rectangle rect = Utility.RoundRectangle(rectF); + PdnGraphicsPath path = new PdnGraphicsPath(); + path.AddEllipse(rect); + + // Avoid asymmetrical circles where the left or right side of the ellipse has a pixel jutting out + using (Matrix m = new Matrix()) + { + m.Reset(); + m.Translate(-0.5f, -0.5f, MatrixOrder.Append); + path.Transform(m); + } + + path.Flatten(Utility.IdentityMatrix, 0.1f); + + PointF[] pointsF = path.PathPoints; + path.Dispose(); + + return new List(pointsF); + } + + protected override void OnActivate() + { + SetCursors( + "Cursors.EllipseSelectToolCursor.cur", + "Cursors.EllipseSelectToolCursorMinus.cur", + "Cursors.EllipseSelectToolCursorPlus.cur", + "Cursors.EllipseSelectToolCursorMouseDown.cur"); + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + } + + public EllipseSelectTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.EllipseSelectToolIcon.png"), + PdnResources.GetString("EllipseSelectTool.Name"), + PdnResources.GetString("EllipseSelectTool.HelpText"), + 's', + ToolBarConfigItems.None) + { + } + } +} diff --git a/src/tools/EllipseTool.cs b/src/tools/EllipseTool.cs new file mode 100644 index 0000000..31f1232 --- /dev/null +++ b/src/tools/EllipseTool.cs @@ -0,0 +1,148 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class EllipseTool + : ShapeTool + { + private ImageResource ellipseToolIcon; + private string statusTextFormat = PdnResources.GetString("EllipseTool.StatusText.Format"); + private Cursor ellipseToolCursor; + + protected override List TrimShapePath(List points) + { + List array = new List(); + + if (points.Count > 0) + { + array.Add(points[0]); + + if (points.Count > 1) + { + array.Add(points[points.Count - 1]); + } + } + + return array; + } + + public override PixelOffsetMode GetPixelOffsetMode() + { + return PixelOffsetMode.None; + } + + protected override RectangleF[] GetOptimizedShapeOutlineRegion(PointF[] points, PdnGraphicsPath path) + { + return Utility.SimplifyTrace(path.PathPoints); + } + + protected override PdnGraphicsPath CreateShapePath(PointF[] points) + { + PointF a = points[0]; + PointF b = points[points.Length - 1]; + RectangleF rect; + + if ((ModifierKeys & Keys.Shift) != 0) + { + PointF dir = new PointF(b.X - a.X, b.Y - a.Y); + float len = (float)Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y); + PointF center = new PointF((a.X + b.X) / 2.0f, (a.Y + b.Y) / 2.0f); + float radius = len / 2.0f; + rect = Utility.RectangleFromCenter(center, radius); + } + else + { + rect = Utility.PointsToRectangle(a, b); + } + + if (rect.Width == 0 || rect.Height == 0) + { + return null; + } + + PdnGraphicsPath path = new PdnGraphicsPath(); + path.AddEllipse(rect); + path.Flatten(Utility.IdentityMatrix, 0.10f); + + MeasurementUnit units = AppWorkspace.Units; + + double widthPhysical = Math.Abs(Document.PixelToPhysicalX(rect.Width, units)); + double heightPhysical = Math.Abs(Document.PixelToPhysicalY(rect.Height, units)); + double areaPhysical = Math.PI * (widthPhysical / 2.0) * (heightPhysical / 2.0); + + string numberFormat; + string unitsAbbreviation; + + if (units != MeasurementUnit.Pixel) + { + string unitsAbbreviationName = "MeasurementUnit." + units.ToString() + ".Abbreviation"; + unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName); + numberFormat = "F2"; + } + else + { + unitsAbbreviation = string.Empty; + numberFormat = "F0"; + } + + string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural"); + + string statusText = string.Format( + this.statusTextFormat, + widthPhysical.ToString(numberFormat), + unitsAbbreviation, + heightPhysical.ToString(numberFormat), + unitsAbbreviation, + areaPhysical.ToString(numberFormat), + unitsString); + + this.SetStatus(this.ellipseToolIcon, statusText); + return path; + } + + protected override void OnActivate() + { + this.ellipseToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.EllipseToolCursor.cur")); + this.ellipseToolIcon = this.Image; + this.Cursor = this.ellipseToolCursor; + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (this.ellipseToolCursor != null) + { + this.ellipseToolCursor.Dispose(); + this.ellipseToolCursor = null; + } + + base.OnDeactivate(); + } + + public EllipseTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.EllipseToolIcon.png"), + PdnResources.GetString("EllipseTool.Name"), + PdnResources.GetString("EllipseTool.HelpText")) + { + } + } +} + diff --git a/src/tools/EraserTool.cs b/src/tools/EraserTool.cs new file mode 100644 index 0000000..4acadbb --- /dev/null +++ b/src/tools/EraserTool.cs @@ -0,0 +1,261 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Diagnostics; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class EraserTool + : Tool + { + private bool mouseDown; + private MouseButtons mouseButton; + private List savedRects; + private Point lastMouseXY; + private RenderArgs renderArgs; + private BitmapLayer bitmapLayer; + private Cursor cursorMouseDown; + private Cursor cursorMouseUp; + private BrushPreviewRenderer previewRenderer; + + protected override void OnMouseEnter() + { + this.previewRenderer.Visible = true; + base.OnMouseEnter(); + } + + protected override void OnMouseLeave() + { + this.previewRenderer.Visible = false; + base.OnMouseLeave(); + } + + protected override void OnActivate() + { + base.OnActivate(); + + // cursor-transitions + this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.EraserToolCursor.cur")); + this.cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.EraserToolCursorMouseDown.cur")); + this.Cursor = cursorMouseUp; + + this.savedRects = new List(); + + if (ActiveLayer != null) + { + bitmapLayer = (BitmapLayer)ActiveLayer; + renderArgs = new RenderArgs(bitmapLayer.Surface); + } + else + { + bitmapLayer = null; + renderArgs = null; + } + + this.previewRenderer = new BrushPreviewRenderer(this.RendererList); + this.RendererList.Add(this.previewRenderer, false); + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + if (cursorMouseDown != null) + { + cursorMouseDown.Dispose(); + cursorMouseDown = null; + } + + if (mouseDown) + { + OnMouseUp(new MouseEventArgs(mouseButton, 0, lastMouseXY.X, lastMouseXY.Y, 0)); + } + + this.RendererList.Remove(this.previewRenderer); + this.previewRenderer.Dispose(); + this.previewRenderer = null; + + this.savedRects.Clear(); + + if (renderArgs != null) + { + renderArgs.Dispose(); + renderArgs = null; + } + + bitmapLayer = null; + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown (e); + + this.Cursor = this.cursorMouseDown; + + if (((e.Button & MouseButtons.Left) == MouseButtons.Left) || + ((e.Button & MouseButtons.Right) == MouseButtons.Right)) + { + this.previewRenderer.Visible = false; + + mouseDown = true; + mouseButton = e.Button; + + lastMouseXY.X = e.X; + lastMouseXY.Y = e.Y; + + PdnRegion clipRegion = Selection.CreateRegion(); + renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace); + clipRegion.Dispose(); + + OnMouseMove(e); + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp (e); + + Cursor = cursorMouseUp; + + if (mouseDown) + { + OnMouseMove(e); + this.previewRenderer.Visible = true; + mouseDown = false; + + if (this.savedRects.Count > 0) + { + Rectangle[] savedScans = this.savedRects.ToArray(); + PdnRegion saveMeRegion = Utility.RectanglesToRegion(savedScans); + + HistoryMemento ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, + ActiveLayerIndex, saveMeRegion, this.ScratchSurface); + + HistoryStack.PushNewMemento(ha); + saveMeRegion.Dispose(); + this.savedRects.Clear(); + ClearSavedMemory(); + } + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (mouseDown && ((e.Button & mouseButton) != MouseButtons.None)) + { + int alpha; + + if (e.Button == MouseButtons.Left) + { + alpha = AppEnvironment.PrimaryColor.A; + } + else + { + alpha = AppEnvironment.SecondaryColor.A; + } + + Pen pen = AppEnvironment.PenInfo.CreatePen(AppEnvironment.BrushInfo, + Color.FromArgb(alpha, 0, 0, 0), Color.FromArgb(alpha, 0, 0, 0)); + + Point a = lastMouseXY; + Point b = new Point(e.X, e.Y); + + Rectangle saveRect = Utility.PointsToRectangle(a, b); + + saveRect.Inflate((int)Math.Ceiling(pen.Width), (int)Math.Ceiling(pen.Width)); + + if (renderArgs.Graphics.SmoothingMode == SmoothingMode.AntiAlias) + { + saveRect.Inflate(1, 1); + } + + saveRect.Intersect(ActiveLayer.Bounds); + + // drawing outside of the canvas is a no-op, so don't do anything in that case! + // also make sure we're within the clip region + if (saveRect.Width > 0 && saveRect.Height > 0 && renderArgs.Graphics.IsVisible(saveRect)) + { + SaveRegion(null, saveRect); + this.savedRects.Add(saveRect); + + if (AppEnvironment.AntiAliasing) + { + renderArgs.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + } + else + { + renderArgs.Graphics.SmoothingMode = SmoothingMode.None; + } + + new UnaryPixelOps.InvertWithAlpha().Apply(renderArgs.Surface, saveRect); + renderArgs.Graphics.CompositingMode = CompositingMode.SourceOver; + + if (pen.Width > 1) + { + renderArgs.Graphics.PixelOffsetMode = PixelOffsetMode.Half; + } + else + { + renderArgs.Graphics.PixelOffsetMode = PixelOffsetMode.None; + } + + pen.EndCap = LineCap.Round; + pen.StartCap = LineCap.Round; + renderArgs.Graphics.DrawLine(pen, a, b); + renderArgs.Graphics.FillEllipse(pen.Brush, a.X - pen.Width / 2.0f, a.Y - pen.Width / 2.0f, pen.Width, pen.Width); + + new UnaryPixelOps.InvertWithAlpha().Apply(renderArgs.Surface, saveRect); + + new BinaryPixelOps.SetColorChannels().Apply(renderArgs.Surface, saveRect.Location, + ScratchSurface, saveRect.Location, saveRect.Size); + + bitmapLayer.Invalidate(saveRect); + Update(); + } + + lastMouseXY = b; + pen.Dispose(); + } + else + { + this.previewRenderer.BrushLocation = new Point(e.X, e.Y); + this.previewRenderer.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + } + } + + public EraserTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.EraserToolIcon.png"), + PdnResources.GetString("EraserTool.Name"), + PdnResources.GetString("EraserTool.HelpText"), //"Click and drag to erase a portion of the image", + 'e', + false, + ToolBarConfigItems.Pen | ToolBarConfigItems.Antialiasing) + { + } + } +} diff --git a/src/tools/FloodToolBase.cs b/src/tools/FloodToolBase.cs new file mode 100644 index 0000000..91722c9 --- /dev/null +++ b/src/tools/FloodToolBase.cs @@ -0,0 +1,362 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.SystemLayer; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal abstract class FloodToolBase + : Tool + { + private bool contiguous; + + private bool clipToSelection = true; + protected bool ClipToSelection + { + get + { + return clipToSelection; + } + + set + { + clipToSelection = value; + } + } + + public FloodToolBase(DocumentWorkspace documentWorkspace, ImageResource toolBarImage, string name, + string helpText, char hotKey, bool skipIfActiveOnHotKey, ToolBarConfigItems toolBarConfigItems) + : base(documentWorkspace, toolBarImage, name, helpText, hotKey, skipIfActiveOnHotKey, + ToolBarConfigItems.FloodMode | ToolBarConfigItems.Tolerance | toolBarConfigItems) + { + } + + private static bool CheckColor(ColorBgra a, ColorBgra b, int tolerance) + { + int sum = 0; + int diff; + + diff = a.R - b.R; + sum += (1 + diff * diff) * a.A / 256; + + diff = a.G - b.G; + sum += (1 + diff * diff) * a.A / 256; + + diff = a.B - b.B; + sum += (1 + diff * diff) * a.A / 256; + + diff = a.A - b.A; + sum += diff * diff; + + return (sum <= tolerance * tolerance * 4); + } + + public unsafe static void FillStencilByColor(Surface surface, IBitVector2D stencil, ColorBgra cmp, int tolerance, + out Rectangle boundingBox, PdnRegion limitRegion, bool limitToSelection) + { + int top = int.MaxValue; + int bottom = int.MinValue; + int left = int.MaxValue; + int right = int.MinValue; + Rectangle[] scans; + + stencil.Clear(false); + + if (limitToSelection) + { + using (PdnRegion excluded = new PdnRegion(new Rectangle(0, 0, stencil.Width, stencil.Height))) + { + excluded.Xor(limitRegion); + scans = excluded.GetRegionScansReadOnlyInt(); + } + } + else + { + scans = new Rectangle[0]; + } + + foreach (Rectangle rect in scans) + { + stencil.Set(rect, true); + } + + for (int y = 0; y < surface.Height; ++y) + { + bool foundPixelInRow = false; + ColorBgra *ptr = surface.GetRowAddressUnchecked(y); + + for (int x = 0; x < surface.Width; ++x) + { + if (CheckColor(cmp, *ptr, tolerance)) + { + stencil.SetUnchecked(x, y, true); + + if (x < left) + { + left = x; + } + + if (x > right) + { + right = x; + } + + foundPixelInRow = true; + } + + ++ptr; + } + + if (foundPixelInRow) + { + if (y < top) + { + top = y; + } + + if (y >= bottom) + { + bottom = y; + } + } + } + + foreach (Rectangle rect in scans) + { + stencil.Set(rect, false); + } + + boundingBox = Rectangle.FromLTRB(left, top, right + 1, bottom + 1); + } + + public unsafe static void FillStencilFromPoint(Surface surface, IBitVector2D stencil, Point start, + int tolerance, out Rectangle boundingBox, PdnRegion limitRegion, bool limitToSelection) + { + ColorBgra cmp = surface[start]; + int top = int.MaxValue; + int bottom = int.MinValue; + int left = int.MaxValue; + int right = int.MinValue; + Rectangle[] scans; + + stencil.Clear(false); + + if (limitToSelection) + { + using (PdnRegion excluded = new PdnRegion(new Rectangle(0, 0, stencil.Width, stencil.Height))) + { + excluded.Xor(limitRegion); + scans = excluded.GetRegionScansReadOnlyInt(); + } + } + else + { + scans = new Rectangle[0]; + } + + foreach (Rectangle rect in scans) + { + stencil.Set(rect, true); + } + + Queue queue = new Queue(16); + queue.Enqueue(start); + + while (queue.Count > 0) + { + Point pt = queue.Dequeue(); + + ColorBgra* rowPtr = surface.GetRowAddressUnchecked(pt.Y); + int localLeft = pt.X - 1; + int localRight = pt.X; + + while (localLeft >= 0 && + !stencil.GetUnchecked(localLeft, pt.Y) && + CheckColor(cmp, rowPtr[localLeft], tolerance)) + { + stencil.SetUnchecked(localLeft, pt.Y, true); + --localLeft; + } + + while (localRight < surface.Width && + !stencil.GetUnchecked(localRight, pt.Y) && + CheckColor(cmp, rowPtr[localRight], tolerance)) + { + stencil.SetUnchecked(localRight, pt.Y, true); + ++localRight; + } + + ++localLeft; + --localRight; + + if (pt.Y > 0) + { + int sleft = localLeft; + int sright = localLeft; + ColorBgra* rowPtrUp = surface.GetRowAddressUnchecked(pt.Y - 1); + + for (int sx = localLeft; sx <= localRight; ++sx) + { + if (!stencil.GetUnchecked(sx, pt.Y - 1) && + CheckColor(cmp, rowPtrUp[sx], tolerance)) + { + ++sright; + } + else + { + if (sright - sleft > 0) + { + queue.Enqueue(new Point(sleft, pt.Y - 1)); + } + + ++sright; + sleft = sright; + } + } + + if (sright - sleft > 0) + { + queue.Enqueue(new Point(sleft, pt.Y - 1)); + } + } + + if (pt.Y < surface.Height - 1) + { + int sleft = localLeft; + int sright = localLeft; + ColorBgra* rowPtrDown = surface.GetRowAddressUnchecked(pt.Y + 1); + + for (int sx = localLeft; sx <= localRight; ++sx) + { + if (!stencil.GetUnchecked(sx, pt.Y + 1) && + CheckColor(cmp, rowPtrDown[sx], tolerance)) + { + ++sright; + } + else + { + if (sright - sleft > 0) + { + queue.Enqueue(new Point(sleft, pt.Y + 1)); + } + + ++sright; + sleft = sright; + } + } + + if (sright - sleft > 0) + { + queue.Enqueue(new Point(sleft, pt.Y + 1)); + } + } + + if (localLeft < left) + { + left = localLeft; + } + + if (localRight > right) + { + right = localRight; + } + + if (pt.Y < top) + { + top = pt.Y; + } + + if (pt.Y > bottom) + { + bottom = pt.Y; + } + } + + foreach (Rectangle rect in scans) + { + stencil.Set(rect, false); + } + + boundingBox = Rectangle.FromLTRB(left, top, right + 1, bottom + 1); + } + + protected abstract void OnFillRegionComputed(Point[][] polygonSet); + + protected override void OnMouseDown(MouseEventArgs e) + { + Point pos = new Point(e.X, e.Y); + + switch (AppEnvironment.FloodMode) + { + case FloodMode.Local: + this.contiguous = true; + break; + + case FloodMode.Global: + this.contiguous = false; + break; + + default: + throw new InvalidEnumArgumentException(); + } + + if ((ModifierKeys & Keys.Shift) != 0) + { + this.contiguous = !this.contiguous; + } + + if (Document.Bounds.Contains(pos)) + { + base.OnMouseDown(e); + + PdnRegion currentRegion = Selection.CreateRegion(); + + // See if the mouse click is valid + if (!currentRegion.IsVisible(pos) && clipToSelection) + { + currentRegion.Dispose(); + currentRegion = null; + return; + } + + // Set the current surface, color picked and color to draw + Surface surface = ((BitmapLayer)ActiveLayer).Surface; + + IBitVector2D stencilBuffer = new BitVector2DSurfaceAdapter(this.ScratchSurface); + + Rectangle boundingBox; + int tolerance = (int)(AppEnvironment.Tolerance * AppEnvironment.Tolerance * 256); + + if (contiguous) + { + // FloodMode.Local + FillStencilFromPoint(surface, stencilBuffer, pos, tolerance, out boundingBox, currentRegion, clipToSelection); + } + else + { + // FloodMode.Global + FillStencilByColor(surface, stencilBuffer, surface[pos], tolerance, out boundingBox, currentRegion, clipToSelection); + } + + Point[][] polygonSet = PdnGraphicsPath.PolygonSetFromStencil(stencilBuffer, boundingBox, 0, 0); + OnFillRegionComputed(polygonSet); + } + + base.OnMouseDown(e); + } + } +} diff --git a/src/tools/FreeformShapeTool.cs b/src/tools/FreeformShapeTool.cs new file mode 100644 index 0000000..bae265c --- /dev/null +++ b/src/tools/FreeformShapeTool.cs @@ -0,0 +1,89 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class FreeformShapeTool + : ShapeTool + { + private Cursor freeformShapeToolCursor; + + protected override RectangleF[] GetOptimizedShapeOutlineRegion(PointF[] points, PdnGraphicsPath path) + { + return Utility.SimplifyTrace(path.PathPoints); + } + + protected override PdnGraphicsPath CreateShapePath(PointF[] points) + { + // make sure we don't screw them up + if (points.Length < 2) + { + return null; + } + + // make sure the shape has an area of at least 1 + // we can determine this by making sure that all the Points in points are not all the same + bool allTheSame = true; + foreach (PointF pt in points) + { + if (pt != points[0]) + { + allTheSame = false; + break; + } + } + + if (allTheSame) + { + return null; + } + + PdnGraphicsPath path = new PdnGraphicsPath(); + path.AddLines(points); + path.AddLine(points[points.Length - 1], points[0]); + path.CloseAllFigures(); + return path; + } + + protected override void OnActivate() + { + this.freeformShapeToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.FreeformShapeToolCursor.cur")); + this.Cursor = this.freeformShapeToolCursor; + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (this.freeformShapeToolCursor != null) + { + this.freeformShapeToolCursor.Dispose(); + this.freeformShapeToolCursor = null; + } + + base.OnDeactivate(); + } + + public FreeformShapeTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.FreeformShapeToolIcon.png"), + PdnResources.GetString("FreeformShapeTool.Name"), + PdnResources.GetString("FreeformShapeTool.HelpText")) + { + } + } +} diff --git a/src/tools/GradientTool.cs b/src/tools/GradientTool.cs new file mode 100644 index 0000000..b174354 --- /dev/null +++ b/src/tools/GradientTool.cs @@ -0,0 +1,751 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal sealed class GradientTool + : Tool + { + public static string StaticName + { + get + { + return PdnResources.GetString("GradientTool.Name"); + } + } + + public static ImageResource StaticImage + { + get + { + return PdnResources.GetImageResource("Icons.GradientToolIcon.png"); + } + } + + private Cursor toolCursor; + private Cursor toolMouseDownCursor; + private ImageResource toolIcon; + + private MoveNubRenderer[] moveNubs; + private MoveNubRenderer startNub; + private MoveNubRenderer endNub; + private MoveNubRenderer mouseNub; // which nub the mouse is manipulating, null for neither + private MouseButtons mouseButton = MouseButtons.None; + private PointF startPoint; + private PointF endPoint; + + private string helpTextInitial = PdnResources.GetString("GradientTool.HelpText"); + private string helpTextWhileAdjustingFormat = PdnResources.GetString("GradientTool.HelpText.WhileAdjusting.Format"); + private string helpTextAdjustable = PdnResources.GetString("GradientTool.HelpText.Adjustable"); + + private bool shouldMoveBothNubs = false; + private bool shouldConstrain = false; + private bool shouldSwapColors = false; + private bool gradientActive = false; // we are drawing or adjusting a gradient + + private CompoundHistoryMemento historyMemento = null; + + private void ConstrainPoints(PointF a, ref PointF b) + { + PointF dir = new PointF(b.X - a.X, b.Y - a.Y); + double theta = Math.Atan2(dir.Y, dir.X); + double len = Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y); + + theta = Math.Round(12 * theta / Math.PI) * Math.PI / 12; + b = new PointF((float)(a.X + len * Math.Cos(theta)), (float)(a.Y + len * Math.Sin(theta))); + } + + protected override void OnPulse() + { + if (this.gradientActive && this.moveNubs != null) + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + if (!this.moveNubs[i].Visible) + { + continue; + } + + // Oscillate between 25% and 100% alpha over a period of 2 seconds + // Alpha value of 100% is sustained for a large duration of this period + const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per second + long tick = (DateTime.Now.Ticks % period) + (i * (period / this.moveNubs.Length)); ; + double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI)); + // sin is [-1, +1] + + sin = Math.Min(0.5, sin); + // sin is [-1, +0.5] + + sin += 1.0; + // sin is [0, 1.5] + + sin /= 2.0; + // sin is [0, 0.75] + + sin += 0.25; + // sin is [0.25, 1] + + int newAlpha = (int)(sin * 255.0); + int clampedAlpha = Utility.Clamp(newAlpha, 0, 255); + this.moveNubs[i].Alpha = clampedAlpha; + } + } + + base.OnPulse(); + } + + private bool controlKeyDown = false; + private DateTime controlKeyDownTime = DateTime.MinValue; + private readonly TimeSpan controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400); + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.ControlKey: + if (!this.controlKeyDown) + { + this.controlKeyDown = true; + this.controlKeyDownTime = DateTime.Now; + } + break; + + case Keys.ShiftKey: + bool oldShouldConstrain = this.shouldConstrain; + this.shouldConstrain = true; + + if (this.gradientActive && + this.mouseButton != MouseButtons.None && + !oldShouldConstrain) + { + RenderGradient(); + } + + break; + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.ControlKey: + TimeSpan heldDuration = (DateTime.Now - this.controlKeyDownTime); + + // If the user taps Ctrl, then we should toggle the visiblity of the moveNubs + if (heldDuration < this.controlKeyDownThreshold) + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i].Visible = this.gradientActive && !this.moveNubs[i].Visible; + } + } + + this.controlKeyDown = false; + break; + + case Keys.ShiftKey: + this.shouldConstrain = false; + + if (this.gradientActive && + this.mouseButton != MouseButtons.None) + { + RenderGradient(); + } + break; + } + + base.OnKeyUp(e); + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + if (this.gradientActive) + { + switch (e.KeyChar) + { + case '\r': // Enter + e.Handled = true; + CommitGradient(); + break; + + case (char)27: // Escape + e.Handled = true; + CommitGradient(); + HistoryStack.StepBackward(); + break; + } + } + + base.OnKeyPress(e); + } + + + private sealed class RenderContext + { + public Surface surface; + public Rectangle[] rois; + public GradientRenderer renderer; + + public void Render(object cpuIndexObj) + { + int cpuIndex = (int)cpuIndexObj; + int start = (this.rois.Length * cpuIndex) / Processor.LogicalCpuCount; + int end = (this.rois.Length * (cpuIndex + 1)) / Processor.LogicalCpuCount; + + renderer.Render(this.surface, this.rois, start, end - start); + } + } + + private void RenderGradient(Surface surface, PdnRegion clipRegion, CompositingMode compositingMode, + PointF startPointF, ColorBgra startColor, PointF endPointF, ColorBgra endColor) + { + GradientRenderer gr = AppEnvironment.GradientInfo.CreateGradientRenderer(); + + gr.StartColor = startColor; + gr.EndColor = endColor; + gr.StartPoint = startPointF; + gr.EndPoint = endPointF; + gr.AlphaBlending = (compositingMode == CompositingMode.SourceOver); + gr.BeforeRender(); + + Rectangle[] oldRois = clipRegion.GetRegionScansReadOnlyInt(); + Rectangle[] newRois; + + if (oldRois.Length == 1) + { + newRois = new Rectangle[Processor.LogicalCpuCount]; + Utility.SplitRectangle(oldRois[0], newRois); + } + else + { + newRois = oldRois; + } + + RenderContext rc = new RenderContext(); + rc.surface = surface; + rc.rois = newRois; + rc.renderer = gr; + + WaitCallback wc = new WaitCallback(rc.Render); + + for (int i = 0; i < Processor.LogicalCpuCount; ++i) + { + if (i == Processor.LogicalCpuCount - 1) + { + wc(BoxedConstants.GetInt32(i)); + } + else + { + PaintDotNet.Threading.ThreadPool.Global.QueueUserWorkItem(wc, BoxedConstants.GetInt32(i)); + } + } + + PaintDotNet.Threading.ThreadPool.Global.Drain(); + } + + private void RenderGradient() + { + ColorBgra startColor = AppEnvironment.PrimaryColor; + ColorBgra endColor = AppEnvironment.SecondaryColor; + + if (this.shouldSwapColors) + { + if (AppEnvironment.GradientInfo.AlphaOnly) + { + // In transparency mode, the color values don't matter. We just need to reverse + // and invert the alpha values. + byte startAlpha = startColor.A; + startColor.A = (byte)(255 - endColor.A); + endColor.A = (byte)(255 - startAlpha); + } + else + { + Utility.Swap(ref startColor, ref endColor); + } + } + + PointF startPointF = this.startPoint; + PointF endPointF = this.endPoint; + + if (this.shouldConstrain) + { + if (this.mouseNub == this.startNub) + { + ConstrainPoints(endPointF, ref startPointF); + } + else + { + ConstrainPoints(startPointF, ref endPointF); + } + } + + RestoreSavedRegion(); + + Surface surface = ((BitmapLayer)DocumentWorkspace.ActiveLayer).Surface; + PdnRegion clipRegion = DocumentWorkspace.Selection.CreateRegion(); + + SaveRegion(clipRegion, clipRegion.GetBoundsInt()); + + RenderGradient(surface, clipRegion, AppEnvironment.GetCompositingMode(), startPointF, startColor, endPointF, endColor); + + using (PdnRegion simplified = Utility.SimplifyAndInflateRegion(clipRegion, Utility.DefaultSimplificationFactor, 0)) + { + DocumentWorkspace.ActiveLayer.Invalidate(simplified); + } + + clipRegion.Dispose(); + + // Set up status bar text + double angle = -180.0 * Math.Atan2(endPointF.Y - startPointF.Y, endPointF.X - startPointF.X) / Math.PI; + MeasurementUnit units = AppWorkspace.Units; + double offsetXPhysical = Document.PixelToPhysicalX(endPointF.X - startPointF.X, units); + double offsetYPhysical = Document.PixelToPhysicalY(endPointF.Y - startPointF.Y, units); + double offsetLengthPhysical = Math.Sqrt(offsetXPhysical * offsetXPhysical + offsetYPhysical * offsetYPhysical); + + string numberFormat; + string unitsAbbreviation; + + if (units != MeasurementUnit.Pixel) + { + string unitsAbbreviationName = "MeasurementUnit." + units.ToString() + ".Abbreviation"; + unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName); + numberFormat = "F2"; + } + else + { + unitsAbbreviation = string.Empty; + numberFormat = "F0"; + } + + string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural"); + + string statusText = string.Format( + this.helpTextWhileAdjustingFormat, + offsetXPhysical.ToString(numberFormat), + unitsAbbreviation, + offsetYPhysical.ToString(numberFormat), + unitsAbbreviation, + offsetLengthPhysical.ToString("F2"), + unitsString, + angle.ToString("F2")); + + SetStatus(this.toolIcon, statusText); + + // Make sure everything is on screen. + Update(); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + PointF mousePt = new PointF(e.X, e.Y); + MoveNubRenderer mouseCursorNub = PointToNub(mousePt); + + if (this.mouseButton != MouseButtons.None) + { + this.shouldMoveBothNubs = !this.shouldMoveBothNubs; + } + else + { + bool startNewGradient = true; + this.mouseButton = e.Button; + + if (!this.gradientActive) + { + this.shouldSwapColors = (this.mouseButton == MouseButtons.Right); + } + else + { + this.shouldMoveBothNubs = false; + + // We are already in the process of drawing or adjusting a gradient. + // Determine if they clicked to drag one of the nubs for adjusting. + + if (mouseCursorNub == null) + { + // No. Commit the old gradient and begin a new one. + CommitGradient(); + startNewGradient = true; + this.shouldSwapColors = (this.mouseButton == MouseButtons.Right); + } + else + { + // Yes. Continue adjusting the old gradient. + Cursor = this.handCursorMouseDown; + + this.mouseNub = mouseCursorNub; + this.mouseNub.Location = mousePt; + + if (this.mouseNub == this.startNub) + { + this.startPoint = mousePt; + } + else + { + this.endPoint = mousePt; + } + + if (this.mouseButton == MouseButtons.Right) + { + this.shouldSwapColors = !this.shouldSwapColors; + } + + RenderGradient(); + startNewGradient = false; + } + } + + if (startNewGradient) + { + // Brand new gradient. Set everything up. + this.startPoint = mousePt; + this.startNub.Location = mousePt; + this.startNub.Visible = true; + + this.endNub.Location = mousePt; + this.endNub.Visible = true; + this.endPoint = mousePt; + + this.mouseNub = mouseCursorNub; + + Cursor = this.toolMouseDownCursor; + + this.gradientActive = true; + + ClearSavedRegion(); + RenderGradient(); + + this.historyMemento = new CompoundHistoryMemento(StaticName, StaticImage); + HistoryStack.PushNewMemento(this.historyMemento); // this makes it so they can push Esc to undo + } + } + + base.OnMouseDown(e); + } + + private MoveNubRenderer PointToNub(PointF mousePtF) + { + float startDistance = Utility.Distance(mousePtF, this.startNub.Location); + float endDistance = Utility.Distance(mousePtF, this.endNub.Location); + + if (this.startNub.Visible && + startDistance < endDistance && + this.startNub.IsPointTouching(mousePtF, true)) + { + return this.startNub; + } + else if (this.endNub.Visible && + this.endNub.IsPointTouching(mousePtF, true)) + { + return this.endNub; + } + else + { + return null; + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + PointF mousePtF = new PointF(e.X, e.Y); + MoveNubRenderer mouseCursorNub = PointToNub(mousePtF); + + if (this.mouseButton == MouseButtons.None) + { + // No mouse button dragging is being tracked. + this.mouseNub = mouseCursorNub; + + if (this.mouseNub == this.startNub || this.mouseNub == this.endNub) + { + Cursor = this.handCursor; + } + else + { + Cursor = this.toolCursor; + } + } + else + { + if (this.mouseNub == this.startNub) + { + // Dragging the start nub + if (this.shouldConstrain && !this.shouldMoveBothNubs) + { + ConstrainPoints(this.endPoint, ref mousePtF); + } + + this.startNub.Location = mousePtF; + + SizeF delta = new SizeF( + this.startNub.Location.X - this.startPoint.X, + this.startNub.Location.Y - this.startPoint.Y); + + this.startPoint = mousePtF; + + if (this.shouldMoveBothNubs) + { + this.endNub.Location += delta; + this.endPoint += delta; + } + } + else if (this.mouseNub == this.endNub) + { + // Dragging the ending nub + if (this.shouldConstrain && !this.shouldMoveBothNubs) + { + ConstrainPoints(this.startPoint, ref mousePtF); + } + + this.endNub.Location = mousePtF; + + SizeF delta = new SizeF( + this.endNub.Location.X - this.endPoint.X, + this.endNub.Location.Y - this.endPoint.Y); + + this.endPoint = mousePtF; + + if (this.shouldMoveBothNubs) + { + this.startNub.Location += delta; + this.startPoint += delta; + } + } + else + { + // Initial drawing + if (this.shouldMoveBothNubs) + { + SizeF delta = new SizeF( + this.endNub.Location.X - mousePtF.X, + this.endNub.Location.Y - mousePtF.Y); + + this.startNub.Location -= delta; + this.startPoint -= delta; + } + else if (this.shouldConstrain) + { + ConstrainPoints(this.startPoint, ref mousePtF); + } + + this.endNub.Location = mousePtF; + this.endPoint = mousePtF; + } + + RenderGradient(); + } + + base.OnMouseMove(e); + } + + private void CommitGradient() + { + if (!this.gradientActive) + { + throw new InvalidOperationException("CommitGradient() called when a gradient was not active"); + } + + RenderGradient(); + + using (PdnRegion clipRegion = DocumentWorkspace.Selection.CreateRegion()) + { + BitmapHistoryMemento bhm = new BitmapHistoryMemento( + StaticName, + StaticImage, + DocumentWorkspace, + DocumentWorkspace.ActiveLayerIndex, + clipRegion, + this.ScratchSurface); + + this.historyMemento.PushNewAction(bhm); + + // We assume this.historyMemento has already been pushed on to the HistoryStack + + this.historyMemento = null; + } + + this.startNub.Visible = false; + this.endNub.Visible = false; + + ClearSavedRegion(); + ClearSavedMemory(); + this.gradientActive = false; + + SetStatus(this.toolIcon, this.helpTextInitial); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + PointF mousePt = new PointF(e.X, e.Y); + + if (!this.gradientActive) + { + // do nothing + } + else if (e.Button != this.mouseButton) + { + this.shouldMoveBothNubs = !this.shouldMoveBothNubs; + } + else + { + if (this.mouseNub == this.startNub) + { + // We were adjusting the start nub. + if (this.shouldConstrain) + { + ConstrainPoints(this.endPoint, ref mousePt); + } + + this.startNub.Location = mousePt; + this.startPoint = mousePt; + } + else if (this.mouseNub == this.endNub) + { + // We were adjusting the ending nub. + if (this.shouldConstrain) + { + ConstrainPoints(this.startPoint, ref mousePt); + } + + this.endNub.Location = mousePt; + this.endPoint = mousePt; + } + else + { + // We were drawing a brand new gradient. + if (this.shouldConstrain) + { + ConstrainPoints(this.startPoint, ref mousePt); + } + + this.endNub.Location = mousePt; + this.endPoint = mousePt; + } + + // In any event, make sure the nubs are visible and other state adjusted accordingly. + this.startNub.Visible = true; + this.endNub.Visible = true; + this.mouseButton = MouseButtons.None; + this.gradientActive = true; + SetStatus(this.toolIcon, this.helpTextAdjustable); + } + + base.OnMouseUp(e); + } + + private void RenderBecauseOfEvent(object sender, EventArgs e) + { + if (this.gradientActive) + { + RenderGradient(); + } + } + + protected override void OnActivate() + { + this.toolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursor.cur")); + this.toolMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur")); + this.Cursor = this.toolCursor; + this.toolIcon = this.Image; + + this.startNub = new MoveNubRenderer(RendererList); + this.startNub.Visible = false; + this.startNub.Shape = MoveNubShape.Circle; + RendererList.Add(this.startNub, false); + + this.endNub = new MoveNubRenderer(RendererList); + this.endNub.Visible = false; + this.endNub.Shape = MoveNubShape.Circle; + RendererList.Add(this.endNub, false); + + this.moveNubs = + new MoveNubRenderer[] + { + this.startNub, + this.endNub + }; + + AppEnvironment.PrimaryColorChanged += new EventHandler(RenderBecauseOfEvent); + AppEnvironment.SecondaryColorChanged += new EventHandler(RenderBecauseOfEvent); + AppEnvironment.GradientInfoChanged += new EventHandler(RenderBecauseOfEvent); + AppEnvironment.AlphaBlendingChanged += new EventHandler(RenderBecauseOfEvent); + AppWorkspace.UnitsChanged += new EventHandler(RenderBecauseOfEvent); + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + AppEnvironment.PrimaryColorChanged -= new EventHandler(RenderBecauseOfEvent); + AppEnvironment.SecondaryColorChanged -= new EventHandler(RenderBecauseOfEvent); + AppEnvironment.GradientInfoChanged -= new EventHandler(RenderBecauseOfEvent); + AppEnvironment.AlphaBlendingChanged -= new EventHandler(RenderBecauseOfEvent); + AppWorkspace.UnitsChanged -= new EventHandler(RenderBecauseOfEvent); + + if (this.gradientActive) + { + CommitGradient(); + this.mouseButton = MouseButtons.None; + } + + if (this.startNub != null) + { + RendererList.Remove(this.startNub); + this.startNub.Dispose(); + this.startNub = null; + } + + if (this.endNub != null) + { + RendererList.Remove(this.endNub); + this.endNub.Dispose(); + this.endNub = null; + } + + this.moveNubs = null; + + if (this.toolCursor != null) + { + this.toolCursor.Dispose(); + this.toolCursor = null; + } + + if (this.toolMouseDownCursor != null) + { + this.toolMouseDownCursor.Dispose(); + this.toolMouseDownCursor = null; + } + + base.OnDeactivate(); + } + + public GradientTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + StaticImage, + StaticName, + PdnResources.GetString("GradientTool.HelpText"), + 'g', + false, + ToolBarConfigItems.Gradient | ToolBarConfigItems.AlphaBlending) + { + } + } +} diff --git a/src/tools/LassoSelectTool.cs b/src/tools/LassoSelectTool.cs new file mode 100644 index 0000000..01a4fad --- /dev/null +++ b/src/tools/LassoSelectTool.cs @@ -0,0 +1,61 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class LassoSelectTool + : SelectionTool + { + protected override List CreateShape(List inputTracePoints) + { + List inputTracePointsF = base.CreateShape(inputTracePoints); + + if (this.SelectionMode != CombineMode.Replace && + inputTracePointsF.Count > 2 && + inputTracePointsF[0] != inputTracePointsF[inputTracePointsF.Count - 1]) + { + inputTracePointsF.Add(inputTracePointsF[0]); + } + + return inputTracePointsF; + } + + protected override void OnActivate() + { + SetCursors( + "Cursors.LassoSelectToolCursor.cur", + "Cursors.LassoSelectToolCursorMinus.cur", + "Cursors.LassoSelectToolCursorPlus.cur", + "Cursors.LassoSelectToolCursorMouseDown.cur"); + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + } + + public LassoSelectTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.LassoSelectToolIcon.png"), + PdnResources.GetString("LassoSelectTool.Name"), + PdnResources.GetString("LassoSelectTool.HelpText"), + 's', + ToolBarConfigItems.None) + { + } + } +} diff --git a/src/tools/LineTool.cs b/src/tools/LineTool.cs new file mode 100644 index 0000000..d1b97d5 --- /dev/null +++ b/src/tools/LineTool.cs @@ -0,0 +1,605 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class LineTool + : ShapeTool + { + private const int controlPointCount = 4; + private const float flattenConstant = 0.1f; + private Cursor lineToolCursor; + private Cursor lineToolMouseDownCursor; + private string statusTextFormat = PdnResources.GetString("LineTool.StatusText.Format"); + private ImageResource lineToolIcon; + private MoveNubRenderer[] moveNubs; + private bool inCurveMode = false; + private int draggingNubIndex = -1; + private CurveType curveType; + + private enum CurveType + { + NotDecided, + Bezier, + Spline + } + + private PointF[] LineToSpline(PointF a, PointF b, int points) + { + PointF[] spline = new PointF[points]; + + for (int i = 0; i < spline.Length; ++i) + { + float frac = (float)i / (float)(spline.Length - 1); + PointF mid = Utility.Lerp(a, b, frac); + spline[i] = mid; + } + + return spline; + } + + protected override List TrimShapePath(List points) + { + if (this.inCurveMode) + { + return points; + } + else + { + List array = new List(); + + if (points.Count > 0) + { + array.Add(points[0]); + + if (points.Count > 1) + { + array.Add(points[points.Count - 1]); + } + } + + return array; + } + } + + private void ConstrainPoints(ref PointF a, ref PointF b) + { + PointF dir = new PointF(b.X - a.X, b.Y - a.Y); + double theta = Math.Atan2(dir.Y, dir.X); + double len = Math.Sqrt(dir.X * dir.X + dir.Y * dir.Y); + + theta = Math.Round(12 * theta / Math.PI) * Math.PI / 12; + b = new PointF((float)(a.X + len * Math.Cos(theta)), (float)(a.Y + len * Math.Sin(theta))); + } + + protected override PdnGraphicsPath CreateShapePath(PointF[] points) + { + if (points.Length >= 4) + { + PdnGraphicsPath path = new PdnGraphicsPath(); + + switch (this.curveType) + { + default: + case CurveType.Spline: + path.AddCurve(points); + break; + + case CurveType.Bezier: + path.AddBezier(points[0], points[1], points[2], points[3]); + break; + } + + path.Flatten(Utility.IdentityMatrix, flattenConstant); + return path; + } + else //if (points.Length <= 2) + { + PointF a = points[0]; + PointF b = points[points.Length - 1]; + + if (0 != (ModifierKeys & Keys.Shift) && a != b) + { + ConstrainPoints(ref a, ref b); + } + + double angle = -180.0 * Math.Atan2(b.Y - a.Y, b.X - a.X) / Math.PI; + MeasurementUnit units = AppWorkspace.Units; + double offsetXPhysical = Document.PixelToPhysicalX(b.X - a.X, units); + double offsetYPhysical = Document.PixelToPhysicalY(b.Y - a.Y, units); + double offsetLengthPhysical = Math.Sqrt(offsetXPhysical * offsetXPhysical + offsetYPhysical * offsetYPhysical); + + string numberFormat; + string unitsAbbreviation; + + if (units != MeasurementUnit.Pixel) + { + string unitsAbbreviationName = "MeasurementUnit." + units.ToString() + ".Abbreviation"; + unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName); + numberFormat = "F2"; + } + else + { + unitsAbbreviation = string.Empty; + numberFormat = "F0"; + } + + string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural"); + + string statusText = string.Format( + this.statusTextFormat, + offsetXPhysical.ToString(numberFormat), + unitsAbbreviation, + offsetYPhysical.ToString(numberFormat), + unitsAbbreviation, + offsetLengthPhysical.ToString("F2"), + unitsString, + angle.ToString("F2")); + + SetStatus(this.lineToolIcon, statusText); + + if (a == b) + { + return null; + } + else + { + PdnGraphicsPath path = new PdnGraphicsPath(); + PointF[] spline = LineToSpline(a, b, controlPointCount); + path.AddCurve(spline); + path.Flatten(Utility.IdentityMatrix, flattenConstant); + return path; + } + } + } + + public override PixelOffsetMode GetPixelOffsetMode() + { + return PixelOffsetMode.None; + } + + protected override void OnPulse() + { + if (this.moveNubs != null) + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + if (!this.moveNubs[i].Visible) + { + continue; + } + + // Oscillate between 25% and 100% alpha over a period of 2 seconds + // Alpha value of 100% is sustained for a large duration of this period + const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per second + long tick = (DateTime.Now.Ticks % period) + (i * (period / this.moveNubs.Length));; + double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI)); + // sin is [-1, +1] + + sin = Math.Min(0.5, sin); + // sin is [-1, +0.5] + + sin += 1.0; + // sin is [0, 1.5] + + sin /= 2.0; + // sin is [0, 0.75] + + sin += 0.25; + // sin is [0.25, 1] + + int newAlpha = (int)(sin * 255.0); + int clampedAlpha = Utility.Clamp(newAlpha, 0, 255); + this.moveNubs[i].Alpha = clampedAlpha; + } + } + + base.OnPulse(); + } + + private const int toggleStartCapOrdinal = 0; + private const int toggleDashOrdinal = 1; + private const int toggleEndCapOrdinal = 2; + + protected override bool OnWildShortcutKey(int ordinal) + { + switch (ordinal) + { + case toggleStartCapOrdinal: + AppWorkspace.Widgets.ToolConfigStrip.CyclePenStartCap(); + return true; + + case toggleDashOrdinal: + AppWorkspace.Widgets.ToolConfigStrip.CyclePenDashStyle(); + return true; + + case toggleEndCapOrdinal: + AppWorkspace.Widgets.ToolConfigStrip.CyclePenEndCap(); + return true; + } + + return base.OnWildShortcutKey(ordinal); + } + + private bool controlKeyDown = false; + private DateTime controlKeyDownTime = DateTime.MinValue; + private readonly TimeSpan controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400); + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.ControlKey: + if (!this.controlKeyDown) + { + this.controlKeyDown = true; + this.controlKeyDownTime = DateTime.Now; + } + + break; + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.ControlKey: + TimeSpan heldDuration = (DateTime.Now - this.controlKeyDownTime); + + // If the user taps Ctrl, then we should toggle the visiblity of the moveNubs + if (heldDuration < this.controlKeyDownThreshold) + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i].Visible = this.inCurveMode && !this.moveNubs[i].Visible; + } + } + + this.controlKeyDown = false; + break; + } + + base.OnKeyUp(e); base.OnKeyUp(e); + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + if (this.inCurveMode) + { + switch (e.KeyChar) + { + case '\r': // Enter + e.Handled = true; + CommitShape(); + break; + + case (char)27: // Escape + // Only recognize if the user is not pressing Ctrl. + // Reason for this is that Ctrl+[ ends up being sent + // to us as (char)27 as well, but the user probably + // wants to use that for the decrease brush size + // shortcut, not cancel :) + if ((ModifierKeys & Keys.Control) == 0) + { + e.Handled = true; + HistoryStack.StepBackward(); + } + break; + } + } + + base.OnKeyPress(e); + } + + protected override void OnShapeCommitting() + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i].Visible = false; + } + + this.inCurveMode = false; + this.curveType = CurveType.NotDecided; + this.Cursor = this.lineToolCursor; + this.draggingNubIndex = -1; + + DocumentWorkspace.UpdateStatusBarToToolHelpText(); + } + + protected override bool OnShapeEnd() + { + // init move nubs + List points = GetTrimmedShapePath(); + + if (points.Count < 2) + { + return true; + } + else + { + PointF a = (PointF)points[0]; + PointF b = (PointF)points[points.Count - 1]; + + if (0 != (ModifierKeys & Keys.Shift) && a != b) + { + ConstrainPoints(ref a, ref b); + } + + PointF[] spline = LineToSpline(a, b, controlPointCount); + List newPoints = new List(); + + this.inCurveMode = true; + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i].Location = spline[i]; + this.moveNubs[i].Visible = true; + newPoints.Add(spline[i]); + } + + string helpText2 = PdnResources.GetString("LineTool.PreCurveHelpText"); + this.SetStatus(null, helpText2); + SetShapePath(newPoints); + return false; + } + } + + protected override void OnStylusDown(StylusEventArgs e) + { + bool callBase = false; + + if (!this.inCurveMode) + { + callBase = true; + } + else + { + PointF mousePtF = new PointF(e.Fx, e.Fy); + Point mousePt = Point.Truncate(mousePtF); + float minDistance = float.MaxValue; + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + if (this.moveNubs[i].IsPointTouching(mousePt, true)) + { + float distance = Utility.Distance(mousePtF, this.moveNubs[i].Location); + + if (distance < minDistance) + { + minDistance = distance; + this.draggingNubIndex = i; + } + } + } + + if (this.draggingNubIndex == -1) + { + callBase = true; + } + else + { + this.Cursor = this.handCursorMouseDown; + + if (this.curveType == CurveType.NotDecided) + { + if (e.Button == MouseButtons.Right) + { + this.curveType = CurveType.Bezier; + } + else + { + this.curveType = CurveType.Spline; + } + } + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i].Visible = false; + } + + string helpText2 = PdnResources.GetString("LineTool.CurvingHelpText"); + SetStatus(null, helpText2); + OnStylusMove(e); + } + } + + if (callBase) + { + base.OnStylusDown(e); + Cursor = this.lineToolMouseDownCursor; + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + if (!this.inCurveMode) + { + base.OnMouseDown(e); + } + } + + protected override void OnStylusUp(StylusEventArgs e) + { + if (!this.inCurveMode) + { + base.OnStylusUp(e); + } + else + { + if (this.draggingNubIndex != -1) + { + OnStylusMove(e); + this.draggingNubIndex = -1; + this.Cursor = this.lineToolCursor; + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i].Visible = true; + } + } + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + if (!this.inCurveMode) + { + base.OnMouseUp(e); + } + } + + protected override void OnStylusMove(StylusEventArgs e) + { + if (!this.inCurveMode) + { + base.OnStylusMove(e); + } + else if (this.draggingNubIndex != -1) + { + PointF mousePt = new PointF(e.Fx, e.Fy); + this.moveNubs[this.draggingNubIndex].Location = mousePt; + List points = GetTrimmedShapePath(); + points[this.draggingNubIndex] = mousePt; + SetShapePath(points); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (this.draggingNubIndex != -1) + { + RenderShape(); + Update(); + } + else + { + Point mousePt = new Point(e.X, e.Y); + bool hot = false; + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + if (this.moveNubs[i].Visible && this.moveNubs[i].IsPointTouching(Point.Truncate(mousePt), true)) + { + this.Cursor = this.handCursor; + hot = true; + break; + } + } + + if (!hot) + { + if (IsMouseDown) + { + Cursor = this.lineToolMouseDownCursor; + } + else + { + Cursor = this.lineToolCursor; + } + } + } + + base.OnMouseMove(e); + } + + protected override void OnActivate() + { + this.lineToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.LineToolCursor.cur")); + this.lineToolMouseDownCursor = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur")); + this.Cursor = this.lineToolCursor; + this.lineToolIcon = this.Image; + + this.moveNubs = new MoveNubRenderer[controlPointCount]; + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i] = new MoveNubRenderer(this.RendererList); + this.moveNubs[i].Visible = false; + this.RendererList.Add(this.moveNubs[i], false); + } + + AppEnvironment.PrimaryColorChanged += new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.SecondaryColorChanged += new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.AntiAliasingChanged += new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.AlphaBlendingChanged += new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.BrushInfoChanged += new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.PenInfoChanged += new EventHandler(RenderShapeBecauseOfEvent); + AppWorkspace.UnitsChanged += new EventHandler(RenderShapeBecauseOfEvent); + + base.OnActivate(); + } + + private void RenderShapeBecauseOfEvent(object sender, EventArgs e) + { + if (this.inCurveMode) + { + RenderShape(); + } + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + + AppEnvironment.PrimaryColorChanged -= new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.SecondaryColorChanged -= new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.AntiAliasingChanged -= new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.AlphaBlendingChanged -= new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.BrushInfoChanged -= new EventHandler(RenderShapeBecauseOfEvent); + AppEnvironment.PenInfoChanged -= new EventHandler(RenderShapeBecauseOfEvent); + AppWorkspace.UnitsChanged -= new EventHandler(RenderShapeBecauseOfEvent); + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.RendererList.Remove(this.moveNubs[i]); + this.moveNubs[i].Dispose(); + this.moveNubs[i] = null; + } + + this.moveNubs = null; + + if (this.lineToolCursor != null) + { + this.lineToolCursor.Dispose(); + this.lineToolCursor = null; + } + + if (this.lineToolMouseDownCursor != null) + { + this.lineToolMouseDownCursor.Dispose(); + this.lineToolMouseDownCursor = null; + } + } + + public LineTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.LineToolIcon.png"), + PdnResources.GetString("LineTool.Name"), + PdnResources.GetString("LineTool.HelpText"), + ToolBarConfigItems.None | ToolBarConfigItems.PenCaps, + ToolBarConfigItems.ShapeType) + { + this.ForceShapeDrawType = true; + this.ForcedShapeDrawType = ShapeDrawType.Outline; + this.UseDashStyle = true; + } + } +} diff --git a/src/tools/MagicWandTool.cs b/src/tools/MagicWandTool.cs new file mode 100644 index 0000000..0641e7d --- /dev/null +++ b/src/tools/MagicWandTool.cs @@ -0,0 +1,158 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class MagicWandTool + : FloodToolBase + { + private Cursor cursorMouseUp; + private Cursor cursorMouseUpMinus; + private Cursor cursorMouseUpPlus; + private CombineMode combineMode; + + private Cursor GetCursor(bool ctrlDown, bool altDown) + { + Cursor cursor; + + if (ctrlDown) + { + cursor = this.cursorMouseUpPlus; + } + else if (altDown) + { + cursor = this.cursorMouseUpMinus; + } + else + { + cursor = this.cursorMouseUp; + } + + return cursor; + } + + private Cursor GetCursor() + { + return GetCursor((ModifierKeys & Keys.Control) != 0, (ModifierKeys & Keys.Alt) != 0); + } + + protected override void OnActivate() + { + DocumentWorkspace.EnableSelectionTinting = true; + this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.MagicWandToolCursor.cur")); + this.cursorMouseUpMinus = new Cursor(PdnResources.GetResourceStream("Cursors.MagicWandToolCursorMinus.cur")); + this.cursorMouseUpPlus = new Cursor(PdnResources.GetResourceStream("Cursors.MagicWandToolCursorPlus.cur")); + this.Cursor = GetCursor(); + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (this.cursorMouseUp != null) + { + this.cursorMouseUp.Dispose(); + this.cursorMouseUp = null; + } + + if (this.cursorMouseUpMinus != null) + { + this.cursorMouseUpMinus.Dispose(); + this.cursorMouseUpMinus = null; + } + + if (this.cursorMouseUpPlus != null) + { + this.cursorMouseUpPlus.Dispose(); + this.cursorMouseUpPlus = null; + } + + DocumentWorkspace.EnableSelectionTinting = false; + base.OnDeactivate(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + Cursor = GetCursor(); + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + Cursor = GetCursor(); + base.OnKeyUp(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + Cursor = GetCursor(); + base.OnMouseUp(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + Cursor = Cursors.WaitCursor; + + if ((ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Left) + { + this.combineMode = CombineMode.Union; + } + else if ((ModifierKeys & Keys.Alt) != 0 && e.Button == MouseButtons.Left) + { + this.combineMode = CombineMode.Exclude; + } + else if ((ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Right) + { + this.combineMode = CombineMode.Xor; + } + else if ((ModifierKeys & Keys.Alt) != 0 && e.Button == MouseButtons.Right) + { + this.combineMode = CombineMode.Intersect; + } + else + { + this.combineMode = AppEnvironment.SelectionCombineMode; + } + + base.OnMouseDown(e); + } + + protected override void OnFillRegionComputed(Point[][] polygonSet) + { + SelectionHistoryMemento undoAction = new SelectionHistoryMemento(this.Name, this.Image, this.DocumentWorkspace); + + Selection.PerformChanging(); + Selection.SetContinuation(polygonSet, this.combineMode); + Selection.CommitContinuation(); + Selection.PerformChanged(); + + HistoryStack.PushNewMemento(undoAction); + } + + public MagicWandTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.MagicWandToolIcon.png"), + PdnResources.GetString("MagicWandTool.Name"), + PdnResources.GetString("MagicWandTool.HelpText"), + 's', + false, + ToolBarConfigItems.SelectionCombineMode) + { + ClipToSelection = false; + } + } +} \ No newline at end of file diff --git a/src/tools/MoveSelectionTool.cs b/src/tools/MoveSelectionTool.cs new file mode 100644 index 0000000..1130573 --- /dev/null +++ b/src/tools/MoveSelectionTool.cs @@ -0,0 +1,305 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Runtime.Serialization; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class MoveSelectionTool + : MoveToolBase + { + public static string StaticName + { + get + { + return PdnResources.GetString("MoveSelectionTool.Name"); + } + } + + private class ContextHistoryMemento + : ToolHistoryMemento + { + [Serializable] + private class OurHistoryMementoData + : HistoryMementoData + { + public Context context; + + public OurHistoryMementoData(Context context) + { + this.context = (Context)context.Clone(); + } + } + + protected override HistoryMemento OnToolUndo() + { + MoveSelectionTool moveSelectionTool = DocumentWorkspace.Tool as MoveSelectionTool; + + if (moveSelectionTool == null) + { + throw new InvalidOperationException("Current Tool is not the MoveSelectionTool"); + } + + ContextHistoryMemento cha = new ContextHistoryMemento(DocumentWorkspace, moveSelectionTool.context, this.Name, this.Image); + OurHistoryMementoData ohad = (OurHistoryMementoData)this.Data; + Context newContext = ohad.context; + + moveSelectionTool.context.Dispose(); + moveSelectionTool.context = newContext; + + moveSelectionTool.DestroyNubs(); + + if (moveSelectionTool.context.lifted) + { + moveSelectionTool.PositionNubs(moveSelectionTool.context.currentMode); + } + + return cha; + } + + public ContextHistoryMemento(DocumentWorkspace documentWorkspace, Context context, string name, ImageResource image) + : base(documentWorkspace, name, image) + { + this.Data = new OurHistoryMementoData(context); + } + } + + protected override void OnActivate() + { + DocumentWorkspace.EnableSelectionTinting = true; + + this.moveToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.MoveSelectionToolCursor.cur")); + this.Cursor = this.moveToolCursor; + + this.context.offset = new Point(0, 0); + this.context.liftedBounds = Selection.GetBoundsF(); + + this.tracking = false; + PositionNubs(this.context.currentMode); + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + DocumentWorkspace.EnableSelectionTinting = false; + + if (this.moveToolCursor != null) + { + this.moveToolCursor.Dispose(); + this.moveToolCursor = null; + } + + if (context.lifted) + { + Drop(); + } + + this.tracking = false; + DestroyNubs(); + + base.OnDeactivate(); + } + + protected override void Drop() + { + ContextHistoryMemento cha = new ContextHistoryMemento(this.DocumentWorkspace, this.context, this.Name, this.Image); + this.currentHistoryMementos.Add(cha); + + SelectionHistoryMemento sha = new SelectionHistoryMemento(this.Name, this.Image, this.DocumentWorkspace); + this.currentHistoryMementos.Add(sha); + + this.context.Dispose(); + this.context = new Context(); + + this.FlushHistoryMementos(PdnResources.GetString("MoveSelectionTool.HistoryMemento.DropSelection")); + } + + protected override void OnSelectionChanging() + { + base.OnSelectionChanging(); + + if (!dontDrop) + { + if (context.lifted) + { + Drop(); + } + + if (tracking) + { + tracking = false; + } + } + } + + protected override void OnSelectionChanged() + { + if (!this.context.lifted) + { + DestroyNubs(); + PositionNubs(this.context.currentMode); + } + + base.OnSelectionChanged(); + } + + protected override void OnLift(MouseEventArgs e) + { + // do nothing + } + + protected override void PushContextHistoryMemento() + { + ContextHistoryMemento cha = new ContextHistoryMemento(this.DocumentWorkspace, this.context, null, null); + this.currentHistoryMementos.Add(cha); + } + + protected override void Render(Point newOffset, bool useNewOffset) + { + PositionNubs(this.context.currentMode); + } + + protected override void PreRender() + { + // do nothing + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (!tracking) + { + return; + } + + OnMouseMove(e); + + this.rotateNub.Visible = false; + tracking = false; + PositionNubs(this.context.currentMode); + + string resourceName; + switch (this.context.currentMode) + { + default: + throw new InvalidEnumArgumentException(); + + case Mode.Rotate: + resourceName = "MoveSelectionTool.HistoryMemento.Rotate"; + break; + + case Mode.Scale: + resourceName = "MoveSelectionTool.HistoryMemento.Scale"; + break; + + case Mode.Translate: + resourceName = "MoveSelectionTool.HistoryMemento.Translate"; + break; + } + + this.context.startAngle += this.angleDelta; + + string actionName = PdnResources.GetString(resourceName); + FlushHistoryMementos(actionName); + } + + private void FlushHistoryMementos(string name) + { + if (this.currentHistoryMementos.Count > 0) + { + CompoundHistoryMemento cha = new CompoundHistoryMemento(null, null, + this.currentHistoryMementos.ToArray()); + + string haName; + + if (name == null) + { + haName = this.Name; + } + else + { + haName = name; + } + + ImageResource image = this.Image; + + CompoundToolHistoryMemento ctha = new CompoundToolHistoryMemento(cha, DocumentWorkspace, haName, image); + + ctha.SeriesGuid = context.seriesGuid; + HistoryStack.PushNewMemento(ctha); + + this.currentHistoryMementos.Clear(); + } + } + + public MoveSelectionTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.MoveSelectionToolIcon.png"), + MoveSelectionTool.StaticName, + PdnResources.GetString("MoveSelectionTool.HelpText"), // "Click and drag to move a selected region", + 'm', + false, + ToolBarConfigItems.None) + { + this.context = new Context(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose (disposing); + + if (disposing) + { + DestroyNubs(); + + if (this.context != null) + { + this.context.Dispose(); + this.context = null; + } + } + } + + protected override void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e) + { + this.dontDrop = true; + + if (e.MayAlterSuspendTool) + { + e.SuspendTool = false; + } + } + + protected override void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e) + { + if (this.context.lifted) + { + Render(context.offset, true); + } + else + { + DestroyNubs(); + PositionNubs(this.context.currentMode); + } + + this.dontDrop = false; + } + } +} diff --git a/src/tools/MoveTool.cs b/src/tools/MoveTool.cs new file mode 100644 index 0000000..e85f0d7 --- /dev/null +++ b/src/tools/MoveTool.cs @@ -0,0 +1,700 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Leave uncommented to always use bilinear rendering. Otherwise nearest neighbor +// is used while interacting with the selection via the mouse, for better performance. +//#define ALWAYSHIGHQUALITY + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using PaintDotNet.Threading; +using System; +using System.Collections; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Runtime.Serialization; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class MoveTool + : MoveToolBase + { + public static string StaticName + { + get + { + return PdnResources.GetString("MoveTool.Name"); + } + } + + // if this equals false, then Render() will always use NearestNeighbor, regardless of AppEnvironment.ResamplingAlgorithm + private bool fullQuality = false; + + private BitmapLayer activeLayer; + private RenderArgs renderArgs; + private bool didPaste = false; + + private MoveToolContext ourContext + { + get + { + return (MoveToolContext)this.context; + } + } + + [Serializable] + private sealed class MoveToolContext + : MoveToolBase.Context + { + [NonSerialized] + private MaskedSurface liftedPixels; + + [NonSerialized] + public PersistedObject poLiftedPixels; + + public Guid poLiftedPixelsGuid; + + public MaskedSurface LiftedPixels + { + get + { + if (this.liftedPixels == null) + { + if (this.poLiftedPixels != null) + { + this.liftedPixels = (MaskedSurface)poLiftedPixels.Object; + } + } + + return this.liftedPixels; + } + + set + { + if (value == null) + { + this.poLiftedPixels = null; + this.liftedPixels = null; + } + else + { + this.poLiftedPixels = new PersistedObject(value, true); + this.poLiftedPixelsGuid = PersistedObjectLocker.Add(this.poLiftedPixels); + this.liftedPixels = null; + } + } + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + info.AddValue("poLiftedPixelsGuid", this.poLiftedPixelsGuid); + } + + public MoveToolContext(SerializationInfo info, StreamingContext context) + : base(info, context) + { + this.poLiftedPixelsGuid = (Guid)info.GetValue("poLiftedPixelsGuid", typeof(Guid)); + this.poLiftedPixels = PersistedObjectLocker.Get(this.poLiftedPixelsGuid); + } + + public MoveToolContext(MoveToolContext cloneMe) + : base(cloneMe) + { + this.poLiftedPixelsGuid = cloneMe.poLiftedPixelsGuid; + this.poLiftedPixels = cloneMe.poLiftedPixels; // do not clone + this.liftedPixels = cloneMe.liftedPixels; // do not clone + } + + public MoveToolContext() + { + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + + base.Dispose(disposing); + } + + public override object Clone() + { + return new MoveToolContext(this); + } + } + + private class ContextHistoryMemento + : ToolHistoryMemento + { + private int layerIndex; + private object liftedPixelsRef; // prevent this from being GC'd + + [Serializable] + private class OurContextHistoryMementoData + : HistoryMementoData + { + public MoveToolContext context; + + public OurContextHistoryMementoData(Context context) + { + this.context = (MoveToolContext)context.Clone(); + } + } + + protected override HistoryMemento OnToolUndo() + { + MoveTool moveTool = DocumentWorkspace.Tool as MoveTool; + + if (moveTool == null) + { + throw new InvalidOperationException("Current Tool is not the MoveTool"); + } + + ContextHistoryMemento cha = new ContextHistoryMemento(DocumentWorkspace, moveTool.ourContext, this.Name, this.Image); + OurContextHistoryMementoData ohad = (OurContextHistoryMementoData)this.Data; + Context newContext = ohad.context; + + if (moveTool.ActiveLayerIndex != this.layerIndex) + { + bool oldDOLC = moveTool.deactivateOnLayerChange; + moveTool.deactivateOnLayerChange = false; + moveTool.ActiveLayerIndex = this.layerIndex; + moveTool.deactivateOnLayerChange = oldDOLC; + moveTool.activeLayer = (BitmapLayer)moveTool.ActiveLayer; + moveTool.renderArgs = new RenderArgs(moveTool.activeLayer.Surface); + moveTool.ClearSavedMemory(); + } + + moveTool.context.Dispose(); + moveTool.context = newContext; + + moveTool.DestroyNubs(); + + if (moveTool.context.lifted) + { + moveTool.PositionNubs(moveTool.context.currentMode); + } + + return cha; + } + + public ContextHistoryMemento(DocumentWorkspace documentWorkspace, MoveToolContext context, string name, ImageResource image) + : base(documentWorkspace, name, image) + { + this.Data = new OurContextHistoryMementoData(context); + this.layerIndex = this.DocumentWorkspace.ActiveLayerIndex; + this.liftedPixelsRef = context.poLiftedPixels; + } + } + + protected override void OnActivate() + { + AppEnvironment.ResamplingAlgorithmChanged += AppEnvironment_ResamplingAlgorithmChanged; + + this.moveToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.MoveToolCursor.cur")); + this.Cursor = this.moveToolCursor; + + this.context.lifted = false; + this.ourContext.LiftedPixels = null; + this.context.offset = new Point(0, 0); + this.context.liftedBounds = Selection.GetBoundsF(); + this.activeLayer = (BitmapLayer)ActiveLayer; + + if (this.renderArgs != null) + { + this.renderArgs.Dispose(); + this.renderArgs = null; + } + + if (this.activeLayer == null) + { + this.renderArgs = null; + } + else + { + this.renderArgs = new RenderArgs(this.activeLayer.Surface); + } + + this.tracking = false; + PositionNubs(this.context.currentMode); + +#if ALWAYSHIGHQUALITY + this.fullQuality = true; +#endif + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + AppEnvironment.ResamplingAlgorithmChanged -= AppEnvironment_ResamplingAlgorithmChanged; + + if (this.moveToolCursor != null) + { + this.moveToolCursor.Dispose(); + this.moveToolCursor = null; + } + + if (context.lifted) + { + Drop(); + } + + this.activeLayer = null; + + if (this.renderArgs != null) + { + this.renderArgs.Dispose(); + this.renderArgs = null; + } + + this.tracking = false; + DestroyNubs(); + base.OnDeactivate(); + } + + private void AppEnvironment_ResamplingAlgorithmChanged(object sender, EventArgs e) + { + if (this.ourContext.LiftedPixels != null) + { + bool oldHQ = this.fullQuality; + this.fullQuality = true; + PreRender(); + Render(this.context.offset, true); + Update(); + this.fullQuality = oldHQ; + } + } + + protected override void Drop() + { + RestoreSavedRegion(); + + PdnRegion regionCopy = Selection.CreateRegion(); + + using (PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(regionCopy, + Utility.DefaultSimplificationFactor, 2)) + { + HistoryMemento bitmapAction2 = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, + ActiveLayerIndex, simplifiedRegion); + + bool oldHQ = this.fullQuality; + this.fullQuality = true; + Render(this.context.offset, true); + this.fullQuality = oldHQ; + this.currentHistoryMementos.Add(bitmapAction2); + + activeLayer.Invalidate(simplifiedRegion); + Update(); + } + + regionCopy.Dispose(); + regionCopy = null; + + ContextHistoryMemento cha = new ContextHistoryMemento(this.DocumentWorkspace, this.ourContext, this.Name, this.Image); + this.currentHistoryMementos.Add(cha); + + string name; + ImageResource image; + + if (didPaste) + { + name = EnumLocalizer.EnumValueToLocalizedName(typeof(CommonAction), CommonAction.Paste); + image = PdnResources.GetImageResource("Icons.MenuEditPasteIcon.png"); + } + else + { + name = this.Name; + image = this.Image; + } + + didPaste = false; + + SelectionHistoryMemento sha = new SelectionHistoryMemento(this.Name, this.Image, this.DocumentWorkspace); + this.currentHistoryMementos.Add(sha); + + this.context.Dispose(); + this.context = new MoveToolContext(); + + this.FlushHistoryMementos(PdnResources.GetString("MoveTool.HistoryMemento.DropPixels")); + } + + protected override void OnSelectionChanging() + { + base.OnSelectionChanging(); + + if (!dontDrop) + { + if (context.lifted) + { + Drop(); + } + + if (tracking) + { + tracking = false; + } + } + } + + protected override void OnSelectionChanged() + { + if (!context.lifted) + { + DestroyNubs(); + PositionNubs(this.context.currentMode); + } + + base.OnSelectionChanged(); + } + + /// + /// Provided as a special entry point so that Paste can work well. + /// + /// What you want to paste. + /// Where you want to paste it. + public void PasteMouseDown(SurfaceForClipboard sfc, Point offset) + { + if (this.context.lifted) + { + Drop(); + } + + MaskedSurface pixels = sfc.MaskedSurface; + PdnGraphicsPath pastePath = pixels.CreatePath(); + + PdnRegion pasteRegion = new PdnRegion(pastePath); + + PdnRegion simplifiedPasteRegion = Utility.SimplifyAndInflateRegion(pasteRegion); + + HistoryMemento bitmapAction = new BitmapHistoryMemento(Name, Image, + DocumentWorkspace, ActiveLayerIndex, simplifiedPasteRegion); // SLOW (110ms) + + this.currentHistoryMementos.Add(bitmapAction); + + PushContextHistoryMemento(); + + this.context.seriesGuid = Guid.NewGuid(); + this.context.currentMode = Mode.Translate; + this.context.startEdge = Edge.None; + this.context.startAngle = 0.0f; + + this.ourContext.LiftedPixels = pixels; + this.context.lifted = true; + this.context.liftTransform = new Matrix(); + this.context.liftTransform.Reset(); + this.context.deltaTransform = new Matrix(); + this.context.deltaTransform.Reset(); + this.context.offset = new Point(0, 0); + + bool oldDD = this.dontDrop; + this.dontDrop = true; + + SelectionHistoryMemento sha = new SelectionHistoryMemento(null, null, DocumentWorkspace); + this.currentHistoryMementos.Add(sha); + + Selection.PerformChanging(); + Selection.Reset(); + Selection.SetContinuation(pastePath, CombineMode.Replace, true); + pastePath = null; + Selection.CommitContinuation(); + Selection.PerformChanged(); + + PushContextHistoryMemento(); + + this.context.liftedBounds = Selection.GetBoundsF(false); + this.context.startBounds = this.context.liftedBounds; + this.context.baseTransform = new Matrix(); + this.context.baseTransform.Reset(); + this.tracking = true; + + this.dontDrop = oldDD; + this.didPaste = true; + + this.tracking = true; + + DestroyNubs(); + PositionNubs(this.context.currentMode); + + // we use the value 70,000 to simulate mouse input because that's guaranteed to be out of bounds of where + // the mouse can actually be -- PDN is limited to 65536 x 65536 images by design + MouseEventArgs mea1 = new MouseEventArgs(MouseButtons.Left, 0, 70000, 70000, 0); + MouseEventArgs mea2 = new MouseEventArgs(MouseButtons.Left, 0, 70000 + offset.X, 70000 + offset.Y, 0); + this.context.startMouseXY = new Point(70000, 70000); + + OnMouseDown(mea1); + OnMouseMove(mea2); // SLOW (200ms) + OnMouseUp(mea2); + } + + protected override void OnLift(MouseEventArgs e) + { + PdnGraphicsPath liftPath = Selection.CreatePath(); + PdnRegion liftRegion = Selection.CreateRegion(); + + this.ourContext.LiftedPixels = new MaskedSurface(activeLayer.Surface, liftPath); + + HistoryMemento bitmapAction = new BitmapHistoryMemento( + Name, + Image, + DocumentWorkspace, + ActiveLayerIndex, + this.ourContext.poLiftedPixelsGuid); + + this.currentHistoryMementos.Add(bitmapAction); + + // If the user is holding down the control key, we want to *copy* the pixels + // and not "lift and erase" + if ((ModifierKeys & Keys.Control) == Keys.None) + { + ColorBgra fill = AppEnvironment.SecondaryColor; + fill.A = 0; + UnaryPixelOp op = new UnaryPixelOps.Constant(fill); + op.Apply(this.renderArgs.Surface, liftRegion); + } + + liftRegion.Dispose(); + liftRegion = null; + + liftPath.Dispose(); + liftPath = null; + } + + protected override void PushContextHistoryMemento() + { + ContextHistoryMemento cha = new ContextHistoryMemento(this.DocumentWorkspace, this.ourContext, null, null); + this.currentHistoryMementos.Add(cha); + } + + protected override void Render(Point newOffset, bool useNewOffset) + { + Render(newOffset, useNewOffset, true); + } + + protected void Render(Point newOffset, bool useNewOffset, bool saveRegion) + { + Rectangle saveBounds = Selection.GetBounds(); + PdnRegion selectedRegion = Selection.CreateRegion(); + PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(selectedRegion); + + if (saveRegion) + { + SaveRegion(simplifiedRegion, saveBounds); + } + + WaitCursorChanger wcc = null; + + if (this.fullQuality && AppEnvironment.ResamplingAlgorithm == ResamplingAlgorithm.Bilinear) + { + wcc = new WaitCursorChanger(DocumentWorkspace); + } + + this.ourContext.LiftedPixels.Draw( + this.renderArgs.Surface, + this.context.deltaTransform, + this.fullQuality ? + AppEnvironment.ResamplingAlgorithm : + ResamplingAlgorithm.NearestNeighbor); + + if (wcc != null) + { + wcc.Dispose(); + wcc = null; + } + + activeLayer.Invalidate(simplifiedRegion); + PositionNubs(this.context.currentMode); + + simplifiedRegion.Dispose(); + selectedRegion.Dispose(); + } + + protected override void PreRender() + { + RestoreSavedRegion(); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (!tracking) + { + return; + } + + this.fullQuality = true; + OnMouseMove(e); + +#if !ALWAYSHIGHQUALITY + this.fullQuality = false; +#endif + + this.rotateNub.Visible = false; + tracking = false; + PositionNubs(this.context.currentMode); + + string resourceName; + switch (this.context.currentMode) + { + default: + throw new InvalidEnumArgumentException(); + + case Mode.Rotate: + resourceName = "MoveTool.HistoryMemento.Rotate"; + break; + + case Mode.Scale: + resourceName = "MoveTool.HistoryMemento.Scale"; + break; + + case Mode.Translate: + resourceName = "MoveTool.HistoryMemento.Translate"; + break; + } + + this.context.startAngle += this.angleDelta; + + if (this.context.liftTransform == null) + { + this.context.liftTransform = new Matrix(); + } + + this.context.liftTransform.Reset(); + this.context.liftTransform.Multiply(this.context.deltaTransform, MatrixOrder.Append); + + string actionName = PdnResources.GetString(resourceName); + FlushHistoryMementos(actionName); + } + + private void FlushHistoryMementos(string name) + { + if (this.currentHistoryMementos.Count > 0) + { + CompoundHistoryMemento cha = new CompoundHistoryMemento(null, null, + this.currentHistoryMementos.ToArray()); + + string haName; + ImageResource image; + + if (this.didPaste) + { + haName = PdnResources.GetString("CommonAction.Paste"); + image = PdnResources.GetImageResource("Icons.MenuEditPasteIcon.png"); + this.didPaste = false; + } + else + { + if (name == null) + { + haName = this.Name; + } + else + { + haName = name; + } + + image = this.Image; + } + + CompoundToolHistoryMemento ctha = new CompoundToolHistoryMemento(cha, this.DocumentWorkspace, haName, image); + + ctha.SeriesGuid = context.seriesGuid; + HistoryStack.PushNewMemento(ctha); + + this.currentHistoryMementos.Clear(); + } + } + + public MoveTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.MoveToolIcon.png"), + MoveTool.StaticName, + PdnResources.GetString("MoveTool.HelpText"), // "Click and drag to move a selected region", + 'm', + false, + ToolBarConfigItems.Resampling) + { + this.context = new MoveToolContext(); + this.enableOutline = false; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + DestroyNubs(); + + if (this.renderArgs != null) + { + this.renderArgs.Dispose(); + this.renderArgs = null; + } + + if (this.context != null) + { + this.context.Dispose(); + this.context = null; + } + } + } + + protected override void OnExecutingHistoryMemento(ExecutingHistoryMementoEventArgs e) + { + this.dontDrop = true; + + RestoreSavedRegion(); + ClearSavedMemory(); + + if (e.MayAlterSuspendTool) + { + e.SuspendTool = false; + } + } + + protected override void OnExecutedHistoryMemento(ExecutedHistoryMementoEventArgs e) + { + if (context.lifted) + { + bool oldHQ = this.fullQuality; + this.fullQuality = false; + Render(context.offset, true); + ClearSavedMemory(); + this.fullQuality = oldHQ; + } + else + { + DestroyNubs(); + PositionNubs(this.context.currentMode); + } + + this.dontDrop = false; + } + + protected override void OnFinishedHistoryStepGroup() + { + if (context.lifted) + { + bool oldHQ = this.fullQuality; + this.fullQuality = true; + Render(context.offset, true, false); + this.fullQuality = oldHQ; + } + + base.OnFinishedHistoryStepGroup(); + } + } +} diff --git a/src/tools/MoveToolBase.cs b/src/tools/MoveToolBase.cs new file mode 100644 index 0000000..d6dcdad --- /dev/null +++ b/src/tools/MoveToolBase.cs @@ -0,0 +1,1058 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Runtime.Serialization; +using System.Text; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal abstract class MoveToolBase + : Tool + { + protected Cursor moveToolCursor; + protected bool dontDrop = false; // so that OnSelectionChanging() can tell who is raising the event ... don't drop the pixels if WE caused the event + protected float angleDelta; + protected MoveNubRenderer[] moveNubs; + protected RotateNubRenderer rotateNub; + protected bool tracking; + protected Context context; + protected bool hostShouldShowAngle; + protected float hostAngle; + protected List currentHistoryMementos = new List(); + protected bool deactivateOnLayerChange = true; + protected bool enableOutline = true; + + public override bool DeactivateOnLayerChange + { + get + { + return this.deactivateOnLayerChange; + } + } + + protected enum Mode + { + Translate, + Scale, + Rotate + } + + // Corresponds to array positions in this.moveNubs for easy mapping between the two + protected enum Edge + { + TopLeft = 0, + Top = 1, + TopRight = 2, + Right = 3, + BottomRight = 4, + Bottom = 5, + BottomLeft = 6, + Left = 7, + None = 99 + } + + [Serializable] + protected class Context + : ICloneable, + ISerializable, + IDisposable + { + public bool lifted; + public Guid seriesGuid; + public Matrix baseTransform; // a copy of the selection's interim transform at the time of mouse-down + public Matrix liftTransform; // a copy of the selection's interim transform at the time of lifting + public Matrix deltaTransform; // the transformations made since lifting + public RectangleF liftedBounds; + public RectangleF startBounds; + public float startAngle; + public PdnGraphicsPath startPath; + public Mode currentMode; + public Edge startEdge; + public Point startMouseXY; + public Point offset; + + private float[] GetMatrixElements(Matrix m) + { + if (m == null) + { + return null; + } + else + { + return m.Elements; + } + } + + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue("lifted", this.lifted); + info.AddValue("seriesGuid", this.seriesGuid); + info.AddValue("baseTransform", GetMatrixElements(this.baseTransform)); + info.AddValue("deltaTransform", GetMatrixElements(this.deltaTransform)); + info.AddValue("liftTransform", GetMatrixElements(this.liftTransform)); + info.AddValue("liftedBounds", this.liftedBounds); + info.AddValue("startBounds", this.startBounds); + info.AddValue("startAngle", this.startAngle); + info.AddValue("startPath", this.startPath); + info.AddValue("currentMode", this.currentMode); + info.AddValue("startEdge", this.startEdge); + info.AddValue("startMouseXY", this.startMouseXY); + info.AddValue("offset", this.offset); + } + + private Matrix ReadMatrix(SerializationInfo info, StreamingContext context, string name) + { + Matrix m; + float[] e = (float[])info.GetValue(name, typeof(float[])); + + if (e == null) + { + m = null; + } + else + { + m = new Matrix(e[0], e[1], e[2], e[3], e[4], e[5]); + } + + return m; + } + + public Context(SerializationInfo info, StreamingContext context) + { + this.lifted = (bool)info.GetValue("lifted", typeof(bool)); + this.seriesGuid = (Guid)info.GetValue("seriesGuid", typeof(Guid)); + this.baseTransform = ReadMatrix(info, context, "baseTransform"); + this.deltaTransform = ReadMatrix(info, context, "deltaTransform"); + this.liftTransform = ReadMatrix(info, context, "liftTransform"); + this.liftedBounds = (RectangleF)info.GetValue("liftedBounds", typeof(RectangleF)); + this.startBounds = (RectangleF)info.GetValue("startBounds", typeof(RectangleF)); + this.startAngle = (float)info.GetValue("startAngle", typeof(float)); + this.startPath = (PdnGraphicsPath)info.GetValue("startPath", typeof(PdnGraphicsPath)); + this.currentMode = (Mode)info.GetValue("currentMode", typeof(Mode)); + this.startEdge = (Edge)info.GetValue("startEdge", typeof(Edge)); + this.startMouseXY = (Point)info.GetValue("startMouseXY", typeof(Point)); + this.offset = (Point)info.GetValue("offset", typeof(Point)); + } + + public Context() + { + } + + public Context(Context cloneMe) + { + this.lifted = cloneMe.lifted; + this.seriesGuid = cloneMe.seriesGuid; + + if (cloneMe.baseTransform != null) + { + this.baseTransform = cloneMe.baseTransform.Clone(); + } + + if (cloneMe.deltaTransform != null) + { + this.deltaTransform = cloneMe.deltaTransform.Clone(); + } + + if (cloneMe.liftTransform != null) + { + this.liftTransform = cloneMe.liftTransform.Clone(); + } + + this.liftedBounds = cloneMe.liftedBounds; + this.startBounds = cloneMe.startBounds; + this.startAngle = cloneMe.startAngle; + + if (cloneMe.startPath != null) + { + this.startPath = cloneMe.startPath.Clone(); + } + + this.currentMode = cloneMe.currentMode; + this.startEdge = cloneMe.startEdge; + + this.startMouseXY = cloneMe.startMouseXY; + this.offset = cloneMe.offset; + } + + ~Context() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (this.baseTransform != null) + { + this.baseTransform.Dispose(); + this.baseTransform = null; + } + + if (this.deltaTransform != null) + { + this.deltaTransform.Dispose(); + this.deltaTransform = null; + } + + if (this.liftTransform != null) + { + this.liftTransform.Dispose(); + this.liftTransform = null; + } + + if (this.startPath != null) + { + this.startPath.Dispose(); + this.startPath = null; + } + } + } + + public virtual object Clone() + { + return new Context(this); + } + } + + protected class CompoundToolHistoryMemento + : ToolHistoryMemento + { + private CompoundHistoryMemento compoundHistoryMemento; + + public CompoundHistoryMemento CompoundHistoryMemento + { + get + { + return this.compoundHistoryMemento; + } + } + + protected override HistoryMemento OnToolUndo() + { + CompoundHistoryMemento chm = (CompoundHistoryMemento)this.compoundHistoryMemento.PerformUndo(); + CompoundToolHistoryMemento cthm = new CompoundToolHistoryMemento(chm, DocumentWorkspace, this.Name, this.Image); + return cthm; + } + + public CompoundToolHistoryMemento(CompoundHistoryMemento chm, DocumentWorkspace documentWorkspace, string name, ImageResource image) + : base(documentWorkspace, name, image) + { + this.compoundHistoryMemento = chm; + } + } + + public bool HostShouldShowAngle + { + get + { + return this.hostShouldShowAngle; + } + } + + public float HostAngle + { + get + { + return this.hostAngle; + } + } + + protected void DestroyNubs() + { + if (this.moveNubs != null) + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.RendererList.Remove(this.moveNubs[i]); + this.moveNubs[i].Dispose(); + this.moveNubs[i] = null; + } + + this.moveNubs = null; + } + + if (this.rotateNub != null) + { + this.RendererList.Remove(this.rotateNub); + this.rotateNub.Dispose(); + this.rotateNub = null; + } + } + + protected PointF GetEdgeVector(Edge edge) + { + PointF u; + switch (edge) + { + case Edge.TopLeft: + u = new PointF(-1, -1); + break; + + case Edge.Top: + u = new PointF(0, -1); + break; + + case Edge.TopRight: + u = new PointF(1, -1); + break; + + case Edge.Left: + u = new PointF(-1, 0); + break; + + case Edge.Right: + u = new PointF(1, 0); + break; + + case Edge.BottomLeft: + u = new PointF(-1, 1); + break; + + case Edge.BottomRight: + u = new PointF(1, 1); + break; + + case Edge.Bottom: + u = new PointF(0, 1); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + return u; + } + + protected void DetermineMoveMode(MouseEventArgs e, out Mode mode, out Edge edge) + { + mode = Mode.Translate; + edge = Edge.None; + + if (e.Button == MouseButtons.Right) + { + mode = Mode.Rotate; + } + else + { + float minDistance = float.MaxValue; + Point mousePt = new Point(e.X, e.Y); + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + MoveNubRenderer nub = this.moveNubs[i]; + + if (nub.IsPointTouching(mousePt, true)) + { + float distance = Utility.Distance((PointF)mousePt, nub.Location); + + if (distance < minDistance) + { + minDistance = distance; + mode = Mode.Scale; + edge = (Edge)i; + } + } + } + } + + return; + } + + protected override void OnPulse() + { + if (this.moveNubs != null) + { + for (int i = 0; i < this.moveNubs.Length; ++i) + { + // Oscillate between 25% and 100% alpha over a period of 2 seconds + // Alpha value of 100% is sustained for a large duration of this period + const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per period + long tick = (DateTime.Now.Ticks % period) + (i * (period / this.moveNubs.Length)); + double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI)); + // sin is [-1, +1] + + sin = Math.Min(0.5, sin); + // sin is [-1, +0.5] + + sin += 1.0; + // sin is [0, 1.5] + + sin /= 2.0; + // sin is [0, 0.75] + + sin += 0.25; + // sin is [0.25, 1] + + int newAlpha = (int)(sin * 255.0); + int clampedAlpha = Utility.Clamp(newAlpha, 0, 255); + this.moveNubs[i].Alpha = clampedAlpha; + } + } + + base.OnPulse(); + } + + protected void PositionNubs(Mode currentMode) + { + if (this.moveNubs == null) + { + this.moveNubs = new MoveNubRenderer[8]; + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + this.moveNubs[i] = new MoveNubRenderer(this.RendererList); + this.RendererList.Add(this.moveNubs[i], false); + } + + RectangleF bounds = Selection.GetBoundsF(false); + + this.moveNubs[(int)Edge.TopLeft].Location = new PointF(bounds.Left, bounds.Top); + this.moveNubs[(int)Edge.TopLeft].Shape = MoveNubShape.Circle; + + this.moveNubs[(int)Edge.Top].Location = new PointF((bounds.Left + bounds.Right) / 2.0f, bounds.Top); + + this.moveNubs[(int)Edge.TopRight].Location = new PointF(bounds.Right, bounds.Top); + this.moveNubs[(int)Edge.TopRight].Shape = MoveNubShape.Circle; + + this.moveNubs[(int)Edge.Left].Location = new PointF(bounds.Left, (bounds.Top + bounds.Bottom) / 2.0f); + this.moveNubs[(int)Edge.Right].Location = new PointF(bounds.Right, (bounds.Top + bounds.Bottom) / 2.0f); + + this.moveNubs[(int)Edge.BottomLeft].Location = new PointF(bounds.Left, bounds.Bottom); + this.moveNubs[(int)Edge.BottomLeft].Shape = MoveNubShape.Circle; + + this.moveNubs[(int)Edge.Bottom].Location = new PointF((bounds.Left + bounds.Right) / 2.0f, bounds.Bottom); + + this.moveNubs[(int)Edge.BottomRight].Location = new PointF(bounds.Right, bounds.Bottom); + this.moveNubs[(int)Edge.BottomRight].Shape = MoveNubShape.Circle; + } + + if (this.rotateNub == null) + { + this.rotateNub = new RotateNubRenderer(this.RendererList); + rotateNub.Visible = false; + this.RendererList.Add(this.rotateNub, false); + } + + if (Selection.IsEmpty) + { + foreach (SurfaceBoxRenderer nub in this.moveNubs) + { + nub.Visible = false; + } + + this.rotateNub.Visible = false; + } + else + { + foreach (MoveNubRenderer nub in this.moveNubs) + { + nub.Visible = !tracking || currentMode == Mode.Scale; + nub.Transform = Selection.GetInterimTransformReadOnly(); + } + } + } + + protected void HideNubs() + { + if (this.moveNubs != null) + { + foreach (SurfaceBoxRenderer sbr in this.moveNubs) + { + sbr.Visible = false; + } + } + + if (this.rotateNub != null) + { + this.rotateNub.Visible = false; + } + } + + protected Edge FlipEdgeVertically(Edge flipMe) + { + Edge flippedEdge; + + switch (flipMe) + { + default: + throw new InvalidEnumArgumentException(); + + case Edge.Bottom: + flippedEdge = Edge.Top; + break; + + case Edge.BottomLeft: + flippedEdge = Edge.TopLeft; + break; + + case Edge.BottomRight: + flippedEdge = Edge.TopRight; + break; + + case Edge.Left: + flippedEdge = Edge.Left; + break; + + case Edge.None: + flippedEdge = Edge.None; + break; + + case Edge.Right: + flippedEdge = Edge.Right; + break; + + case Edge.Top: + flippedEdge = Edge.Bottom; + break; + + case Edge.TopLeft: + flippedEdge = Edge.BottomLeft; + break; + + case Edge.TopRight: + flippedEdge = Edge.BottomRight; + break; + } + + return flippedEdge; + } + + + // Constrains the given width and height to the aspect ratio of this.liftedBounds + protected void ConstrainScaling(RectangleF liftedBounds, float startWidth, float startHeight, + float newWidth, float newHeight, out float newXScale, out float newYScale) + { + float hRatio = newWidth / (float)liftedBounds.Width; + float vRatio = newHeight / (float)liftedBounds.Height; + + float bestScale = Math.Min(hRatio, vRatio); + float bestWidth = (float)liftedBounds.Width * bestScale; + float bestHeight = (float)liftedBounds.Height * bestScale; + + newXScale = bestWidth / startWidth; + newYScale = bestHeight / startHeight; + } + + // Constrains to nearest 15 degree angle + protected float ConstrainAngle(float angle) + { + while (angle < 0) + { + angle += 360.0f; + } + + int iangle = (int)angle; + int lowerBound = (iangle / 15) * 15; + int upperBound = lowerBound + 15; + float lowerDiff = Math.Abs(angle - (float)lowerBound); + float upperDiff = Math.Abs(angle - (float)upperBound); + + float newAngle; + + if (lowerDiff < upperDiff) + { + newAngle = (float)lowerBound; + } + else + { + newAngle = (float)upperBound; + } + + if (newAngle > 180.0f) + { + newAngle -= 360.0f; + } + + return newAngle; + } + + protected override void OnKeyPress(Keys key) + { + if (!tracking) + { + int dx = 0; + int dy = 0; + + if ((key & Keys.KeyCode) == Keys.Left) + { + dx = -1; + } + else if ((key & Keys.KeyCode) == Keys.Right) + { + dx = +1; + } + else if ((key & Keys.KeyCode) == Keys.Up) + { + dy = -1; + } + else if ((key & Keys.KeyCode) == Keys.Down) + { + dy = +1; + } + + if ((key & Keys.Control) != Keys.None) + { + dx *= 10; + dy *= 10; + } + + // Simulate moving the selection + if (dx != 0 || dy != 0) + { + Point pos = Cursor.Position; + Point docPos = new Point(-70000, -70000); + Point newDocPos = new Point(docPos.X + dx, docPos.Y + dy); + OnMouseDown(new MouseEventArgs(MouseButtons.Left, 0, docPos.X, docPos.Y, 0)); + OnMouseMove(new MouseEventArgs(MouseButtons.Left, 0, newDocPos.X, newDocPos.Y, 0)); + OnMouseUp(new MouseEventArgs(MouseButtons.Left, 0, newDocPos.X, newDocPos.Y, 0)); + } + } + else + { + base.OnKeyPress(key); + } + } + + protected abstract void OnLift(MouseEventArgs e); + protected abstract void Drop(); + protected abstract void PreRender(); + protected abstract void Render(Point newOffset, bool useNewOffset); + protected abstract void PushContextHistoryMemento(); + + protected void Lift(MouseEventArgs e) + { + this.PushContextHistoryMemento(); + + this.context.seriesGuid = Guid.NewGuid(); + DetermineMoveMode(e, out this.context.currentMode, out this.context.startEdge); + + // lift! + this.context.startBounds = this.context.liftedBounds; + this.context.liftedBounds = Selection.GetBoundsF(false); + this.context.startMouseXY = new Point(e.X, e.Y); + this.context.offset = new Point(0, 0); + this.context.startAngle = 0.0f; + this.context.lifted = true; + this.context.liftTransform = Selection.GetCumulativeTransformCopy(); + + OnLift(e); + + PositionNubs(this.context.currentMode); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (tracking) + { + return; + } + + bool determinedMoveMode = false; + Mode newMode = Mode.Translate; + Edge newEdge = Edge.None; + + if (Selection.IsEmpty) + { + SelectionHistoryMemento shm = new SelectionHistoryMemento( + HistoryFunctions.SelectAllFunction.StaticName, + PdnResources.GetImageResource("Icons.MenuEditSelectAllIcon.png"), + DocumentWorkspace); + + DocumentWorkspace.History.PushNewMemento(shm); + + DocumentWorkspace.Selection.PerformChanging(); + DocumentWorkspace.Selection.Reset(); + DocumentWorkspace.Selection.SetContinuation(Document.Bounds, CombineMode.Replace); + DocumentWorkspace.Selection.CommitContinuation(); + DocumentWorkspace.Selection.PerformChanged(); + + if (e.Button == MouseButtons.Right) + { + newMode = Mode.Rotate; + } + else + { + newMode = Mode.Translate; + } + + newEdge = Edge.None; + + determinedMoveMode = true; + } + + DocumentWorkspace.EnableSelectionOutline = this.enableOutline; + + if (!context.lifted) + { + Lift(e); + } + + PushContextHistoryMemento(); + + if (!determinedMoveMode) + { + DetermineMoveMode(e, out newMode, out newEdge); + determinedMoveMode = true; + } + + if (this.context.deltaTransform != null) + { + this.context.deltaTransform.Dispose(); + this.context.deltaTransform = null; + } + + this.context.deltaTransform = new Matrix(); + this.context.deltaTransform.Reset(); + + if (newMode == Mode.Translate || + newMode == Mode.Scale || + newMode != this.context.currentMode || + newMode == Mode.Rotate) + { + this.context.startBounds = Selection.GetBoundsF(); + this.context.startMouseXY = new Point(e.X, e.Y); + this.context.offset = new Point(0, 0); + + if (this.context.baseTransform != null) + { + this.context.baseTransform.Dispose(); + this.context.baseTransform = null; + } + + this.context.baseTransform = Selection.GetInterimTransformCopy(); + } + + this.context.startEdge = newEdge; + this.context.currentMode = newMode; + PositionNubs(this.context.currentMode); + + tracking = true; + this.rotateNub.Visible = (this.context.currentMode == Mode.Rotate); + + if (this.context.startPath != null) + { + this.context.startPath.Dispose(); + this.context.startPath = null; + } + + this.context.startPath = Selection.CreatePath(); + this.context.startAngle = Utility.GetAngleOfTransform(Selection.GetInterimTransformReadOnly()); + + SelectionHistoryMemento sha1 = new SelectionHistoryMemento(this.Name, this.Image, this.DocumentWorkspace); + this.currentHistoryMementos.Add(sha1); + + OnMouseMove(e); + + if (this.enableOutline) + { + DocumentWorkspace.ResetOutlineWhiteOpacity(); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + StringBuilder sbLogger = new StringBuilder(); + + try + { + OnMouseMoveImpl(e, sbLogger); + } + + catch (Exception ex) + { + throw new ApplicationException("Tracing data: " + sbLogger.ToString(), ex); + } + } + + private void OnMouseMoveImpl(MouseEventArgs e, StringBuilder sbLogger) + { + if (!this.tracking) + { + sbLogger.Append("1 "); + Cursor cursor = this.moveToolCursor; + + for (int i = 0; i < this.moveNubs.Length; ++i) + { + sbLogger.Append("2 "); + MoveNubRenderer nub = this.moveNubs[i]; + sbLogger.Append("3 "); + + if (nub.Visible && nub.IsPointTouching(new Point(e.X, e.Y), true)) + { + sbLogger.Append("4 "); + cursor = this.handCursor; + break; + } + } + + this.Cursor = cursor; + sbLogger.Append("5 "); + } + else + { + sbLogger.Append("6 "); + if (this.context.currentMode != Mode.Translate) + { + sbLogger.Append("7 "); + this.Cursor = this.handCursorMouseDown; + } + + sbLogger.Append("8 "); + Point newMouseXY = new Point(e.X, e.Y); + Point newOffset = new Point(newMouseXY.X - context.startMouseXY.X, newMouseXY.Y - context.startMouseXY.Y); + + PreRender(); + + this.dontDrop = true; + + sbLogger.Append("9 "); + Selection.PerformChanging(); + + using (Matrix translateMatrix = new Matrix()) + { + RectangleF rect; + translateMatrix.Reset(); + + if (this.context.baseTransform != null) + { + Selection.SetInterimTransform(this.context.baseTransform); + } + + Matrix interim = Selection.GetInterimTransformCopy(); + + switch (this.context.currentMode) + { + case Mode.Translate: + translateMatrix.Translate((float)newOffset.X, (float)newOffset.Y, MatrixOrder.Append); + break; + + case Mode.Rotate: + rect = this.context.liftedBounds; + PointF center = new PointF(rect.X + (rect.Width / 2.0f), rect.Y + (rect.Height / 2.0f)); + center = Utility.TransformOnePoint(interim, center); + double theta1 = Math.Atan2(context.startMouseXY.Y - center.Y, context.startMouseXY.X - center.X); + double theta2 = Math.Atan2(e.Y - center.Y, e.X - center.X); + double thetaDelta = theta2 - theta1; + this.angleDelta = (float)(thetaDelta * (180.0f / Math.PI)); + float angle = this.context.startAngle + this.angleDelta; + + if ((ModifierKeys & Keys.Shift) != 0) + { + angle = ConstrainAngle(angle); + angleDelta = angle - this.context.startAngle; + } + + translateMatrix.RotateAt(angleDelta, center, MatrixOrder.Append); + this.rotateNub.Location = center; + this.rotateNub.Angle = this.context.startAngle + angleDelta; + break; + + case Mode.Scale: + PointF xyAxes = GetEdgeVector(this.context.startEdge); + PointF xAxis = new PointF(xyAxes.X, 0); + PointF yAxis = new PointF(0, xyAxes.Y); + PointF edgeX = Utility.TransformOneVector(interim, xAxis); + PointF edgeY = Utility.TransformOneVector(interim, yAxis); + PointF edgeXN = Utility.NormalizeVector2(edgeX); + PointF edgeYN = Utility.NormalizeVector2(edgeY); + + PointF xu; + float xulen; + PointF xv; + Utility.GetProjection((PointF)newOffset, edgeXN, out xu, out xulen, out xv); + + PointF yu; + float yulen; + PointF yv; + Utility.GetProjection((PointF)newOffset, edgeYN, out yu, out yulen, out yv); + + PdnGraphicsPath startPath2 = this.context.startPath.Clone(); + RectangleF sp2Bounds = startPath2.GetBounds(); + + PointF sp2BoundsCenter = new PointF((sp2Bounds.Left + sp2Bounds.Right) / 2.0f, + (sp2Bounds.Top + sp2Bounds.Bottom) / 2.0f); + + float tAngle = Utility.GetAngleOfTransform(interim); + bool isFlipped = Utility.IsTransformFlipped(interim); + + using (Matrix spm = new Matrix()) + { + spm.Reset(); + spm.RotateAt(-tAngle, sp2BoundsCenter, MatrixOrder.Append); + translateMatrix.RotateAt(-tAngle, sp2BoundsCenter, MatrixOrder.Append); + startPath2.Transform(spm); + } + + RectangleF spBounds2 = startPath2.GetBounds(); + + startPath2.Dispose(); + startPath2 = null; + + float xTranslate; + float yTranslate; + bool allowConstrain; + + Edge theEdge = this.context.startEdge; + + // If the transform is flipped, then GetTransformAngle will return 180 degrees + // even though no rotation has actually taken place. Thus we have to scratch + // our head and go "hmm, let's make some adjustments to this." Otherwise stretching + // the top and bottom nubs goes in the wrong direction. + if (isFlipped) + { + theEdge = FlipEdgeVertically(theEdge); + } + + switch (theEdge) + { + default: + throw new InvalidEnumArgumentException(); + + case Edge.TopLeft: + allowConstrain = true; + xTranslate = -spBounds2.X - spBounds2.Width; + yTranslate = -spBounds2.Y - spBounds2.Height; + break; + + case Edge.Top: + allowConstrain = false; + xTranslate = 0; + yTranslate = -spBounds2.Y - spBounds2.Height; + break; + + case Edge.TopRight: + allowConstrain = true; + xTranslate = -spBounds2.X; + yTranslate = -spBounds2.Y - spBounds2.Height; + break; + + case Edge.Left: + allowConstrain = false; + xTranslate = -spBounds2.X - spBounds2.Width; + yTranslate = 0; + break; + + case Edge.Right: + allowConstrain = false; + xTranslate = -spBounds2.X; + yTranslate = 0; + break; + + case Edge.BottomLeft: + allowConstrain = true; + xTranslate = -spBounds2.X - spBounds2.Width; + yTranslate = -spBounds2.Y; + break; + + case Edge.Bottom: + allowConstrain = false; + xTranslate = 0; + yTranslate = -spBounds2.Y; + break; + + case Edge.BottomRight: + allowConstrain = true; + xTranslate = -spBounds2.X; + yTranslate = -spBounds2.Y; + break; + } + + translateMatrix.Translate(xTranslate, yTranslate, MatrixOrder.Append); + + float newWidth = spBounds2.Width + xulen; + float newHeight = spBounds2.Height + yulen; + float xScale = newWidth / spBounds2.Width; + float yScale = newHeight / spBounds2.Height; + + if (allowConstrain && (this.ModifierKeys & Keys.Shift) != 0) + { + ConstrainScaling(this.context.liftedBounds, spBounds2.Width, spBounds2.Height, + newWidth, newHeight, out xScale, out yScale); + } + + translateMatrix.Scale(xScale, yScale, MatrixOrder.Append); + translateMatrix.Translate(-xTranslate, -yTranslate, MatrixOrder.Append); + translateMatrix.RotateAt(+tAngle, sp2BoundsCenter, MatrixOrder.Append); + + break; + + default: + throw new InvalidEnumArgumentException(); + } + + this.context.deltaTransform.Reset(); + this.context.deltaTransform.Multiply(this.context.liftTransform, MatrixOrder.Append); + this.context.deltaTransform.Multiply(translateMatrix, MatrixOrder.Append); + + translateMatrix.Multiply(this.context.baseTransform, MatrixOrder.Prepend); + + Selection.SetInterimTransform(translateMatrix); + + interim.Dispose(); + interim = null; + } + + // advertise our angle of rotation to any host (i.e. mainform) that might want to use that information + this.hostShouldShowAngle = this.rotateNub.Visible; + this.hostAngle = -this.rotateNub.Angle; + + Selection.PerformChanged(); + dontDrop = false; + + Render(newOffset, true); + Update(); + + sbLogger.Append("a "); + this.context.offset = newOffset; + + sbLogger.Append("b "); + + if (this.enableOutline) + { + DocumentWorkspace.ResetOutlineWhiteOpacity(); + } + + sbLogger.Append("c "); + } + + sbLogger.Append("d "); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + DocumentWorkspace.EnableSelectionOutline = true; + base.OnMouseUp (e); + } + + public MoveToolBase(DocumentWorkspace documentWorkspace, ImageResource toolBarImage, string name, + string helpText, char hotKey, bool skipIfActiveOnHotKey, ToolBarConfigItems toolBarConfigItems) + : base(documentWorkspace, toolBarImage, name, helpText, hotKey, skipIfActiveOnHotKey, toolBarConfigItems) + { + } + } +} diff --git a/src/tools/PaintBrushTool.cs b/src/tools/PaintBrushTool.cs new file mode 100644 index 0000000..4f54637 --- /dev/null +++ b/src/tools/PaintBrushTool.cs @@ -0,0 +1,349 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class PaintBrushTool + : Tool + { + private bool mouseDown; + private Brush brush; + private MouseButtons mouseButton; + private List savedRects; + private PointF lastMouseXY; + private PointF lastNorm; + private PointF lastDir; + private RenderArgs renderArgs; + private BitmapLayer bitmapLayer; + private Cursor cursorMouseDown; + private Cursor cursorMouseUp; + private BrushPreviewRenderer previewRenderer; + + protected override bool SupportsInk + { + get + { + return true; + } + } + + protected override void OnMouseEnter() + { + this.previewRenderer.Visible = true; + base.OnMouseEnter(); + } + + protected override void OnMouseLeave() + { + this.previewRenderer.Visible = false; + base.OnMouseLeave(); + } + + protected override void OnActivate() + { + base.OnActivate(); + + cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.PaintBrushToolCursor.cur")); + cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PaintBrushToolCursorMouseDown.cur")); + Cursor = cursorMouseUp; + + this.savedRects = new List(); + + if (ActiveLayer != null) + { + bitmapLayer = (BitmapLayer)ActiveLayer; + renderArgs = new RenderArgs(bitmapLayer.Surface); + } + else + { + bitmapLayer = null; + renderArgs = null; + } + + this.previewRenderer = new BrushPreviewRenderer(this.RendererList); + this.RendererList.Add(this.previewRenderer, false); + + mouseDown = false; + } + + protected override void OnDeactivate() + { + if (mouseDown) + { + OnStylusUp(new StylusEventArgs(mouseButton, 0, lastMouseXY.X, lastMouseXY.Y, 0)); + } + + this.RendererList.Remove(this.previewRenderer); + this.previewRenderer.Dispose(); + this.previewRenderer = null; + + this.savedRects = null; + + if (renderArgs != null) + { + renderArgs.Dispose(); + renderArgs = null; + } + + bitmapLayer = null; + + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + if (cursorMouseDown != null) + { + cursorMouseDown.Dispose(); + cursorMouseDown = null; + } + + base.OnDeactivate(); + } + + private float GetWidth(float Pressure) + { + return Pressure * Pressure * AppEnvironment.PenInfo.Width * 0.5f; + } + + protected override void OnStylusDown(StylusEventArgs e) + { + base.OnStylusDown(e); + + if (mouseDown) + { + return; + } + + ClearSavedMemory(); + + this.previewRenderer.Visible = false; + + Cursor = cursorMouseDown; + + if (((e.Button & MouseButtons.Left) == MouseButtons.Left) || + ((e.Button & MouseButtons.Right) == MouseButtons.Right)) + { + mouseButton = e.Button; + + if ((mouseButton & MouseButtons.Left) == MouseButtons.Left) + { + brush = AppEnvironment.CreateBrush(false); + } + else if ((mouseButton & MouseButtons.Right) == MouseButtons.Right) + { + brush = AppEnvironment.CreateBrush(true); + } + + lastMouseXY.X = e.Fx; + lastMouseXY.Y = e.Fy; + + mouseDown = true; + mouseButton = e.Button; + + using (PdnRegion clipRegion = Selection.CreateRegion()) + { + renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace); + } + + this.OnStylusMove(new StylusEventArgs(e.Button, e.Clicks, unchecked(e.Fx + 0.01f), e.Fy, e.Delta, e.Pressure)); + } + } + + private PointF[] MakePolygon(PointF a, PointF b, PointF c, PointF d) + { + PointF dirA = new PointF(a.X - b.X, a.Y - b.Y); + PointF dirB = new PointF(c.X - d.X, c.Y - d.Y); + + // Swap points as necessary to keep the polygon winding one direction + if (dirA.X * dirB.X + dirA.Y * dirB.Y > 0) + { + return new PointF[] { a, b, d, c }; + } + else + { + return new PointF[] { a, b, c, d }; + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (this.mouseDown && e.Button != MouseButtons.None) + { + // This is done so that if drawing falls behind due to a + // large queue of stylus inputs, it won't do any updates + // until it's done. This is accomplished by only updating + // when a MouseMove is caught + Update(); + } + + base.OnMouseMove(e); + } + + protected override void OnStylusMove(StylusEventArgs e) + { + base.OnStylusMove(e); + PointF currMouseXY = new PointF(e.Fx, e.Fy); + + if (mouseDown && ((e.Button & mouseButton) != MouseButtons.None)) + { + float pressure = GetWidth(e.Pressure); + float length; + PointF a = lastMouseXY; + PointF b = currMouseXY; + PointF dir = new PointF(b.X - a.X, b.Y - a.Y); + PointF norm; + PointF[] poly; + RectangleF dotRect = Utility.RectangleFromCenter(currMouseXY, pressure); + + if (pressure > 0.5f) + { + renderArgs.Graphics.PixelOffsetMode = PixelOffsetMode.Half; + } + else + { + renderArgs.Graphics.PixelOffsetMode = PixelOffsetMode.None; + } + + // save direction before normalizing + lastDir = dir; + + // normalize + length = Utility.Magnitude(dir); + dir.X /= length; + dir.Y /= length; + + // compute normal vector, calculate perpendicular offest from stroke for width + norm = new PointF(dir.Y, -dir.X); + norm.X *= pressure; + norm.Y *= pressure; + + a.X -= dir.X * 0.1666f; + a.Y -= dir.Y * 0.1666f; + + lastNorm = norm; + + poly = MakePolygon( + new PointF(a.X - lastNorm.X, a.Y - lastNorm.Y), + new PointF(a.X + lastNorm.X, a.Y + lastNorm.Y), + new PointF(b.X + norm.X, b.Y + norm.Y), + new PointF(b.X - norm.X, b.Y - norm.Y)); + + RectangleF saveRect = RectangleF.Union( + dotRect, + RectangleF.Union( + Utility.PointsToRectangle(poly[0], poly[1]), + Utility.PointsToRectangle(poly[2], poly[3]))); + + saveRect.Inflate(2.0f, 2.0f); // account for anti-aliasing + + saveRect.Intersect(ActiveLayer.Bounds); + + // drawing outside of the canvas is a no-op, so don't do anything in that case! + // also make sure we're within the clip region + if (saveRect.Width > 0 && saveRect.Height > 0 && renderArgs.Graphics.IsVisible(saveRect)) + { + Rectangle saveRectRounded = Utility.RoundRectangle(saveRect); + saveRectRounded.Intersect(ActiveLayer.Bounds); + + if (saveRectRounded.Width > 0 && saveRectRounded.Height > 0) + { + SaveRegion(null, saveRectRounded); + this.savedRects.Add(saveRectRounded); + + if (AppEnvironment.AntiAliasing) + { + renderArgs.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + } + else + { + renderArgs.Graphics.SmoothingMode = SmoothingMode.None; + } + + renderArgs.Graphics.CompositingMode = AppEnvironment.GetCompositingMode(); + + renderArgs.Graphics.FillEllipse(brush, dotRect); + + // bail out early if the mouse hasn't even moved. If we don't bail out, we'll get a 0-distance move, which will result in a div-by-0 + if (lastMouseXY != currMouseXY) + { + renderArgs.Graphics.FillPolygon(brush, poly, FillMode.Winding); + } + } + + bitmapLayer.Invalidate(saveRectRounded); + } + + lastNorm = norm; + lastMouseXY = currMouseXY; + } + else + { + lastMouseXY = currMouseXY; + lastNorm = PointF.Empty; + lastDir = PointF.Empty; + this.previewRenderer.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + } + + this.previewRenderer.BrushLocation = currMouseXY; + } + + protected override void OnStylusUp(StylusEventArgs e) + { + base.OnStylusUp(e); + + Cursor = cursorMouseUp; + + if (mouseDown) + { + this.previewRenderer.Visible = true; + mouseDown = false; + + if (this.savedRects.Count > 0) + { + PdnRegion saveMeRegion = Utility.RectanglesToRegion(this.savedRects.ToArray()); + HistoryMemento ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, + ActiveLayerIndex, saveMeRegion, this.ScratchSurface); + HistoryStack.PushNewMemento(ha); + saveMeRegion.Dispose(); + this.savedRects.Clear(); + this.ClearSavedMemory(); + } + + this.brush.Dispose(); + this.brush = null; + } + } + + public PaintBrushTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.PaintBrushToolIcon.png"), + PdnResources.GetString("PaintBrushTool.Name"), + PdnResources.GetString("PaintBrushTool.HelpText"), + 'b', + false, + ToolBarConfigItems.Brush | ToolBarConfigItems.Pen | ToolBarConfigItems.Antialiasing | ToolBarConfigItems.AlphaBlending) + { + // initialize any state information you need + mouseDown = false; + } + } +} diff --git a/src/tools/PaintBucketTool.cs b/src/tools/PaintBucketTool.cs new file mode 100644 index 0000000..cf2b38b --- /dev/null +++ b/src/tools/PaintBucketTool.cs @@ -0,0 +1,118 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Diagnostics; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class PaintBucketTool + : FloodToolBase + { + private Cursor cursorMouseUp; + private Brush brush; + + protected override void OnMouseDown(MouseEventArgs e) + { + brush = AppEnvironment.CreateBrush((e.Button != MouseButtons.Left)); + Cursor = Cursors.WaitCursor; + + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + Cursor = cursorMouseUp; + base.OnMouseUp (e); + } + + protected override void OnFillRegionComputed(Point[][] polygonSet) + { + using (PdnGraphicsPath path = new PdnGraphicsPath()) + { + path.AddPolygons(polygonSet); + + using (PdnRegion fillRegion = new PdnRegion(path)) + { + Rectangle boundingBox = fillRegion.GetBoundsInt(); + + Surface surface = ((BitmapLayer)ActiveLayer).Surface; + RenderArgs ra = new RenderArgs(surface); + HistoryMemento ha; + + using (PdnRegion affected = Utility.SimplifyAndInflateRegion(fillRegion)) + { + ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, DocumentWorkspace.ActiveLayerIndex, affected); + } + + ra.Graphics.CompositingMode = AppEnvironment.GetCompositingMode(); + ra.Graphics.FillRegion(brush, fillRegion.GetRegionReadOnly()); + + HistoryStack.PushNewMemento(ha); + ActiveLayer.Invalidate(boundingBox); + Update(); + } + } + } + + protected override void OnActivate() + { + // cursor-transitions + cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.PaintBucketToolCursor.cur")); + Cursor = cursorMouseUp; + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + base.OnDeactivate(); + } + + + public PaintBucketTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.PaintBucketIcon.png"), + PdnResources.GetString("PaintBucketTool.Name"), + PdnResources.GetString("PaintBucketTool.HelpText"), + 'f', + false, + ToolBarConfigItems.Brush | ToolBarConfigItems.Antialiasing | ToolBarConfigItems.AlphaBlending) + { + } + + protected override void Dispose(bool disposing) + { + base.Dispose (disposing); + + if (disposing) + { + if (brush != null) + { + brush.Dispose(); + brush = null; + } + } + } + } +} diff --git a/src/tools/PanTool.cs b/src/tools/PanTool.cs new file mode 100644 index 0000000..fbe2baa --- /dev/null +++ b/src/tools/PanTool.cs @@ -0,0 +1,156 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class PanTool + : Tool + { + private bool tracking = false; + private Point lastMouseXY; + private Cursor cursorMouseDown; + private Cursor cursorMouseUp; + private Cursor cursorMouseInvalid; + private int ignoreMouseMove = 0; + + private bool CanPan() + { + if (DocumentWorkspace.VisibleDocumentRectangleF.Size == Document.Size) + { + return false; + } + else + { + return true; + } + } + + protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e) + { + base.OnMouseDown(e); + + lastMouseXY = new Point(e.X, e.Y); + tracking = true; + + if (CanPan()) + { + Cursor = cursorMouseDown; + } + else + { + Cursor = cursorMouseInvalid; + } + } + + protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e) + { + base.OnMouseUp (e); + + if (CanPan()) + { + Cursor = cursorMouseUp; + } + else + { + Cursor = cursorMouseInvalid; + } + + tracking = false; + } + + protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e) + { + base.OnMouseMove(e); + + if (this.ignoreMouseMove > 0) + { + --this.ignoreMouseMove; + } + else if (tracking) + { + Point mouseXY = new Point(e.X, e.Y); + Size delta = new Size(mouseXY.X - lastMouseXY.X, mouseXY.Y - lastMouseXY.Y); + + if (delta.Width != 0 || delta.Height != 0) + { + PointF scrollPos = DocumentWorkspace.DocumentScrollPositionF; + PointF newScrollPos = new PointF(scrollPos.X - delta.Width, scrollPos.Y - delta.Height); + ++this.ignoreMouseMove; // setting DocumentScrollPosition incurs a MouseMove event + DocumentWorkspace.DocumentScrollPositionF = newScrollPos; + + lastMouseXY = mouseXY; + lastMouseXY.X -= delta.Width; + lastMouseXY.Y -= delta.Height; + } + } + else + { + if (CanPan()) + { + Cursor = cursorMouseUp; + } + else + { + Cursor = cursorMouseInvalid; + } + } + } + + protected override void OnActivate() + { + // cursor-action assignments + this.cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorMouseDown.cur")); + this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursor.cur")); + this.cursorMouseInvalid = new Cursor(PdnResources.GetResourceStream("Cursors.PanToolCursorInvalid.cur")); + this.Cursor = cursorMouseUp; + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (cursorMouseDown != null) + { + cursorMouseDown.Dispose(); + cursorMouseDown = null; + } + + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + if (cursorMouseInvalid != null) + { + cursorMouseInvalid.Dispose(); + cursorMouseInvalid = null; + } + + base.OnDeactivate(); + } + + public PanTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.PanToolIcon.png"), + PdnResources.GetString("PanTool.Name"), + PdnResources.GetString("PanTool.HelpText"), + 'h', + false, + ToolBarConfigItems.None) + { + autoScroll = false; + tracking = false; + } + } +} diff --git a/src/tools/PencilTool.cs b/src/tools/PencilTool.cs new file mode 100644 index 0000000..3d614cd --- /dev/null +++ b/src/tools/PencilTool.cs @@ -0,0 +1,314 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Data; +using PaintDotNet.HistoryMementos; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class PencilTool + : Tool + { + private bool mouseDown = false; + private ColorBgra pencilColor; + private MouseButtons mouseButton; + private BitmapLayer bitmapLayer; + private RenderArgs renderArgs; + private List tracePoints; + private List savedRects; + private PdnRegion clipRegion; + private Point lastPoint; + private Point difference; + private Cursor pencilToolCursor; + private BinaryPixelOp blendOp = new UserBlendOps.NormalBlendOp(); + private BinaryPixelOp copyOp = new BinaryPixelOps.AssignFromRhs(); + + protected override void OnActivate() + { + base.OnActivate(); + + this.pencilToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.PencilToolCursor.cur")); + this.Cursor = this.pencilToolCursor; + + this.savedRects = new List(); + + if (ActiveLayer != null) + { + bitmapLayer = (BitmapLayer)ActiveLayer; + renderArgs = new RenderArgs(bitmapLayer.Surface); + tracePoints = new List(); + } + else + { + bitmapLayer = null; + + if (renderArgs != null) + { + renderArgs.Dispose(); + renderArgs = null; + } + } + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + + if (this.pencilToolCursor != null) + { + this.pencilToolCursor.Dispose(); + this.pencilToolCursor = null; + } + + if (mouseDown) + { + Point lastTracePoint = (Point)tracePoints[tracePoints.Count - 1]; + OnMouseUp(new MouseEventArgs(mouseButton, 0, lastTracePoint.X, lastTracePoint.Y, 0)); + } + + this.savedRects = null; + this.tracePoints = null; + this.bitmapLayer = null; + + if (this.renderArgs != null) + { + this.renderArgs.Dispose(); + this.renderArgs = null; + } + + this.mouseDown = false; + + if (clipRegion != null) + { + clipRegion.Dispose(); + clipRegion = null; + } + } + + // Draws a point, but first intersects it with the selection + private void DrawPoint(RenderArgs ra, Point p, ColorBgra color) + { + if (ra.Surface.Bounds.Contains(p)) + { + if (ra.Graphics.IsVisible(p)) + { + BinaryPixelOp op = AppEnvironment.AlphaBlending ? blendOp : copyOp; + ra.Surface[p.X, p.Y] = op.Apply(ra.Surface[p.X, p.Y], color); + } + } + } + + private void DrawLines(RenderArgs ra, List points, int startIndex, int length, ColorBgra color) + { + // Draw a point in the line + if (points.Count == 0) + { + return; + } + else if (points.Count == 1) + { + Point p = (Point)points[0]; + + if (ra.Surface.Bounds.Contains(p)) + { + DrawPoint(ra, p, color); + } + } + else + { + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + Point[] linePoints = Utility.GetLinePoints(points[i - 1], points[i]); + int startPoint = 0; + + if (i != 1) + { + startPoint = 1; + } + + for (int pi = startPoint; pi < linePoints.Length; ++pi) + { + Point p = linePoints[pi]; + DrawPoint(ra, p, color); + } + } + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (mouseDown) + { + return; + } + + if (((e.Button & MouseButtons.Left) == MouseButtons.Left) || + ((e.Button & MouseButtons.Right) == MouseButtons.Right)) + { + mouseDown = true; + mouseButton = e.Button; + tracePoints = new List(); + bitmapLayer = (BitmapLayer)ActiveLayer; + renderArgs = new RenderArgs(bitmapLayer.Surface); + + if (clipRegion != null) + { + clipRegion.Dispose(); + clipRegion = null; + } + + clipRegion = Selection.CreateRegion(); + renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace); + OnMouseMove(e); + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (mouseDown && ((e.Button & mouseButton) != MouseButtons.None)) + { + Point mouseXY = new Point(e.X, e.Y); + + if (lastPoint == Point.Empty) + { + lastPoint = mouseXY; + } + + difference = new Point(mouseXY.X - lastPoint.X, mouseXY.Y - lastPoint.Y); + + if (tracePoints.Count > 0) + { + Point lastMouseXY = (Point)tracePoints[tracePoints.Count - 1]; + if (lastMouseXY == mouseXY) + { + return; + } + } + + if ((mouseButton & MouseButtons.Left) == MouseButtons.Left) + { + this.pencilColor = AppEnvironment.PrimaryColor; + } + else // if ((mouseButton & MouseButtons.Right) == MouseButtons.Right) + { + // right mouse button = swap primary/secondary + this.pencilColor = AppEnvironment.SecondaryColor; + } + + if (!(tracePoints.Count > 0 && mouseXY == (Point)tracePoints[tracePoints.Count - 1])) + { + tracePoints.Add(mouseXY); + } + + if (ActiveLayer is BitmapLayer) + { + Rectangle saveRect; + + if (tracePoints.Count == 1) + { + saveRect = Utility.PointsToRectangle(mouseXY, mouseXY); + } + else + { + // >1 points + saveRect = Utility.PointsToRectangle((Point)tracePoints[tracePoints.Count - 1], (Point)tracePoints[tracePoints.Count - 2]); + } + + saveRect.Inflate(2, 2); + saveRect.Intersect(ActiveLayer.Bounds); + + // drawing outside of the canvas is a no-op, so don't do anything in that case! + // also make sure it's within the clipping bounds + if (saveRect.Width > 0 && saveRect.Height > 0 && renderArgs.Graphics.IsVisible(saveRect)) + { + SaveRegion(null, saveRect); + this.savedRects.Add(saveRect); + + int startIndex; + int length; + + if (tracePoints.Count == 1) + { + startIndex = 0; + length = 1; + } + else + { + startIndex = tracePoints.Count - 2; + length = 2; + } + + DrawLines(this.renderArgs, tracePoints, startIndex, length, pencilColor); + + bitmapLayer.Invalidate(saveRect); + Update(); + } + } + else + { + // will have to do something here if we add other layer types besides BitmapLayer + } + + lastPoint = mouseXY; + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (mouseDown) + { + OnMouseMove(e); + mouseDown = false; + + if (savedRects.Count > 0) + { + Rectangle[] savedScans = this.savedRects.ToArray(); + PdnRegion saveMeRegion = Utility.RectanglesToRegion(savedScans); + + HistoryMemento ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, + ActiveLayerIndex, saveMeRegion, ScratchSurface); + + HistoryStack.PushNewMemento(ha); + saveMeRegion.Dispose(); + this.savedRects.Clear(); + ClearSavedMemory(); + } + + tracePoints = null; + } + } + + public PencilTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.PencilToolIcon.png"), + PdnResources.GetString("PencilTool.Name"), + PdnResources.GetString("PencilTool.HelpText"), + 'p', + true, + ToolBarConfigItems.AlphaBlending) + { + // initialize any state information you need + mouseDown = false; + } + } +} diff --git a/src/tools/RecoloringTool.cs b/src/tools/RecoloringTool.cs new file mode 100644 index 0000000..17065d3 --- /dev/null +++ b/src/tools/RecoloringTool.cs @@ -0,0 +1,837 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class RecolorTool + : Tool + { + private bool mouseDown; + + private Point lastMouseXY; + private MouseButtons mouseButton; + private RenderArgs renderArgs; + private BitmapLayer bitmapLayer; + private ArrayList savedSurfaces; + private BrushPreviewRenderer previewRenderer; + + private float penWidth; + private int ceilingPenWidth; + private int halfPenWidth; + private ColorBgra colorToReplace; + private ColorBgra colorReplacing; + private Cursor cursorMouseDown; + private Cursor cursorMouseUp; + private Cursor cursorMouseDownPickColor; + private Cursor cursorMouseDownAdjustColor; + + // private ColorBgra replacementDiff; + private static ColorBgra colorToleranceBasis = ColorBgra.FromBgra(0x20, 0x20, 0x20, 0x00); + private PdnRegion clipRegion; + private UserBlendOps.NormalBlendOp blendOp = new UserBlendOps.NormalBlendOp(); + private bool hasDrawn; + private Keys modifierDown; + + // AA stuff + private BitVector2D isPointAlreadyAA; + private Surface aaPoints; + + public ColorBgra AAPoints(int x, int y) + { + return aaPoints[x, y]; + } + + public void AAPointsAdd(int x, int y, ColorBgra color) + { + aaPoints[x, y] = color; + isPointAlreadyAA[x, y] = true; + } + + public void AAPointsRemove(int x, int y) + { + isPointAlreadyAA[x, y] = false; + } + + private bool IsPointAlreadyAntiAliased(int x, int y) + { + return isPointAlreadyAA[x, y]; + } + + private bool IsPointAlreadyAntiAliased(Point pt) + { + return IsPointAlreadyAntiAliased(pt.X, pt.Y); + } + + // RenderArgs specifically for a brush mask + private RenderArgs brushRenderArgs; + + private int myTolerance; + + private bool IsColorInTolerance(ColorBgra colorA, ColorBgra colorB) + { + return Utility.ColorDifference(colorA, colorB) <= myTolerance; + } + + private void RestrictTolerance() + { + int difference = Utility.ColorDifference(colorReplacing, colorToReplace); + + if (myTolerance > difference) + { + myTolerance = difference; + } + } + + protected override void OnMouseEnter() + { + this.previewRenderer.Visible = true; + base.OnMouseEnter(); + } + + protected override void OnMouseLeave() + { + this.previewRenderer.Visible = false; + base.OnMouseLeave(); + } + + protected override void OnActivate() + { + base.OnActivate(); + + // initialize any state information you need + cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.RecoloringToolCursor.cur")); + cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.GenericToolCursorMouseDown.cur")); + cursorMouseDownPickColor = new Cursor(PdnResources.GetResourceStream("Cursors.RecoloringToolCursorPickColor.cur")); + cursorMouseDownAdjustColor = new Cursor(PdnResources.GetResourceStream("Cursors.RecoloringToolCursorAdjustColor.cur")); + + this.previewRenderer = new BrushPreviewRenderer(this.RendererList); + this.RendererList.Add(this.previewRenderer, false); + + Cursor = cursorMouseUp; + mouseDown = false; + + // fetch colors from workspace palette + this.colorToReplace = this.AppEnvironment.PrimaryColor; + this.colorReplacing = this.AppEnvironment.SecondaryColor; + + this.aaPoints = this.ScratchSurface; + this.isPointAlreadyAA = new BitVector2D(aaPoints.Width, aaPoints.Height); + + if (savedSurfaces != null) + { + foreach (PlacedSurface ps in savedSurfaces) + { + ps.Dispose(); + } + } + + savedSurfaces = new ArrayList(); + + if (ActiveLayer != null) + { + bitmapLayer = (BitmapLayer)ActiveLayer; + renderArgs = new RenderArgs(bitmapLayer.Surface); + } + else + { + bitmapLayer = null; + renderArgs = null; + } + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + + if (mouseDown) + { + OnMouseUp(new MouseEventArgs(mouseButton, 0, lastMouseXY.X, lastMouseXY.Y, 0)); + } + + this.RendererList.Remove(this.previewRenderer); + this.previewRenderer.Dispose(); + this.previewRenderer = null; + + if (savedSurfaces != null) + { + if (savedSurfaces != null) + { + foreach (PlacedSurface ps in savedSurfaces) + { + ps.Dispose(); + } + } + + savedSurfaces.Clear(); + savedSurfaces = null; + } + + renderArgs.Dispose();; + renderArgs = null; + + aaPoints = null; + renderArgs = null; + bitmapLayer = null; + + if (clipRegion != null) + { + clipRegion.Dispose(); + clipRegion = null; + } + + + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + if (cursorMouseDown != null) + { + cursorMouseDown.Dispose(); + cursorMouseDown = null; + } + + if (cursorMouseDownPickColor != null) + { + cursorMouseDownPickColor.Dispose(); + cursorMouseDownPickColor = null; + } + + if (cursorMouseDownAdjustColor != null) + { + cursorMouseDownAdjustColor.Dispose(); + cursorMouseDownAdjustColor = null; + } + } + + private ColorBgra LiftColor(int x, int y) + { + return ((BitmapLayer)ActiveLayer).Surface[x, y]; + } + + /// + /// Picks up the color under the mouse and assigns to the forecolor (or backcolor). + /// If assigning to the forecolor, the backcolor will be adjusted respective to the + /// difference of the old forecolor versus the new forecolor. + /// + /// + private void AdjustDrawingColor(MouseEventArgs e) + { + ColorBgra oldColor; + + if (BtnDownMouseLeft(e)) + { + oldColor = this.AppEnvironment.PrimaryColor; + PickColor(e); + + this.AppEnvironment.SecondaryColor = AdjustColorDifference(oldColor, + this.AppEnvironment.PrimaryColor, this.AppEnvironment.SecondaryColor); + } + + if (BtnDownMouseRight(e)) + { + oldColor = this.AppEnvironment.SecondaryColor; + PickColor(e); + + this.AppEnvironment.PrimaryColor = AdjustColorDifference(oldColor, + this.AppEnvironment.SecondaryColor, this.AppEnvironment.PrimaryColor); + } + } + + private byte AdjustColorByte(byte oldByte, byte newByte, byte basisByte) + { + if (oldByte > newByte) + { + return Utility.ClampToByte(basisByte - (oldByte - newByte)); + } + else + { + return Utility.ClampToByte(basisByte + (newByte - oldByte)); + } + } + + /// + /// Returns a ColorBgra shift by the difference between oldcolor and newcolor but using + /// basisColor as the basis. + /// + /// + /// + /// + /// + private ColorBgra AdjustColorDifference(ColorBgra oldColor, ColorBgra newColor, ColorBgra basisColor) + { + ColorBgra returnColor; + + // eliminate testing for the "equal to" case + returnColor = basisColor; + + returnColor.B = AdjustColorByte(oldColor.B, newColor.B, basisColor.B); + returnColor.G = AdjustColorByte(oldColor.G, newColor.G, basisColor.G); + returnColor.R = AdjustColorByte(oldColor.R, newColor.R, basisColor.R); + + return returnColor; + } + + private void PickColor(MouseEventArgs e) + { + if (!DocumentWorkspace.Document.Bounds.Contains(e.X, e.Y)) + { + return; + } + + // if we managed to get here without any mouse buttons down + // we return promptly. + if (BtnDownMouseLeft(e) || BtnDownMouseRight(e)) + { + // since the above statement exits if one or the other + if (BtnDownMouseLeft(e)) + { + colorReplacing = LiftColor(e.X, e.Y); + colorReplacing.A = this.AppEnvironment.PrimaryColor.A; + this.AppEnvironment.PrimaryColor = colorReplacing; + } + else + { + colorToReplace = LiftColor(e.X, e.Y); + colorToReplace.A = this.AppEnvironment.SecondaryColor.A; + this.AppEnvironment.SecondaryColor = colorToReplace; + } + } + else + { + return; + } + + // before assigned the newly lifted color, we preserve the + // alpha from the user's selected color. + } + + private RenderArgs RenderCircleBrush() + { + // create pen mask surface + Surface brushSurface = new Surface(ceilingPenWidth, ceilingPenWidth); + brushSurface.Clear((ColorBgra)0); + RenderArgs brush = new RenderArgs(brushSurface); + + if (AppEnvironment.AntiAliasing) + { + brush.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + } + else + { + brush.Graphics.SmoothingMode = SmoothingMode.None; + } + + if (AppEnvironment.AntiAliasing) + { + if (penWidth > 2) + { + penWidth = penWidth - 1.0f; + } + else + { + penWidth = penWidth / 2; + } + } + else + { + if (penWidth <= 1.0f) + { + brush.Surface[1, 1] = ColorBgra.Black; + } + else + { + penWidth = (float)Math.Round(penWidth + 1.0f); + } + } + + using (Brush testBrush = new SolidBrush(System.Drawing.Color.Black)) + { + brush.Graphics.FillEllipse(testBrush, 0.0f, 0.0f, penWidth, penWidth); + } + + return brush; + } + + private unsafe void DrawOverPoints(Point start, Point finish, ColorBgra colorToReplaceWith, ColorBgra colorBeingReplaced) + { + ColorBgra colorAdjusted = ColorBgra.FromColor(Color.Empty); + byte dstAlpha; + ColorBgra colorLifted; + Rectangle[] rectSelRegions; + Rectangle rectBrushArea; + Rectangle rectBrushRelativeOffset = new Rectangle(0, 0, 0, 0); + + // special condition for a canvas with no active selection + // create an array of rectangles with a single rectangle + // specifying the size of the canvas + if (Selection.IsEmpty) + { + rectSelRegions = new Rectangle[] { DocumentWorkspace.Document.Bounds }; + } + else + { + rectSelRegions = clipRegion.GetRegionScansReadOnlyInt(); + } + + // code ripped off from clone stamp tool + Point direction = new Point(finish.X - start.X, finish.Y - start.Y); + float length = Utility.Magnitude(direction); + float bw = AppEnvironment.PenInfo.Width / 2; + + float fInc; + if (length == 0.0f) + { + fInc = float.PositiveInfinity; + } + else + { + fInc = (float)Math.Sqrt(bw) / length; + } + + // iterate through all points in the linear stroke + for (float f = 0; f < 1; f += fInc) + { + PointF q = new PointF(finish.X * (1 - f) + f * start.X, + finish.Y * (1 - f) + f * start.Y); + + Point p = Point.Round(q); + + // iterate through all rectangles + foreach (Rectangle rectSel in rectSelRegions) + { + // set the perimeter values for the rectBrushRegion rectangle + // so the area can be intersected with the active + // selection individual recSelRegion rectangle. + rectBrushArea = new Rectangle(p.X - halfPenWidth, p.Y - halfPenWidth, ceilingPenWidth, ceilingPenWidth); + + // test the intersection... + // the perimeter values of rectBrushRegion (above) + // may calculate negative but + // *should* always be clipped to acceptable values by + // by the following intersection. + if (rectBrushArea.IntersectsWith(rectSel)) + { + // a valid intersection was found. + // prune the brush rectangle to fit the intersection. + rectBrushArea.Intersect(rectSel); + for (int y = rectBrushArea.Top; y < rectBrushArea.Bottom; y++) + { + // create a new rectangle for an offset relative to the + // the brush mask + rectBrushRelativeOffset.X = Math.Max(rectSel.X - (p.X - halfPenWidth), 0); + rectBrushRelativeOffset.Y = Math.Max(rectSel.Y - (p.Y - halfPenWidth), 0); + rectBrushRelativeOffset.Size = rectBrushArea.Size; + + ColorBgra *srcBgra; + ColorBgra *dstBgra; + + try + { + + // get the source address of the first pixel from the brush mask. + srcBgra = (ColorBgra *)brushRenderArgs.Surface.GetPointAddress(rectBrushRelativeOffset.Left, + rectBrushRelativeOffset.Y + (y - rectBrushArea.Y)); + + // get the address of the pixel we want to change on the canvas. + dstBgra = (ColorBgra *)renderArgs.Surface.GetPointAddress(rectBrushArea.Left, y); + } + + catch + { + return; + } + + for (int x = rectBrushArea.Left; x < rectBrushArea.Right; x++) + { + if (srcBgra->A != 0) + { + colorLifted = *dstBgra; + + // hasDrawn is set if a pixel endures color replacement so that + // the placed surface will be left alone, otherwise, the placed + // surface will be discarded + // adjust the channel color up and down based on the difference calculated + // from the source. These values are clamped to a byte. It's possible + // that the new color is too dark or too bright to take the whole range + + bool boolCIT = this.IsColorInTolerance(colorLifted, colorBeingReplaced); + bool boolPAAA = false; + + if (AppEnvironment.AntiAliasing) + { + boolPAAA = this.IsPointAlreadyAntiAliased(x, y); + } + + if (boolCIT || boolPAAA) + { + if (boolPAAA) + { + colorAdjusted = (ColorBgra)AAPoints(x, y); + + if (penWidth < 2.0f) + { + colorAdjusted.B = Utility.ClampToByte(colorToReplaceWith.B + (colorAdjusted.B - colorBeingReplaced.B)); + colorAdjusted.G = Utility.ClampToByte(colorToReplaceWith.G + (colorAdjusted.G - colorBeingReplaced.G)); + colorAdjusted.R = Utility.ClampToByte(colorToReplaceWith.R + (colorAdjusted.R - colorBeingReplaced.R)); + colorAdjusted.A = Utility.ClampToByte(colorToReplaceWith.A + (colorAdjusted.A - colorBeingReplaced.A)); + } + } + else + { + colorAdjusted.B = Utility.ClampToByte(colorLifted.B + (colorToReplaceWith.B - colorBeingReplaced.B)); + colorAdjusted.G = Utility.ClampToByte(colorLifted.G + (colorToReplaceWith.G - colorBeingReplaced.G)); + colorAdjusted.R = Utility.ClampToByte(colorLifted.R + (colorToReplaceWith.R - colorBeingReplaced.R)); + colorAdjusted.A = Utility.ClampToByte(colorLifted.A + (colorToReplaceWith.A - colorBeingReplaced.A)); + } + + if ((srcBgra->A != 255) && AppEnvironment.AntiAliasing) + { + colorAdjusted.A = srcBgra->A; + dstAlpha = dstBgra->A; + *dstBgra = blendOp.Apply(*dstBgra, colorAdjusted); + dstBgra->A = dstAlpha; + + if (!this.IsPointAlreadyAntiAliased(x, y)) + { + AAPointsAdd(x, y, colorAdjusted); + } + } + else + { + colorAdjusted.A = (*dstBgra).A; + *dstBgra = colorAdjusted; + + if (boolPAAA) + { + AAPointsRemove(x, y); + } + } + + hasDrawn = true; + } + } + + ++srcBgra; + ++dstBgra; + } + } + } + } + } + } + + private bool KeyDownShiftOnly() + { + return ModifierKeys == Keys.Shift; + } + + private bool KeyDownControlOnly() + { + return ModifierKeys == Keys.Control; + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if ((modifierDown == Keys.Control) || + (modifierDown == Keys.Shift)) + { + return; + } + else + { + if (!mouseDown) + { + if (KeyDownControlOnly()) + { + Cursor = cursorMouseDownPickColor; + } + else if (KeyDownShiftOnly()) + { + Cursor = cursorMouseDownAdjustColor; + } + else + { + base.OnKeyDown(e); + } + } + } + } + + protected override void OnKeyUp(KeyEventArgs e) + { + if (!KeyDownControlOnly() && !KeyDownShiftOnly()) + { + if (!mouseDown) + { + modifierDown = 0; + Cursor = cursorMouseUp; + } + } + + base.OnKeyUp(e); + } + + /// + /// Button down mouse left. Returns true if only the left mouse button is depressed. + /// + /// + /// + private bool BtnDownMouseLeft(MouseEventArgs e) + { + return(e.Button == MouseButtons.Left); + } + + /// + /// Button down mouse right. Returns true if only the right mouse is depressed. + /// + /// + /// + private bool BtnDownMouseRight(MouseEventArgs e) + { + return(e.Button == MouseButtons.Right); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (mouseDown) + { + return; + } + + if (BtnDownMouseLeft(e) || BtnDownMouseRight(e)) + { + this.previewRenderer.Visible = false; + + mouseDown = true; + Cursor = cursorMouseDown; + + if ((!KeyDownControlOnly()) && (!KeyDownShiftOnly())) + { + mouseButton = e.Button; + + lastMouseXY.X = e.X; + lastMouseXY.Y = e.Y; + + // parses and establishes the active selection area + if (clipRegion != null) + { + clipRegion.Dispose(); + clipRegion = null; + } + + clipRegion = Selection.CreateRegion(); + renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace); + + // find the replacement color and the color to replace + colorReplacing = AppEnvironment.PrimaryColor; + colorToReplace = AppEnvironment.SecondaryColor; + penWidth = AppEnvironment.PenInfo.Width; + + // get the pen width find the ceiling integer of half of the pen width + ceilingPenWidth = (int)Math.Max(Math.Ceiling(penWidth), 3); + + // used only for cursor positioning + halfPenWidth = (int)Math.Ceiling(penWidth / 2.0f); + + // set hasDrawn to false since nothing has been drawn + hasDrawn = false; + + // render the circle via GDI+ so the AA techniques can precisely + // mimic GDI+. + this.brushRenderArgs = RenderCircleBrush(); + + // establish tolerance + myTolerance = (int)(AppEnvironment.Tolerance * 256); + + // restrict tolerance so no overlap is permitted + RestrictTolerance(); + OnMouseMove(e); + } + else + { + modifierDown = ModifierKeys; + OnMouseMove(e); + } + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp(e); + + if (!KeyDownShiftOnly() && !KeyDownControlOnly()) + { + Cursor = cursorMouseUp; + } + + if (mouseDown) + { + this.previewRenderer.Visible = true; + + OnMouseMove(e); + + if (savedSurfaces.Count > 0) + { + PdnRegion saveMeRegion = new PdnRegion(); + saveMeRegion.MakeEmpty(); + + foreach (PlacedSurface pi1 in savedSurfaces) + { + saveMeRegion.Union(pi1.Bounds); + } + + PdnRegion simplifiedRegion = Utility.SimplifyAndInflateRegion(saveMeRegion); + + using (IrregularSurface weDrewThis = new IrregularSurface(renderArgs.Surface, simplifiedRegion)) + { + for (int i = savedSurfaces.Count - 1; i >= 0; --i) + { + PlacedSurface ps = (PlacedSurface)savedSurfaces[i]; + ps.Draw(renderArgs.Surface); + ps.Dispose(); + } + + savedSurfaces.Clear(); + + if (hasDrawn) + { + HistoryMemento ha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, + ActiveLayerIndex, simplifiedRegion); + + weDrewThis.Draw(bitmapLayer.Surface); + HistoryStack.PushNewMemento(ha); + } + } + } + + mouseDown = false; + modifierDown = 0; + } + + if (brushRenderArgs != null) + { + if (brushRenderArgs.Surface != null) + { + brushRenderArgs.Surface.Dispose(); + } + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + this.previewRenderer.BrushLocation = new Point(e.X, e.Y); + this.previewRenderer.BrushSize = AppEnvironment.PenInfo.Width / 2.0f; + + if (mouseDown) + { + if (BtnDownMouseLeft(e) || BtnDownMouseRight(e)) + { + if (modifierDown == 0) + { + // if the primary and secondary colors are identical, + // return...there's no point in committing any action + if (colorReplacing == colorToReplace) + { + return; + } + + // get our start and end coordinates, since we need + // to trace along an action line -- the user will expect this behavior + // if we don't, it'll look like a tin can riddled with bullet holes + Point pointStartCorner = lastMouseXY; // start point + Point pointEndCorner = new Point(e.X, e.Y); // end point + + // create the rectangle with the 'a' and 'b' points above + Rectangle inspectionRect = + Utility.PointsToRectangle(pointStartCorner, pointEndCorner); + + // inflate the region to address account for the pen width + // then intersect with the Workspace to "clip" the boundary + // the total area of the clipped rectangle includes the + // width of the pen surrounding the points limited by either + // the canvas perimeter or the selection outline + inspectionRect.Inflate(1 + ceilingPenWidth / 2, 1 + ceilingPenWidth / 2); + inspectionRect.Intersect(ActiveLayer.Bounds); + + // Enforce the selection area restrictions. + // If within the selection area restrictions, build an image history + bool gotWidth = inspectionRect.Width > 0; + bool gotHeight = inspectionRect.Height > 0; + bool isInClip = renderArgs.Graphics.IsVisible(inspectionRect); + + if ((gotWidth) && (gotHeight) && (isInClip)) + { + PlacedSurface savedPS = new PlacedSurface(renderArgs.Surface, inspectionRect); + savedSurfaces.Add(savedPS); + + renderArgs.Graphics.CompositingMode = CompositingMode.SourceOver; + + // check the mouse buttons and if we've made it this far, at least + // one of the mouse buttons (left|right) was depressed + if (BtnDownMouseLeft(e)) + { + this.DrawOverPoints(pointStartCorner, pointEndCorner, colorReplacing, colorToReplace); + } + else + { + this.DrawOverPoints(pointStartCorner, pointEndCorner, colorToReplace, colorReplacing); + } + + bitmapLayer.Invalidate(inspectionRect); + Update(); + } + + // update the lastMouseXY so we know how to "connect the dots" + lastMouseXY = pointEndCorner; + } + else + { + switch (modifierDown & (Keys.Control | Keys.Shift)) + { + case Keys.Control: + PickColor(e); + break; + + case Keys.Shift: + AdjustDrawingColor(e); + break; + + default: + break; + } + } + } + } + } + + public RecolorTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.RecoloringToolIcon.png"), + PdnResources.GetString("RecolorTool.Name"), + PdnResources.GetString("RecolorTool.HelpText"), + 'r', + false, + ToolBarConfigItems.Pen | ToolBarConfigItems.Antialiasing | ToolBarConfigItems.Tolerance) + { + } + } +} \ No newline at end of file diff --git a/src/tools/RectangleSelectTool.cs b/src/tools/RectangleSelectTool.cs new file mode 100644 index 0000000..1d3732e --- /dev/null +++ b/src/tools/RectangleSelectTool.cs @@ -0,0 +1,156 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class RectangleSelectTool + : SelectionTool + { + protected override List TrimShapePath(System.Collections.Generic.List tracePoints) + { + List array = new List(); + + if (tracePoints.Count > 0) + { + array.Add(tracePoints[0]); + + if (tracePoints.Count > 1) + { + array.Add(tracePoints[tracePoints.Count - 1]); + } + } + + return array; + } + + protected override List CreateShape(List tracePoints) + { + Point a = tracePoints[0]; + Point b = tracePoints[tracePoints.Count - 1]; + + Rectangle rect; + + SelectionDrawModeInfo sdmInfo = AppEnvironment.SelectionDrawModeInfo; + switch (sdmInfo.DrawMode) + { + case SelectionDrawMode.Normal: + if ((ModifierKeys & Keys.Shift) != 0) + { + rect = Utility.PointsToConstrainedRectangle(a, b); + } + else + { + rect = Utility.PointsToRectangle(a, b); + } + break; + + case SelectionDrawMode.FixedRatio: + try + { + int drawnWidth = b.X - a.X; + int drawnHeight = b.Y - a.Y; + + double drawnWidthScale = (double)drawnWidth / (double)sdmInfo.Width; + double drawnWidthSign = Math.Sign(drawnWidthScale); + double drawnHeightScale = (double)drawnHeight / (double)sdmInfo.Height; + double drawnHeightSign = Math.Sign(drawnHeightScale); + + double aspect = (double)sdmInfo.Width / (double)sdmInfo.Height; + + if (drawnWidthScale < drawnHeightScale) + { + rect = Utility.PointsToRectangle( + new Point(a.X, a.Y), + new Point(a.X + drawnWidth, a.Y + (int)(drawnHeightSign * Math.Abs((double)drawnWidth / aspect)))); + } + else + { + rect = Utility.PointsToRectangle( + new Point(a.X, a.Y), + new Point(a.X + (int)(drawnWidthSign * Math.Abs((double)drawnHeight * aspect)), a.Y + drawnHeight)); + } + } + + catch (ArithmeticException) + { + rect = new Rectangle(a.X, a.Y, 0, 0); + } + + break; + + case SelectionDrawMode.FixedSize: + double pxWidth = Document.ConvertMeasurement(sdmInfo.Width, sdmInfo.Units, this.Document.DpuUnit, this.Document.DpuX, MeasurementUnit.Pixel); + double pxHeight = Document.ConvertMeasurement(sdmInfo.Height, sdmInfo.Units, this.Document.DpuUnit, this.Document.DpuY, MeasurementUnit.Pixel); + + rect = new Rectangle(b.X, b.Y, (int)pxWidth, (int)pxHeight); + + break; + + default: + throw new InvalidEnumArgumentException(); + } + + rect.Intersect(DocumentWorkspace.Document.Bounds); + + List shape; + + if (rect.Width > 0 && rect.Height > 0) + { + shape = new List(5); + + shape.Add(new PointF(rect.Left, rect.Top)); + shape.Add(new PointF(rect.Right, rect.Top)); + shape.Add(new PointF(rect.Right, rect.Bottom)); + shape.Add(new PointF(rect.Left, rect.Bottom)); + shape.Add(shape[0]); + } + else + { + shape = new List(0); + } + + return shape; + } + + protected override void OnActivate() + { + SetCursors( + "Cursors.RectangleSelectToolCursor.cur", + "Cursors.RectangleSelectToolCursorMinus.cur", + "Cursors.RectangleSelectToolCursorPlus.cur", + "Cursors.RectangleSelectToolCursorMouseDown.cur"); + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + } + + public RectangleSelectTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.RectangleSelectToolIcon.png"), + PdnResources.GetString("RectangleSelectTool.Name"), + PdnResources.GetString("RectangleSelectTool.HelpText"), + 's', + ToolBarConfigItems.SelectionDrawMode) + { + } + } +} diff --git a/src/tools/RectangleTool.cs b/src/tools/RectangleTool.cs new file mode 100644 index 0000000..db9d37c --- /dev/null +++ b/src/tools/RectangleTool.cs @@ -0,0 +1,138 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class RectangleTool + : ShapeTool + { + private ImageResource rectangleToolIcon; + private string statusTextFormat = PdnResources.GetString("RectangleTool.StatusText.Format"); + private Cursor rectangleToolCursor; + + protected override List TrimShapePath(List points) + { + List array = new List(); + + if (points.Count > 0) + { + array.Add(points[0]); + + if (points.Count > 1) + { + array.Add(points[points.Count - 1]); + } + } + + return array; + } + + public override PixelOffsetMode GetPixelOffsetMode() + { + if (AppEnvironment.PenInfo.Width == 1.0f) + { + return PixelOffsetMode.None; + } + + return base.GetPixelOffsetMode(); + } + + protected override PdnGraphicsPath CreateShapePath(PointF[] points) + { + PointF a = points[0]; + PointF b = points[points.Length - 1]; + RectangleF rect; + + if ((ModifierKeys & Keys.Shift) != 0) + { + rect = Utility.PointsToConstrainedRectangle(a, b); + } + else + { + rect = Utility.PointsToRectangle(a, b); + } + + PdnGraphicsPath path = new PdnGraphicsPath(); + path.AddRectangle(rect); + path.CloseFigure(); + path.Reverse(); + + MeasurementUnit units = AppWorkspace.Units; + double widthPhysical = Math.Abs(Document.PixelToPhysicalX(rect.Width, units)); + double heightPhysical = Math.Abs(Document.PixelToPhysicalY(rect.Height, units)); + double areaPhysical = widthPhysical * heightPhysical; + + string numberFormat; + string unitsAbbreviation; + + if (units != MeasurementUnit.Pixel) + { + string unitsAbbreviationName = "MeasurementUnit." + units.ToString() + ".Abbreviation"; + unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName); + numberFormat = "F2"; + } + else + { + unitsAbbreviation = string.Empty; + numberFormat = "F0"; + } + + string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural"); + + string statusText = string.Format( + this.statusTextFormat, + widthPhysical.ToString(numberFormat), + unitsAbbreviation, + heightPhysical.ToString(numberFormat), + unitsAbbreviation, + areaPhysical.ToString(numberFormat), + unitsString); + + this.SetStatus(this.rectangleToolIcon, statusText); + return path; + } + + protected override void OnActivate() + { + rectangleToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.RectangleToolCursor.cur")); + this.rectangleToolIcon = this.Image; + this.Cursor = rectangleToolCursor; + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (this.rectangleToolCursor != null) + { + this.rectangleToolCursor.Dispose(); + this.rectangleToolCursor = null; + } + + base.OnDeactivate(); + } + + public RectangleTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.RectangleToolIcon.png"), + PdnResources.GetString("RectangleTool.Name"), + PdnResources.GetString("RectangleTool.HelpText")) + { + } + } +} diff --git a/src/tools/RoundedRectangleTool.cs b/src/tools/RoundedRectangleTool.cs new file mode 100644 index 0000000..1b8a5c5 --- /dev/null +++ b/src/tools/RoundedRectangleTool.cs @@ -0,0 +1,234 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class RoundedRectangleTool + : ShapeTool + { + private ImageResource roundedRectangleToolIcon; + private string statusTextFormat = PdnResources.GetString("RoundedRectangleTool.StatusText.Format"); + private Cursor roundedRectangleCursor; + + protected override RectangleF[] GetOptimizedShapeOutlineRegion(PointF[] points, PdnGraphicsPath path) + { + return Utility.SimplifyTrace(path.PathPoints); + } + + protected override List TrimShapePath(List points) + { + List array = new List(); + + if (points.Count > 0) + { + array.Add(points[0]); + + if (points.Count > 1) + { + array.Add(points[points.Count - 1]); + } + } + + return array; + } + + protected override PdnGraphicsPath CreateShapePath(PointF[] points) + { + PointF a = points[0]; + PointF b = points[points.Length - 1]; + RectangleF rect; + float radius = 10; + + if ((ModifierKeys & Keys.Shift) != 0) + { + rect = Utility.PointsToConstrainedRectangle(a, b); + } + else + { + rect = Utility.PointsToRectangle(a, b); + } + + PdnGraphicsPath path = this.GetRoundedRect(rect, radius); + path.Flatten(); + + if (path.PathPoints[0] != path.PathPoints[path.PathPoints.Length - 1]) + { + path.AddLine(path.PathPoints[0], path.PathPoints[path.PathPoints.Length - 1]); + path.CloseFigure(); + } + + MeasurementUnit units = AppWorkspace.Units; + double widthPhysical = Math.Abs(Document.PixelToPhysicalX(rect.Width, units)); + double heightPhysical = Math.Abs(Document.PixelToPhysicalY(rect.Height, units)); + double areaPhysical = widthPhysical * heightPhysical; + + string numberFormat; + string unitsAbbreviation; + + if (units != MeasurementUnit.Pixel) + { + string unitsAbbreviationName = "MeasurementUnit." + units.ToString() + ".Abbreviation"; + unitsAbbreviation = PdnResources.GetString(unitsAbbreviationName); + numberFormat = "F2"; + } + else + { + unitsAbbreviation = string.Empty; + numberFormat = "F0"; + } + + string unitsString = PdnResources.GetString("MeasurementUnit." + units.ToString() + ".Plural"); + + string statusText = string.Format( + this.statusTextFormat, + widthPhysical.ToString(numberFormat), + unitsAbbreviation, + heightPhysical.ToString(numberFormat), + unitsAbbreviation, + areaPhysical.ToString(numberFormat), + unitsString); + + this.SetStatus(this.roundedRectangleToolIcon, statusText); + + return path; + } + + protected override void OnActivate() + { + this.roundedRectangleCursor = new Cursor(PdnResources.GetResourceStream("Cursors.RoundedRectangleToolCursor.cur")); + this.Cursor = this.roundedRectangleCursor; + this.roundedRectangleToolIcon = this.Image; + base.OnActivate(); + } + + protected override void OnDeactivate() + { + if (this.roundedRectangleCursor != null) + { + this.roundedRectangleCursor.Dispose(); + this.roundedRectangleCursor = null; + } + + base.OnDeactivate(); + } + + public RoundedRectangleTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.RoundedRectangleToolIcon.png"), + PdnResources.GetString("RoundedRectangleTool.Name"), + PdnResources.GetString("RoundedRectangleTool.HelpText")) + { + } + + // credit for the this function is given to Aaron Reginald http://www.codeproject.com/cs/media/ExtendedGraphics.asp + protected PdnGraphicsPath GetRoundedRect(RectangleF baseRect, float radius) + { + // if corner radius is less than or equal to zero, + // return the original rectangle + if (radius <= 0.0f) + { + PdnGraphicsPath mPath = new PdnGraphicsPath(); + mPath.AddRectangle(baseRect); + mPath.CloseFigure(); + return mPath; + } + + // if the corner radius is greater than or equal to + // half the width, or height (whichever is shorter) + // then return a capsule instead of a lozenge + if (radius >= (Math.Min(baseRect.Width, baseRect.Height)) / 2.0) + { + return GetCapsule(baseRect); + } + + // create the arc for the rectangle sides and declare + // a graphics path object for the drawing + float diameter = radius * 2.0f; + SizeF sizeF = new SizeF(diameter, diameter); + RectangleF arc = new RectangleF(baseRect.Location, sizeF); + PdnGraphicsPath path = new PdnGraphicsPath(); + + // top left arc + path.AddArc (arc, 180, 90); + + // top right arc + arc.X = baseRect.Right - diameter; + path.AddArc (arc, 270, 90); + + // bottom right arc + arc.Y = baseRect.Bottom - diameter; + path.AddArc (arc, 0, 90); + + // bottom left arc + arc.X = baseRect.Left; + path.AddArc (arc, 90, 90); + + path.CloseFigure(); + return path; + } + + // credit for the this function is given to Aaron Reginald http://www.codeproject.com/cs/media/ExtendedGraphics.asp + private PdnGraphicsPath GetCapsule(RectangleF baseRect) + { + float diameter; + RectangleF arc; + PdnGraphicsPath path = new PdnGraphicsPath(); + + try + { + if (baseRect.Width>baseRect.Height) + { + // return horizontal capsule + diameter = baseRect.Height; + SizeF sizeF = new SizeF(diameter, diameter); + arc = new RectangleF(baseRect.Location, sizeF); + path.AddArc(arc, 90, 180); + arc.X = baseRect.Right-diameter; + path.AddArc(arc, 270, 180); + } + else if (baseRect.Width < baseRect.Height) + { + // return vertical capsule + diameter = baseRect.Width; + SizeF sizeF = new SizeF(diameter, diameter); + arc = new RectangleF(baseRect.Location, sizeF); + path.AddArc(arc, 180, 180); + arc.Y = baseRect.Bottom-diameter; + path.AddArc(arc, 0, 180); + } + else + { // return circle + path.AddEllipse(baseRect); + } + } + + catch (Exception) + { + path.AddEllipse(baseRect); + } + + finally + { + path.CloseFigure(); + } + + return path; + } + } +} diff --git a/src/tools/SelectionTool.cs b/src/tools/SelectionTool.cs new file mode 100644 index 0000000..04aa6e4 --- /dev/null +++ b/src/tools/SelectionTool.cs @@ -0,0 +1,558 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.Actions; +using PaintDotNet.HistoryMementos; +using PaintDotNet.HistoryFunctions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + internal class SelectionTool + : Tool + { + private bool tracking = false; + private bool moveOriginMode = false; + private Point lastXY; + private SelectionHistoryMemento undoAction; + private CombineMode combineMode; + private List tracePoints = null; + private DateTime startTime; + private bool hasMoved = false; + private bool append = false; + private bool wasNotEmpty = false; + + private Selection newSelection; + private SelectionRenderer newSelectionRenderer; + + private Cursor cursorMouseUp; + private Cursor cursorMouseUpMinus; + private Cursor cursorMouseUpPlus; + private Cursor cursorMouseDown; + + protected CombineMode SelectionMode + { + get + { + return this.combineMode; + } + } + + protected void SetCursors( + string cursorMouseUpResName, + string cursorMouseUpMinusResName, + string cursorMouseUpPlusResName, + string cursorMouseDownResName) + { + if (this.cursorMouseUp != null) + { + this.cursorMouseUp.Dispose(); + this.cursorMouseUp = null; + } + + if (cursorMouseUpResName != null) + { + this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream(cursorMouseUpResName)); + } + + if (this.cursorMouseUpMinus != null) + { + this.cursorMouseUpMinus.Dispose(); + this.cursorMouseUpMinus = null; + } + + if (cursorMouseUpMinusResName != null) + { + this.cursorMouseUpMinus = new Cursor(PdnResources.GetResourceStream(cursorMouseUpMinusResName)); + } + + if (this.cursorMouseUpPlus != null) + { + this.cursorMouseUpPlus.Dispose(); + this.cursorMouseUpPlus = null; + } + + if (cursorMouseUpPlusResName != null) + { + this.cursorMouseUpPlus = new Cursor(PdnResources.GetResourceStream(cursorMouseUpPlusResName)); + } + + if (this.cursorMouseDown != null) + { + this.cursorMouseDown.Dispose(); + this.cursorMouseDown = null; + } + + if (cursorMouseDownResName != null) + { + this.cursorMouseDown = new Cursor(PdnResources.GetResourceStream(cursorMouseDownResName)); + } + } + + private Cursor GetCursor(bool mouseDown, bool ctrlDown, bool altDown) + { + Cursor cursor; + + if (mouseDown) + { + cursor = this.cursorMouseDown; + } + else if (ctrlDown) + { + cursor = this.cursorMouseUpPlus; + } + else if (altDown) + { + cursor = this.cursorMouseUpMinus; + } + else + { + cursor = this.cursorMouseUp; + } + + return cursor; + } + + private Cursor GetCursor() + { + return GetCursor(IsMouseDown, (ModifierKeys & Keys.Control) != 0, (ModifierKeys & Keys.Alt) != 0); + } + + protected override void OnActivate() + { + // Assume that SetCursors() has been called by now + + this.Cursor = GetCursor(); + DocumentWorkspace.EnableSelectionTinting = true; + + this.newSelection = new Selection(); + this.newSelectionRenderer = new SelectionRenderer(this.RendererList, this.newSelection, this.DocumentWorkspace); + this.newSelectionRenderer.EnableSelectionTinting = false; + this.newSelectionRenderer.EnableOutlineAnimation = false; + this.newSelectionRenderer.Visible = false; + this.RendererList.Add(this.newSelectionRenderer, true); + + base.OnActivate(); + } + + protected override void OnDeactivate() + { + DocumentWorkspace.EnableSelectionTinting = false; + + if (this.tracking) + { + Done(); + } + + base.OnDeactivate(); + + SetCursors(null, null, null, null); // dispose 'em + + this.RendererList.Remove(this.newSelectionRenderer); + this.newSelectionRenderer.Dispose(); + this.newSelectionRenderer = null; + this.newSelection = null; + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + this.Cursor = GetCursor(); + + if (tracking) + { + moveOriginMode = true; + lastXY = new Point(e.X, e.Y); + OnMouseMove(e); + } + else if ((e.Button & MouseButtons.Left) == MouseButtons.Left || + (e.Button & MouseButtons.Right) == MouseButtons.Right) + { + tracking = true; + hasMoved = false; + startTime = DateTime.Now; + + tracePoints = new List(); + tracePoints.Add(new Point(e.X, e.Y)); + + undoAction = new SelectionHistoryMemento("sentinel", this.Image, DocumentWorkspace); + + wasNotEmpty = !Selection.IsEmpty; + + // Determine this.combineMode + + if ((ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Left) + { + this.combineMode = CombineMode.Union; + } + else if ((ModifierKeys & Keys.Alt) != 0 && e.Button == MouseButtons.Left) + { + this.combineMode = CombineMode.Exclude; + } + else if ((ModifierKeys & Keys.Control) != 0 && e.Button == MouseButtons.Right) + { + this.combineMode = CombineMode.Xor; + } + else if ((ModifierKeys & Keys.Alt) != 0 && e.Button == MouseButtons.Right) + { + this.combineMode = CombineMode.Intersect; + } + else + { + this.combineMode = AppEnvironment.SelectionCombineMode; + } + + + DocumentWorkspace.EnableSelectionOutline = false; + + this.newSelection.Reset(); + PdnGraphicsPath basePath = Selection.CreatePath(); + this.newSelection.SetContinuation(basePath, CombineMode.Replace, true); + this.newSelection.CommitContinuation(); + + bool newSelectionRendererVisible = true; + + // Act on this.combineMode + switch (this.combineMode) + { + case CombineMode.Xor: + append = true; + Selection.ResetContinuation(); + break; + + case CombineMode.Union: + append = true; + Selection.ResetContinuation(); + break; + + case CombineMode.Exclude: + append = true; + Selection.ResetContinuation(); + break; + + case CombineMode.Replace: + append = false; + Selection.Reset(); + break; + + case CombineMode.Intersect: + append = true; + Selection.ResetContinuation(); + break; + + default: + throw new InvalidEnumArgumentException(); + } + + this.newSelectionRenderer.Visible = newSelectionRendererVisible; + } + } + + protected virtual List TrimShapePath(List trimTheseTracePoints) + { + return trimTheseTracePoints; + } + + protected virtual List CreateShape(List inputTracePoints) + { + List points = Utility.PointListToPointFList(inputTracePoints); + return points; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (moveOriginMode) + { + Size delta = new Size(e.X - lastXY.X, e.Y - lastXY.Y); + + for (int i = 0; i < tracePoints.Count; ++i) + { + Point pt = (Point)tracePoints[i]; + pt.X += delta.Width; + pt.Y += delta.Height; + tracePoints[i] = pt; + } + + lastXY = new Point(e.X, e.Y); + Render(); + } + else if (tracking) + { + Point mouseXY = new Point(e.X, e.Y); + + if (mouseXY != (Point)tracePoints[tracePoints.Count - 1]) + { + tracePoints.Add(mouseXY); + } + + hasMoved = true; + Render(); + } + } + + private PointF[] CreateSelectionPolygon() + { + List trimmedTrace = this.TrimShapePath(tracePoints); + List shapePoints = CreateShape(trimmedTrace); + List polygon; + + switch (this.combineMode) + { + case CombineMode.Xor: + case CombineMode.Exclude: + polygon = shapePoints; + break; + + default: + case CombineMode.Complement: + case CombineMode.Intersect: + case CombineMode.Replace: + case CombineMode.Union: + polygon = Utility.SutherlandHodgman(DocumentWorkspace.Document.Bounds, shapePoints); + break; + } + + return polygon.ToArray(); + } + + private void Render() + { + if (tracePoints != null && tracePoints.Count > 2) + { + PointF[] polygon = CreateSelectionPolygon(); + + if (polygon.Length > 2) + { + DocumentWorkspace.ResetOutlineWhiteOpacity(); + this.newSelectionRenderer.ResetOutlineWhiteOpacity(); + + Selection.SetContinuation(polygon, this.combineMode); + + CombineMode cm; + + if (SelectionMode == CombineMode.Replace) + { + cm = CombineMode.Replace; + } + else + { + cm = CombineMode.Xor; + } + + this.newSelection.SetContinuation(polygon, cm); + + Update(); + } + } + } + + protected override void OnPulse() + { + if (this.tracking) + { + DocumentWorkspace.ResetOutlineWhiteOpacity(); + this.newSelectionRenderer.ResetOutlineWhiteOpacity(); + } + + base.OnPulse(); + } + + private enum WhatToDo + { + Clear, + Emit, + Reset, + } + + private void Done() + { + if (tracking) + { + // Truth table for what we should do based on three flags: + // append | moved | tooQuick | result | optimized expression to yield true + // ---------+-------+----------+----------------------------------------------------------------------- + // F | T | T | clear selection | !append && (!moved || tooQuick) + // F | T | F | emit new selected area | !append && moved && !tooQuick + // F | F | T | clear selection | !append && (!moved || tooQuick) + // F | F | F | clear selection | !append && (!moved || tooQuick) + // T | T | T | append to selection | append && moved + // T | T | F | append to selection | append && moved + // T | F | T | reset selection | append && !moved + // T | F | F | reset selection | append && !moved + // + // append --> If the user was holding control, then true. Else false. + // moved --> If they never moved the mouse, false. Else true. + // tooQuick --> If they held the mouse button down for more than 50ms, false. Else true. + // + // "Clear selection" means to result in no selected area. If the selection area was previously empty, + // then no HistoryMemento is emitted. Otherwise a Deselect HistoryMemento is emitted. + // + // "Reset selection" means to reset the selected area to how it was before interaction with the tool, + // without a HistoryMemento. + + PointF[] polygon = CreateSelectionPolygon(); + this.hasMoved &= (polygon.Length > 1); + + // They were "too quick" if they weren't doing a selection for more than 50ms + // This takes care of the case where someone wants to click to deselect, but accidentally moves + // the mouse. This happens VERY frequently. + bool tooQuick = Utility.TicksToMs((DateTime.Now - startTime).Ticks) <= 50; + + // If their selection was completedly out of bounds, it will be clipped + bool clipped = (polygon.Length == 0); + + // What the user drew had no effect on the slection, e.g. subtraction where there was nothing in the first place + bool noEffect = false; + + WhatToDo whatToDo; + + // If their selection gets completely clipped (i.e. outside the image canvas), + // then result in a no-op + if (append) + { + if (!hasMoved || clipped || noEffect) + { + whatToDo = WhatToDo.Reset; + } + else + { + whatToDo = WhatToDo.Emit; + } + } + else + { + if (hasMoved && !tooQuick && !clipped && !noEffect) + { + whatToDo = WhatToDo.Emit; + } + else + { + whatToDo = WhatToDo.Clear; + } + } + + switch (whatToDo) + { + case WhatToDo.Clear: + if (wasNotEmpty) + { + // emit a deselect history action + undoAction.Name = DeselectFunction.StaticName; + undoAction.Image = DeselectFunction.StaticImage; + HistoryStack.PushNewMemento(undoAction); + } + + Selection.Reset(); + break; + + case WhatToDo.Emit: + // emit newly selected area + undoAction.Name = this.Name; + HistoryStack.PushNewMemento(undoAction); + Selection.CommitContinuation(); + break; + + case WhatToDo.Reset: + // reset selection, no HistoryMemento + Selection.ResetContinuation(); + break; + } + + DocumentWorkspace.ResetOutlineWhiteOpacity(); + this.newSelectionRenderer.ResetOutlineWhiteOpacity(); + this.newSelection.Reset(); + this.newSelectionRenderer.Visible = false; + + this.tracking = false; + + DocumentWorkspace.EnableSelectionOutline = true; + DocumentWorkspace.InvalidateSurface(Utility.RoundRectangle(DocumentWorkspace.VisibleDocumentRectangleF)); + } + } + + protected override void OnMouseUp(MouseEventArgs e) + { + OnMouseMove(e); + + if (moveOriginMode) + { + moveOriginMode = false; + } + else + { + Done(); + } + + base.OnMouseUp(e); + + Cursor = GetCursor(); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + base.OnKeyDown(e); + + if (tracking) + { + Render(); + } + + Cursor = GetCursor(); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + + if (tracking) + { + Render(); + } + + Cursor = GetCursor(); + } + + protected override void OnClick() + { + base.OnClick(); + + if (!moveOriginMode) + { + Done(); + } + } + + public SelectionTool( + DocumentWorkspace documentWorkspace, + ImageResource toolBarImage, + string name, + string helpText, + char hotKey, + ToolBarConfigItems toolBarConfigItems) + : base(documentWorkspace, + toolBarImage, + name, + helpText, + hotKey, + false, + toolBarConfigItems | ToolBarConfigItems.SelectionCombineMode) + { + this.tracking = false; + } + } +} diff --git a/src/tools/ShapeTool.cs b/src/tools/ShapeTool.cs new file mode 100644 index 0000000..716f8e4 --- /dev/null +++ b/src/tools/ShapeTool.cs @@ -0,0 +1,678 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet.HistoryMementos; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Resources; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + /// + /// Allows the user to draw a shape that can be defined using two points on the canvas. + /// The user clicks and drags between two points to define the area that bounds the shape. + /// + internal abstract class ShapeTool + : Tool + { + private const char defaultShortcut = 'o'; + private bool moveOriginMode; + private PointF lastXY; + private bool mouseDown; + private MouseButtons mouseButton; + private BitmapLayer bitmapLayer; + private RenderArgs renderArgs; + private PdnRegion interiorSaveRegion; + private PdnRegion outlineSaveRegion; + private List points; + private PdnRegion lastDrawnRegion = null; + private Cursor cursorMouseUp; + private Cursor cursorMouseDown; + private bool shapeWasCommited = true; + private CompoundHistoryMemento chaAlreadyOnStack = null; + private bool useDashStyle = false; // if set to false, then the DashStyle will always be forced to DashStyle.Flat + private bool forceShapeType = false; + private ShapeDrawType forcedShapeDrawType = ShapeDrawType.Both; + + protected override bool SupportsInk + { + get + { + return true; + } + } + + // This is for shapes that should only be draw in one ShapeDrawType + // The line shape, for instance, should only ever be drawn in ShapeDrawType.Outline + protected bool ForceShapeDrawType + { + get + { + return this.forceShapeType; + } + + set + { + this.forceShapeType = value; + } + } + + protected ShapeDrawType ForcedShapeDrawType + { + get + { + return this.forcedShapeDrawType; + } + + set + { + this.forcedShapeDrawType = value; + } + } + + protected bool UseDashStyle + { + get + { + return this.useDashStyle; + } + + set + { + this.useDashStyle = value; + } + } + + /// + /// Different shapes may not require all the points given to them, and as such + /// if the user is drawing for a long time there may be lots of memory that's + /// allocated that doesn't need to be. So before CreateShapePath is called, + /// this method is called first. + /// For example, the LineTool would return a new array containing only the + /// first and last points. + /// It is ok to return the same array that was passed in, even if it is modified. + /// + /// A list containing PointF instances. + /// + protected virtual List TrimShapePath(List trimThesePoints) + { + return trimThesePoints; + } + + /// + /// Override this function to return an "optimized" region that encompasses + /// the shape's outline. For example, a circle would return a list of rectangles + /// that traces the outline. This is necessary because normally simplification + /// will produce a region that, for a circle's outline, encompasses its + /// interior as well. If you return null, then the default simplification + /// algorithm will be used. + /// + /// + /// + /// + protected virtual RectangleF[] GetOptimizedShapeOutlineRegion(PointF[] optimizeThesePoints, PdnGraphicsPath path) + { + return null; + } + + // Implement this! + protected abstract PdnGraphicsPath CreateShapePath(PointF[] shapePoints); + + protected override void OnActivate() + { + base.OnActivate(); + + outlineSaveRegion = null; + interiorSaveRegion = null; + + // creates a bitmap layer from the active layer + bitmapLayer = (BitmapLayer)ActiveLayer; + + // create Graphics object + renderArgs = new RenderArgs(bitmapLayer.Surface); + + lastDrawnRegion = new PdnRegion(); + lastDrawnRegion.MakeEmpty(); + } + + protected override void OnDeactivate() + { + base.OnDeactivate(); + + if (mouseDown) + { + PointF lastPoint = (PointF)points[points.Count - 1]; + OnStylusUp(new StylusEventArgs(mouseButton, 0, lastPoint.X, lastPoint.Y, 0)); + } + + if (!this.shapeWasCommited) + { + CommitShape(); + } + + bitmapLayer = null; + + if (renderArgs != null) + { + renderArgs.Dispose(); + renderArgs = null; + } + + if (outlineSaveRegion != null) + { + outlineSaveRegion.Dispose(); + outlineSaveRegion = null; + } + + if (interiorSaveRegion != null) + { + interiorSaveRegion.Dispose(); + interiorSaveRegion = null; + } + + points = null; + } + + protected virtual void OnShapeBegin() + { + } + + /// + /// Called when the shape is finished being traced by the default input handlers. + /// + /// Do not call the base implementation of this method if you are overriding it. + /// true to commit the shape immediately + protected virtual bool OnShapeEnd() + { + return true; + } + + protected override void OnStylusDown(StylusEventArgs e) + { + base.OnStylusDown(e); + + if (!this.shapeWasCommited) + { + CommitShape(); + } + + this.ClearSavedMemory(); + this.ClearSavedRegion(); + + cursorMouseUp = Cursor; + Cursor = cursorMouseDown; + + if (mouseDown && e.Button == mouseButton) + { + return; + } + + if (mouseDown) + { + moveOriginMode = true; + lastXY = new PointF(e.Fx, e.Fy); + OnStylusMove(e); + } + else if (((e.Button & MouseButtons.Left) == MouseButtons.Left) || + ((e.Button & MouseButtons.Right) == MouseButtons.Right)) + { + // begin new shape + this.shapeWasCommited = false; + + OnShapeBegin(); + + mouseDown = true; + mouseButton = e.Button; + + using (PdnRegion clipRegion = Selection.CreateRegion()) + { + renderArgs.Graphics.SetClip(clipRegion.GetRegionReadOnly(), CombineMode.Replace); + } + + // reset the points we're drawing! + points = new List(); + + OnStylusMove(e); + } + } + + protected override void OnStylusMove(StylusEventArgs e) + { + base.OnStylusMove (e); + + if (moveOriginMode) + { + SizeF delta = new SizeF(e.Fx - lastXY.X, e.Fy - lastXY.Y); + + for (int i = 0; i < points.Count; ++i) + { + PointF ptF = (PointF)points[i]; + ptF.X += delta.Width; + ptF.Y += delta.Height; + points[i] = ptF; + } + + lastXY = new PointF(e.Fx, e.Fy); + } + else if (mouseDown && ((e.Button & mouseButton) != MouseButtons.None)) + { + PointF mouseXY = new PointF(e.Fx, e.Fy); + points.Add(mouseXY); + } + } + + public virtual PixelOffsetMode GetPixelOffsetMode() + { + return PixelOffsetMode.Half; + } + + protected List GetTrimmedShapePath() + { + List pointsCopy = new List(this.points); + pointsCopy = TrimShapePath(pointsCopy); + return pointsCopy; + } + + protected void SetShapePath(List newPoints) + { + this.points = newPoints; + } + + protected void RenderShape() + { + // create the Pen we will use to draw with + Pen outlinePen = null; + Brush interiorBrush = null; + PenInfo pi = AppEnvironment.PenInfo; + BrushInfo bi = AppEnvironment.BrushInfo; + + ColorBgra primary = AppEnvironment.PrimaryColor; + ColorBgra secondary = AppEnvironment.SecondaryColor; + + if (!ForceShapeDrawType && AppEnvironment.ShapeDrawType == ShapeDrawType.Interior) + { + Utility.Swap(ref primary, ref secondary); + } + + // Initialize pens and brushes to the correct colors + if ((mouseButton & MouseButtons.Left) == MouseButtons.Left) + { + outlinePen = pi.CreatePen(AppEnvironment.BrushInfo, primary.ToColor(), secondary.ToColor()); + interiorBrush = bi.CreateBrush(secondary.ToColor(), primary.ToColor()); + } + else if ((mouseButton & MouseButtons.Right) == MouseButtons.Right) + { + outlinePen = pi.CreatePen(AppEnvironment.BrushInfo, secondary.ToColor(), primary.ToColor()); + interiorBrush = bi.CreateBrush(primary.ToColor(), secondary.ToColor()); + } + + if (!this.useDashStyle) + { + outlinePen.DashStyle = DashStyle.Solid; + } + + outlinePen.LineJoin = LineJoin.MiterClipped; + outlinePen.MiterLimit = 2; + + // redraw the old saveSurface + if (interiorSaveRegion != null) + { + RestoreRegion(interiorSaveRegion); + interiorSaveRegion.Dispose(); + interiorSaveRegion = null; + } + + if (outlineSaveRegion != null) + { + RestoreRegion(outlineSaveRegion); + outlineSaveRegion.Dispose(); + outlineSaveRegion = null; + } + + // anti-aliasing? Don't mind if I do + if (AppEnvironment.AntiAliasing) + { + renderArgs.Graphics.SmoothingMode = SmoothingMode.AntiAlias; + } + else + { + renderArgs.Graphics.SmoothingMode = SmoothingMode.None; + } + + // also set the pixel offset mode + renderArgs.Graphics.PixelOffsetMode = GetPixelOffsetMode(); + + // figure out how we're going to draw + ShapeDrawType drawType; + + if (ForceShapeDrawType) + { + drawType = ForcedShapeDrawType; + } + else + { + drawType = AppEnvironment.ShapeDrawType; + } + + // get the region we want to save + points = this.TrimShapePath(points); + PointF[] pointsArray = points.ToArray(); + PdnGraphicsPath shapePath = CreateShapePath(pointsArray); + + if (shapePath != null) + { + // create non-optimized interior region + PdnRegion interiorRegion = new PdnRegion(shapePath); + + // create non-optimized outline region + PdnRegion outlineRegion; + + using (PdnGraphicsPath outlinePath = (PdnGraphicsPath)shapePath.Clone()) + { + try + { + outlinePath.Widen(outlinePen); + outlineRegion = new PdnRegion(outlinePath); + } + + // Sometimes GDI+ gets cranky if we have a very small shape (e.g. all points + // are coincident). + catch (OutOfMemoryException) + { + outlineRegion = new PdnRegion(shapePath); + } + } + + // create optimized outlineRegion for purposes of rendering, if it is possible to do so + // shapes will often provide an "optimized" region that circumvents the fact that + // we'd otherwise get a region that encompasses the outline *and* the interior, thus + // slowing rendering significantly in many cases. + RectangleF[] optimizedOutlineRegion = GetOptimizedShapeOutlineRegion(pointsArray, shapePath); + PdnRegion invalidOutlineRegion; + + if (optimizedOutlineRegion != null) + { + Utility.InflateRectanglesInPlace(optimizedOutlineRegion, (int)(outlinePen.Width + 2)); + invalidOutlineRegion = Utility.RectanglesToRegion(optimizedOutlineRegion); + } + else + { + invalidOutlineRegion = Utility.SimplifyAndInflateRegion(outlineRegion, Utility.DefaultSimplificationFactor, (int)(outlinePen.Width + 2)); + } + + // create optimized interior region + PdnRegion invalidInteriorRegion = Utility.SimplifyAndInflateRegion(interiorRegion, Utility.DefaultSimplificationFactor, 3); + + PdnRegion invalidRegion = new PdnRegion(); + invalidRegion.MakeEmpty(); + + // set up alpha blending + renderArgs.Graphics.CompositingMode = AppEnvironment.GetCompositingMode(); + + SaveRegion(invalidOutlineRegion, invalidOutlineRegion.GetBoundsInt()); + this.outlineSaveRegion = invalidOutlineRegion; + if ((drawType & ShapeDrawType.Outline) != 0) + { + shapePath.Draw(renderArgs.Graphics, outlinePen); + } + + invalidRegion.Union(invalidOutlineRegion); + + // draw shape + if ((drawType & ShapeDrawType.Interior) != 0) + { + SaveRegion(invalidInteriorRegion, invalidInteriorRegion.GetBoundsInt()); + this.interiorSaveRegion = invalidInteriorRegion; + renderArgs.Graphics.FillPath(interiorBrush, shapePath); + invalidRegion.Union(invalidInteriorRegion); + } + else + { + invalidInteriorRegion.Dispose(); + invalidInteriorRegion = null; + } + + bitmapLayer.Invalidate(invalidRegion); + + invalidRegion.Dispose(); + invalidRegion = null; + + outlineRegion.Dispose(); + outlineRegion = null; + + interiorRegion.Dispose(); + interiorRegion = null; + } + + Update(); + + if (shapePath != null) + { + shapePath.Dispose(); + shapePath = null; + } + + outlinePen.Dispose(); + interiorBrush.Dispose(); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + // if mouse button not down then leave function + if (mouseDown && ((e.Button & mouseButton) != MouseButtons.None)) + { + RenderShape(); + } + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (mouseDown) + { + RenderShape(); + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + if (mouseDown) + { + RenderShape(); + } + + base.OnKeyUp(e); + } + + protected virtual void OnShapeCommitting() + { + } + + protected void CommitShape() + { + OnShapeCommitting(); + + mouseDown = false; + + ArrayList has = new ArrayList(); + PdnRegion activeRegion = Selection.CreateRegion(); + + if (outlineSaveRegion != null) + { + using (PdnRegion clipTest = activeRegion.Clone()) + { + clipTest.Intersect(outlineSaveRegion); + + if (!clipTest.IsEmpty()) + { + BitmapHistoryMemento bha = new BitmapHistoryMemento(Name, Image, this.DocumentWorkspace, + ActiveLayerIndex, outlineSaveRegion, this.ScratchSurface); + + has.Add(bha); + outlineSaveRegion.Dispose(); + outlineSaveRegion = null; + } + } + } + + if (interiorSaveRegion != null) + { + using (PdnRegion clipTest = activeRegion.Clone()) + { + clipTest.Intersect(interiorSaveRegion); + + if (!clipTest.IsEmpty()) + { + BitmapHistoryMemento bha = new BitmapHistoryMemento(Name, Image, this.DocumentWorkspace, + ActiveLayerIndex, interiorSaveRegion, this.ScratchSurface); + + has.Add(bha); + interiorSaveRegion.Dispose(); + interiorSaveRegion = null; + } + } + } + + if (has.Count > 0) + { + CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, (HistoryMemento[])has.ToArray(typeof(HistoryMemento))); + + if (this.chaAlreadyOnStack == null) + { + HistoryStack.PushNewMemento(cha); + } + else + { + this.chaAlreadyOnStack.PushNewAction(cha); + this.chaAlreadyOnStack = null; + } + } + + activeRegion.Dispose(); + points = null; + Update(); + this.shapeWasCommited = true; + } + + protected override void OnStylusUp(StylusEventArgs e) + { + base.OnStylusUp(e); + + Cursor = cursorMouseUp; + + if (moveOriginMode) + { + moveOriginMode = false; + } + else if (mouseDown) + { + bool doCommit = OnShapeEnd(); + + if (doCommit) + { + CommitShape(); + } + else + { + // place a 'sentinel' history action on the stack that will be filled in later + CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, new List()); + HistoryStack.PushNewMemento(cha); + this.chaAlreadyOnStack = cha; + } + } + } + + public ShapeTool(DocumentWorkspace documentWorkspace, + ImageResource toolBarImage, + string name, + string helpText) + : this(documentWorkspace, + toolBarImage, + name, + helpText, + defaultShortcut, + ToolBarConfigItems.None, + ToolBarConfigItems.None) + { + } + + public ShapeTool(DocumentWorkspace documentWorkspace, + ImageResource toolBarImage, + string name, + string helpText, + ToolBarConfigItems toolBarConfigItemsInclude, + ToolBarConfigItems toolBarConfigItemsExclude) + : this(documentWorkspace, + toolBarImage, + name, + helpText, + defaultShortcut, + toolBarConfigItemsInclude, + toolBarConfigItemsExclude) + { + } + + public ShapeTool(DocumentWorkspace documentWorkspace, + ImageResource toolBarImage, + string name, + string helpText, + char hotKey, + ToolBarConfigItems toolBarConfigItemsInclude, + ToolBarConfigItems toolBarConfigItemsExclude) + : base(documentWorkspace, + toolBarImage, + name, + helpText, + hotKey, + false, + (toolBarConfigItemsInclude | + (ToolBarConfigItems.Brush | + ToolBarConfigItems.Pen | + ToolBarConfigItems.ShapeType | + ToolBarConfigItems.Antialiasing | + ToolBarConfigItems.AlphaBlending)) & + ~(toolBarConfigItemsExclude)) + { + this.mouseDown = false; + this.points = null; + this.cursorMouseUp = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursor.cur")); + this.cursorMouseDown = new Cursor(PdnResources.GetResourceStream("Cursors.ShapeToolCursorMouseDown.cur")); + } + + protected override void Dispose(bool disposing) + { + base.Dispose (disposing); + + if (disposing) + { + if (cursorMouseUp != null) + { + cursorMouseUp.Dispose(); + cursorMouseUp = null; + } + + if (cursorMouseDown != null) + { + cursorMouseDown.Dispose(); + cursorMouseDown = null; + } + } + } + } +} diff --git a/src/tools/TextTool.cs b/src/tools/TextTool.cs new file mode 100644 index 0000000..58b3a52 --- /dev/null +++ b/src/tools/TextTool.cs @@ -0,0 +1,1636 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using PaintDotNet; +using PaintDotNet.HistoryMementos; +using PaintDotNet.SystemLayer; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using System.Windows.Forms; +using System.Diagnostics; +using System.Collections; +using System.ComponentModel; + +namespace PaintDotNet.Tools +{ + internal class TextTool + : Tool + { + private enum EditingMode + { + NotEditing, + EmptyEdit, + Editing + } + + private string statusBarTextFormat = PdnResources.GetString("TextTool.StatusText.TextInfo.Format"); + private Point startMouseXY; + private Point startClickPoint; + private bool tracking; + + private MoveNubRenderer moveNub; + private int ignoreRedraw; + private RenderArgs ra; + private EditingMode mode; + private ArrayList lines; + private int linePos; + private int textPos; + private Point clickPoint; + private Font font; + private TextAlignment alignment; + private IrregularSurface saved; + private const int cursorInterval = 300; + private bool pulseEnabled; + private System.DateTime startTime; + private bool lastPulseCursorState; + private Cursor textToolCursor; + private PaintDotNet.Threading.ThreadPool threadPool; + private bool enableNub = true; + + private CompoundHistoryMemento currentHA; + + private bool controlKeyDown = false; + private DateTime controlKeyDownTime = DateTime.MinValue; + private readonly TimeSpan controlKeyDownThreshold = new TimeSpan(0, 0, 0, 0, 400); + + private void AlphaBlendingChangedHandler(object sender, EventArgs e) + { + if (mode != EditingMode.NotEditing) + { + RedrawText(true); + } + } + + private EventHandler fontChangedDelegate; + private void FontChangedHandler(object sender, EventArgs a) + { + font = AppEnvironment.FontInfo.CreateFont(); + if (mode != EditingMode.NotEditing) + { + this.sizes = null; + RedrawText(true); + } + } + + private EventHandler fontSmoothingChangedDelegate; + private void FontSmoothingChangedHandler(object sender, EventArgs e) + { + if (mode != EditingMode.NotEditing) + { + this.sizes = null; + RedrawText(true); + } + } + + private EventHandler alignmentChangedDelegate; + private void AlignmentChangedHandler(object sender, EventArgs a) + { + alignment = AppEnvironment.TextAlignment; + if (mode != EditingMode.NotEditing) + { + this.sizes = null; + RedrawText(true); + } + } + + private EventHandler brushChangedDelegate; + private void BrushChangedHandler(object sender, EventArgs a) + { + if (mode != EditingMode.NotEditing) + { + RedrawText(true); + } + } + + private EventHandler antiAliasChangedDelegate; + private void AntiAliasChangedHandler(object sender, EventArgs a) + { + if (mode != EditingMode.NotEditing) + { + this.sizes = null; + RedrawText(true); + } + } + + private EventHandler foreColorChangedDelegate; + private void ForeColorChangedHandler(object sender, EventArgs e) + { + if (mode != EditingMode.NotEditing) + { + RedrawText(true); + } + } + + private void BackColorChangedHandler(object sender, EventArgs e) + { + if (mode != EditingMode.NotEditing) + { + RedrawText(true); + } + } + + private bool OnBackspaceTyped(Keys keys) + { + if (!this.DocumentWorkspace.Visible) + { + return false; + } + else if (this.mode != EditingMode.NotEditing) + { + OnKeyPress(Keys.Back); + return true; + } + else + { + return false; + } + } + + protected override void OnActivate() + { + PdnBaseForm.RegisterFormHotKey(Keys.Back, OnBackspaceTyped); + + base.OnActivate(); + + this.textToolCursor = new Cursor(PdnResources.GetResourceStream("Cursors.TextToolCursor.cur")); + this.Cursor = this.textToolCursor; + + fontChangedDelegate = new EventHandler(FontChangedHandler); + fontSmoothingChangedDelegate = new EventHandler(FontSmoothingChangedHandler); + alignmentChangedDelegate = new EventHandler(AlignmentChangedHandler); + brushChangedDelegate = new EventHandler(BrushChangedHandler); + antiAliasChangedDelegate = new EventHandler(AntiAliasChangedHandler); + foreColorChangedDelegate = new EventHandler(ForeColorChangedHandler); + + ra = new RenderArgs(((BitmapLayer)ActiveLayer).Surface); + mode = EditingMode.NotEditing; + + font = AppEnvironment.FontInfo.CreateFont(); + alignment = AppEnvironment.TextAlignment; + + AppEnvironment.BrushInfoChanged += brushChangedDelegate; + AppEnvironment.FontInfoChanged += fontChangedDelegate; + AppEnvironment.FontSmoothingChanged += fontSmoothingChangedDelegate; + AppEnvironment.TextAlignmentChanged += alignmentChangedDelegate; + AppEnvironment.AntiAliasingChanged += antiAliasChangedDelegate; + AppEnvironment.PrimaryColorChanged += foreColorChangedDelegate; + AppEnvironment.SecondaryColorChanged += new EventHandler(BackColorChangedHandler); + AppEnvironment.AlphaBlendingChanged += new EventHandler(AlphaBlendingChangedHandler); + + this.threadPool = new PaintDotNet.Threading.ThreadPool(); + + this.moveNub = new MoveNubRenderer(this.RendererList); + this.moveNub.Shape = MoveNubShape.Compass; + this.moveNub.Size = new SizeF(10, 10); + this.moveNub.Visible = false; + this.RendererList.Add(this.moveNub, false); + } + + protected override void OnDeactivate() + { + PdnBaseForm.UnregisterFormHotKey(Keys.Back, OnBackspaceTyped); + + base.OnDeactivate(); + + switch (mode) + { + case EditingMode.Editing: + SaveHistoryMemento(); + break; + + case EditingMode.EmptyEdit: + RedrawText(false); + break; + + case EditingMode.NotEditing: + break; + + default: + throw new InvalidEnumArgumentException("Invalid Editing Mode"); + } + + if (ra != null) + { + ra.Dispose(); + ra = null; + } + + if (saved != null) + { + saved.Dispose(); + saved = null; + } + + AppEnvironment.BrushInfoChanged -= brushChangedDelegate; + AppEnvironment.FontInfoChanged -= fontChangedDelegate; + AppEnvironment.FontSmoothingChanged -= fontSmoothingChangedDelegate; + AppEnvironment.TextAlignmentChanged -= alignmentChangedDelegate; + AppEnvironment.AntiAliasingChanged -= antiAliasChangedDelegate; + AppEnvironment.PrimaryColorChanged -= foreColorChangedDelegate; + AppEnvironment.SecondaryColorChanged -= new EventHandler(BackColorChangedHandler); + AppEnvironment.AlphaBlendingChanged -= new EventHandler(AlphaBlendingChangedHandler); + + StopEditing(); + this.threadPool = null; + + this.RendererList.Remove(this.moveNub); + this.moveNub.Dispose(); + this.moveNub = null; + + if (this.textToolCursor != null) + { + this.textToolCursor.Dispose(); + this.textToolCursor = null; + } + } + + private void StopEditing() + { + mode = EditingMode.NotEditing; + pulseEnabled = false; + lines = null; + this.moveNub.Visible = false; + } + + private void StartEditing() + { + this.linePos = 0; + this.textPos = 0; + this.lines = new ArrayList(); + this.sizes = null; + this.lines.Add(string.Empty); + this.startTime = DateTime.Now; + this.mode = EditingMode.EmptyEdit; + this.pulseEnabled = true; + UpdateStatusText(); + } + + private void UpdateStatusText() + { + string text; + ImageResource image; + + if (this.tracking) + { + text = GetStatusBarXYText(); + image = Image; + } + else + { + text = PdnResources.GetString("TextTool.StatusText.StartTyping"); + image = null; + } + + SetStatus(image, text); + } + + private void PerformEnter() + { + if (this.lines == null) + { + return; + } + + string currentLine = (string)this.lines[this.linePos]; + + if (this.textPos == currentLine.Length) + { + // If we are at the end of a line, insert an empty line at the next line + this.lines.Insert(this.linePos + 1, string.Empty); + } + else + { + this.lines.Insert(this.linePos + 1, currentLine.Substring(textPos, currentLine.Length - this.textPos)); + this.lines[this.linePos] = ((string)this.lines[this.linePos]).Substring(0, this.textPos); + } + + this.linePos++; + this.textPos = 0; + this.sizes = null; + + } + + private void PerformBackspace() + { + if (textPos == 0 && linePos > 0) + { + int ntp = ((string)lines[linePos - 1]).Length; + + lines[linePos - 1] = ((string)lines[linePos - 1]) + ((string)lines[linePos]); + lines.RemoveAt(linePos); + linePos--; + textPos = ntp; + sizes = null; + } + else if (textPos > 0) + { + string ln = (string)lines[linePos]; + + // If we are at the end of a line, we don't need to place a compound string + if (textPos == ln.Length) + { + lines[linePos] = ln.Substring(0, ln.Length - 1); + } + else + { + lines[linePos] = ln.Substring(0, textPos - 1) + ln.Substring(textPos); + } + + textPos--; + sizes = null; + } + } + + private void PerformControlBackspace() + { + if (textPos == 0 && linePos > 0) + { + PerformBackspace(); + } + else if (textPos > 0) + { + string currentLine = (string)lines[linePos]; + int ntp = textPos; + + if (Char.IsLetterOrDigit(currentLine[ntp - 1])) + { + while (ntp > 0 && (Char.IsLetterOrDigit(currentLine[ntp - 1]))) + { + ntp--; + } + } + else if (Char.IsWhiteSpace(currentLine[ntp - 1])) + { + while (ntp > 0 && (Char.IsWhiteSpace(currentLine[ntp - 1]))) + { + ntp--; + } + } + else if (Char.IsPunctuation(currentLine[ntp - 1])) + { + while (ntp > 0 && (Char.IsPunctuation(currentLine[ntp - 1]))) + { + ntp--; + } + } + else + { + ntp--; + } + + lines[linePos] = currentLine.Substring(0, ntp) + currentLine.Substring(textPos); + textPos = ntp; + sizes = null; + } + } + + private void PerformDelete() + { + // Where are we?! + if ((linePos == lines.Count - 1) && (textPos == ((string)lines[lines.Count - 1]).Length)) + { + // If the cursor is at the end of the text block + return; + } + else if (textPos == ((string)lines[linePos]).Length) + { + // End of a line, must merge strings + lines[linePos] = ((string)lines[linePos]) + ((string)lines[linePos + 1]); + lines.RemoveAt(linePos + 1); + } + else + { + // Middle of a line somewhere + lines[linePos] = ((string)lines[linePos]).Substring(0, textPos) + ((string)lines[linePos]).Substring(textPos + 1); + } + + // Check for state change + if (lines.Count == 1 && ((string)lines[0]) == "") + { + mode = EditingMode.EmptyEdit; + } + + sizes = null; + } + + private void PerformControlDelete() + { + // where are we?! + if ((linePos == lines.Count - 1) && (textPos == ((string)lines[lines.Count - 1]).Length)) + { + // If the cursor is at the end of the text block + return; + } + else if (textPos == ((string)lines[linePos]).Length) + { + // End of a line, must merge strings + lines[linePos] = ((string)lines[linePos]) + ((string)lines[linePos + 1]); + lines.RemoveAt(linePos + 1); + } + else + { + // Middle of a line somewhere + int ntp = textPos; + string currentLine = (string)lines[linePos]; + + if (Char.IsLetterOrDigit(currentLine[ntp])) + { + while (ntp < currentLine.Length && (Char.IsLetterOrDigit(currentLine[ntp]))) + { + currentLine = currentLine.Remove(ntp, 1); + } + } + else if (Char.IsWhiteSpace(currentLine[ntp])) + { + while (ntp < currentLine.Length && (Char.IsWhiteSpace(currentLine[ntp]))) + { + currentLine = currentLine.Remove(ntp, 1); + } + } + else if (Char.IsPunctuation(currentLine[ntp])) + { + while (ntp < currentLine.Length && (Char.IsPunctuation(currentLine[ntp]))) + { + currentLine = currentLine.Remove(ntp, 1); + } + } + else + { + ntp--; + } + + lines[linePos] = currentLine; + } + + // Check for state change + if (lines.Count == 1 && ((string)lines[0]) == "") + { + mode = EditingMode.EmptyEdit; + } + + sizes = null; + } + + private void PerformLeft() + { + if (textPos > 0) + { + textPos--; + } + else if (textPos == 0 && linePos > 0) + { + linePos--; + textPos = ((string)lines[linePos]).Length; + } + } + + private void PerformControlLeft() + { + if (textPos > 0) + { + int ntp = textPos; + string currentLine = (string)lines[linePos]; + + if (Char.IsLetterOrDigit(currentLine[ntp - 1])) + { + while (ntp > 0 && (Char.IsLetterOrDigit(currentLine[ntp - 1]))) + { + ntp--; + } + } + else if (Char.IsWhiteSpace(currentLine[ntp - 1])) + { + while (ntp > 0 && (Char.IsWhiteSpace(currentLine[ntp - 1]))) + { + ntp--; + } + } + else if (ntp > 0 && Char.IsPunctuation(currentLine[ntp - 1])) + { + while (ntp > 0 && Char.IsPunctuation(currentLine[ntp - 1])) + { + ntp--; + } + } + else + { + ntp--; + } + + textPos = ntp; + } + else if (textPos == 0 && linePos > 0) + { + linePos--; + textPos = ((string)lines[linePos]).Length; + } + } + + private void PerformRight() + { + if (textPos < ((string)lines[linePos]).Length) + { + textPos++; + } + else if (textPos == ((string)lines[linePos]).Length && linePos < lines.Count - 1) + { + linePos++; + textPos = 0; + } + } + + private void PerformControlRight() + { + if (textPos < ((string)lines[linePos]).Length) + { + int ntp = textPos; + string currentLine = (string)lines[linePos]; + + if (Char.IsLetterOrDigit(currentLine[ntp])) + { + while (ntp < currentLine.Length && (Char.IsLetterOrDigit(currentLine[ntp]))) + { + ntp++; + } + } + else if (Char.IsWhiteSpace(currentLine[ntp])) + { + while (ntp < currentLine.Length && (Char.IsWhiteSpace(currentLine[ntp]))) + { + ntp++; + } + } + else if (ntp > 0 && Char.IsPunctuation(currentLine[ntp])) + { + while (ntp < currentLine.Length && Char.IsPunctuation(currentLine[ntp])) + { + ntp++; + } + } + else + { + ntp++; + } + + textPos = ntp; + } + else if (textPos == ((string)lines[linePos]).Length && linePos < lines.Count - 1) + { + linePos++; + textPos = 0; + } + } + + private void PerformUp() + { + PointF p = TextPositionToPoint(new Position(linePos, textPos)); + p.Y -= this.sizes[0].Height; //font.Height; + Position np = PointToTextPosition(p); + linePos = np.Line; + textPos = np.Offset; + } + + private void PerformDown() + { + if (linePos == lines.Count - 1) + { + // last line -> don't do squat + } + else + { + PointF p = TextPositionToPoint(new Position(linePos, textPos)); + p.Y += this.sizes[0].Height; //font.Height; + Position np = PointToTextPosition(p); + linePos = np.Line; + textPos = np.Offset; + } + } + + private Point GetUpperLeft(Size sz, int line) + { + Point p = clickPoint; + p.Y = (int)(p.Y - (0.5 * sz.Height) + (line * sz.Height)); + + switch (alignment) + { + case TextAlignment.Center: + p.X = (int)(p.X - (0.5) * sz.Width); + break; + + case TextAlignment.Right: + p.X = (int)(p.X - sz.Width); + break; + } + + return p; + } + + private Size StringSize(string s) + { + // We measure using a 1x1 device context to avoid performance problems that arise otherwise with large images. + using (Surface window = ScratchSurface.CreateWindow(new Rectangle(0, 0, 1, 1))) + { + using (RenderArgs ra2 = new RenderArgs(window)) + { + return SystemLayer.Fonts.MeasureString( + ra2.Graphics, + this.font, + s, + AppEnvironment.AntiAliasing, + AppEnvironment.FontSmoothing); + } + } + } + + private sealed class Position + { + private int line; + public int Line + { + get + { + return line; + } + + set + { + if (value >= 0) + { + line = value; + } + else + { + line = 0; + } + } + } + + private int offset; + public int Offset + { + get + { + return offset; + } + + set + { + if (value >= 0) + { + offset = value; + } + else + { + offset = 0; + } + } + } + + public Position(int line, int offset) + { + this.line = line; + this.offset = offset; + } + } + + private void SaveHistoryMemento() + { + pulseEnabled = false; + RedrawText(false); + + if (saved != null) + { + PdnRegion hitTest = Selection.CreateRegion(); + hitTest.Intersect(saved.Region); + + if (!hitTest.IsEmpty()) + { + BitmapHistoryMemento bha = new BitmapHistoryMemento(Name, Image, DocumentWorkspace, + ActiveLayerIndex, saved); + + if (this.currentHA == null) + { + HistoryStack.PushNewMemento(bha); + } + else + { + this.currentHA.PushNewAction(bha); + this.currentHA = null; + } + } + + hitTest.Dispose(); + saved.Dispose(); + saved = null; + } + } + + private void DrawText(Surface dst, Font textFont, string text, Point pt, Size measuredSize, bool antiAliasing, Brush brush) + { + Rectangle dstRect = new Rectangle(pt, measuredSize); + Rectangle dstRectClipped = Rectangle.Intersect(dstRect, ScratchSurface.Bounds); + + if (dstRectClipped.Width == 0 || dstRectClipped.Height == 0) + { + return; + } + + using (Surface surface = new Surface(8, 8)) + { + using (RenderArgs renderArgs = new RenderArgs(surface)) + { + renderArgs.Graphics.FillRectangle(brush, 0, 0, surface.Width, surface.Height); + } + + DrawText(dst, textFont, text, pt, measuredSize, antiAliasing, surface); + } + } + + private unsafe void DrawText(Surface dst, Font textFont, string text, Point pt, Size measuredSize, bool antiAliasing, Surface brush8x8) + { + Point pt2 = pt; + Size measuredSize2 = measuredSize; + int offset = (int)textFont.Height; + pt.X -= offset; + measuredSize.Width += 2 * offset; + Rectangle dstRect = new Rectangle(pt, measuredSize); + Rectangle dstRectClipped = Rectangle.Intersect(dstRect, ScratchSurface.Bounds); + + if (dstRectClipped.Width == 0 || dstRectClipped.Height == 0) + { + return; + } + + // We only use the first 8,8 of brush + using (RenderArgs renderArgs = new RenderArgs(this.ScratchSurface)) + { + renderArgs.Graphics.FillRectangle(Brushes.White, pt.X, pt.Y, measuredSize.Width, measuredSize.Height); + + if (measuredSize.Width > 0 && measuredSize.Height > 0) + { + using (Surface s2 = renderArgs.Surface.CreateWindow(dstRectClipped)) + { + using (RenderArgs renderArgs2 = new RenderArgs(s2)) + { + SystemLayer.Fonts.DrawText( + renderArgs2.Graphics, + this.font, + text, + new Point(dstRect.X - dstRectClipped.X + offset, dstRect.Y - dstRectClipped.Y), + AppEnvironment.AntiAliasing, + AppEnvironment.FontSmoothing); + } + } + } + + // Mask out anything that isn't within the user's clip region (selected region) + using (PdnRegion clip = Selection.CreateRegion()) + { + clip.Xor(renderArgs.Surface.Bounds); // invert + clip.Intersect(new Rectangle(pt, measuredSize)); + renderArgs.Graphics.FillRegion(Brushes.White, clip.GetRegionReadOnly()); + } + + int skipX; + + if (pt.X < 0) + { + skipX = -pt.X; + } + else + { + skipX = 0; + } + + int xEnd = Math.Min(dst.Width, pt.X + measuredSize.Width); + + bool blending = AppEnvironment.AlphaBlending; + + if (dst.IsColumnVisible(pt.X + skipX)) + { + for (int y = pt.Y; y < pt.Y + measuredSize.Height; ++y) + { + if (!dst.IsRowVisible(y)) + { + continue; + } + + ColorBgra *dstPtr = dst.GetPointAddressUnchecked(pt.X + skipX, y); + ColorBgra *srcPtr = ScratchSurface.GetPointAddress(pt.X + skipX, y); + ColorBgra *brushPtr = brush8x8.GetRowAddressUnchecked(y & 7); + + for (int x = pt.X + skipX; x < xEnd; ++x) + { + ColorBgra srcPixel = *srcPtr; + ColorBgra dstPixel = *dstPtr; + ColorBgra brushPixel = brushPtr[x & 7]; + + int alpha = ((255 - srcPixel.R) * brushPixel.A) / 255; // we could use srcPixel.R, .G, or .B -- the choice here is arbitrary + brushPixel.A = (byte)alpha; + + if (srcPtr->R == 255) // could use R, G, or B -- arbitrary choice + { + // do nothing -- leave dst alone + } + else if (alpha == 255 || !blending) + { + // copy it straight over + *dstPtr = brushPixel; + } + else + { + // do expensive blending + *dstPtr = UserBlendOps.NormalBlendOp.ApplyStatic(dstPixel, brushPixel); + } + + ++dstPtr; + ++srcPtr; + } + } + } + } + } + + /// + /// Redraws the Text on the screen + /// + /// + /// assumes that the font and the alignment are already set + /// + /// + private void RedrawText(bool cursorOn) + { + if (this.ignoreRedraw > 0) + { + return; + } + + if (saved != null) + { + saved.Draw(ra.Surface); + ActiveLayer.Invalidate(saved.Region); + saved.Dispose(); + saved = null; + } + + // Save the Space behind the lines + Rectangle[] rects = new Rectangle[lines.Count + 1]; + Point[] localUls = new Point[lines.Count]; + + // All Lines + bool recalcSizes = false; + + if (this.sizes == null) + { + recalcSizes = true; + this.sizes = new Size[lines.Count + 1]; + } + + if (recalcSizes) + { + for (int i = 0; i < lines.Count; ++i) + { + this.threadPool.QueueUserWorkItem(new System.Threading.WaitCallback(this.MeasureText), + BoxedConstants.GetInt32(i)); + } + + this.threadPool.Drain(); + } + + for (int i = 0; i < lines.Count; ++i) + { + Point upperLeft = GetUpperLeft(sizes[i], i); + localUls[i] = upperLeft; + Rectangle rect = new Rectangle(upperLeft, sizes[i]); + rects[i] = rect; + } + + // The Cursor Line + string cursorLine = ((string)lines[linePos]).Substring(0, textPos); + Size cursorLineSize; + Point cursorUL; + Rectangle cursorRect; + bool emptyCursorLineFlag; + + if (cursorLine.Length == 0) + { + emptyCursorLineFlag = true; + Size fullLineSize = sizes[linePos]; + cursorLineSize = new Size(2, (int)(Math.Ceiling(font.GetHeight()))); + cursorUL = GetUpperLeft(fullLineSize, linePos); + cursorRect = new Rectangle(cursorUL, cursorLineSize); + } + else if (cursorLine.Length == ((string)lines[linePos]).Length) + { + emptyCursorLineFlag = false; + cursorLineSize = sizes[linePos]; + cursorUL = localUls[linePos]; + cursorRect = new Rectangle(cursorUL, cursorLineSize); + } + else + { + emptyCursorLineFlag = false; + cursorLineSize = StringSize(cursorLine); + cursorUL = localUls[linePos]; + cursorRect = new Rectangle(cursorUL, cursorLineSize); + } + + rects[lines.Count] = cursorRect; + + // Account for overhang on italic or fancy fonts + int offset = (int)this.font.Height; + for (int i = 0; i < rects.Length; ++i) + { + rects[i].X -= offset; + rects[i].Width += 2 * offset; + } + + // Set the saved region + using (PdnRegion reg = Utility.RectanglesToRegion(Utility.InflateRectangles(rects, 3))) + { + saved = new IrregularSurface(ra.Surface, reg); + } + + // Draw the Lines + this.uls = localUls; + + for (int i = 0; i < lines.Count; i++) + { + threadPool.QueueUserWorkItem(new System.Threading.WaitCallback(this.RenderText), BoxedConstants.GetInt32(i)); + } + + threadPool.Drain(); + + // Draw the Cursor + if (cursorOn) + { + using (Pen cursorPen = new Pen(Color.FromArgb(255, AppEnvironment.PrimaryColor.ToColor()), 2)) + { + if (emptyCursorLineFlag) + { + ra.Graphics.FillRectangle(cursorPen.Brush, cursorRect); + } + else + { + ra.Graphics.DrawLine(cursorPen, new Point(cursorRect.Right, cursorRect.Top), new Point(cursorRect.Right, cursorRect.Bottom)); + } + } + } + + PlaceMoveNub(); + UpdateStatusText(); + ActiveLayer.Invalidate(saved.Region); + Update(); + } + + private string GetStatusBarXYText() + { + string unitsAbbreviationXY; + string xString; + string yString; + + Document.CoordinatesToStrings(AppWorkspace.Units, this.uls[0].X, this.uls[0].Y, out xString, out yString, out unitsAbbreviationXY); + + string statusBarText = string.Format( + this.statusBarTextFormat, + xString, + unitsAbbreviationXY, + yString, + unitsAbbreviationXY); + + return statusBarText; + } + + // Only used when measuring via background threads + private void MeasureText(object lineNumberObj) + { + int lineNumber = (int)lineNumberObj; + this.sizes[lineNumber] = StringSize((string)lines[lineNumber]); + } + + // Only used when rendering via background threads + private Point[] uls; + private Size[] sizes; + + private void RenderText(object lineNumberObj) + { + int lineNumber = (int)lineNumberObj; + + using (Brush brush = AppEnvironment.CreateBrush(false)) + { + DrawText(ra.Surface, this.font, (string)this.lines[lineNumber], this.uls[lineNumber], this.sizes[lineNumber], AppEnvironment.AntiAliasing, brush); + } + } + + private void PlaceMoveNub() + { + if (this.uls != null && this.uls.Length > 0) + { + Point pt = this.uls[uls.Length - 1]; + pt.X += this.sizes[uls.Length - 1].Width; + pt.Y += this.sizes[uls.Length - 1].Height; + pt.X += (int)(10.0 / DocumentWorkspace.ScaleFactor.Ratio); + pt.Y += (int)(10.0 / DocumentWorkspace.ScaleFactor.Ratio); + + pt.X = (int)Math.Round(Math.Min(this.ra.Surface.Width - this.moveNub.Size.Width, pt.X)); + pt.X = (int)Math.Round(Math.Max(this.moveNub.Size.Width, pt.X)); + pt.Y = (int)Math.Round(Math.Min(this.ra.Surface.Height - this.moveNub.Size.Height, pt.Y)); + pt.Y = (int)Math.Round(Math.Max(this.moveNub.Size.Height, pt.Y)); + + this.moveNub.Location = pt; + } + } + + protected override void OnKeyDown(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Space: + if (mode != EditingMode.NotEditing) + { + // Prevent pan cursor from flicking to 'hand w/ the X' whenever use types a space in their text + e.Handled = true; + } + break; + + case Keys.ControlKey: + if (!this.controlKeyDown) + { + this.controlKeyDown = true; + this.controlKeyDownTime = DateTime.Now; + } + break; + + // Make sure these are not used to scroll the document around + case Keys.Home | Keys.Shift: + case Keys.Home: + case Keys.End: + case Keys.End | Keys.Shift: + case Keys.Next | Keys.Shift: + case Keys.Next: + case Keys.Prior | Keys.Shift: + case Keys.Prior: + if (this.mode != EditingMode.NotEditing) + { + OnKeyPress(e.KeyCode); + e.Handled = true; + } + break; + + case Keys.Tab: + if ((e.Modifiers & Keys.Control) == 0) + { + if (this.mode != EditingMode.NotEditing) + { + OnKeyPress(e.KeyCode); + e.Handled = true; + } + } + break; + + case Keys.Back: + case Keys.Delete: + if (this.mode != EditingMode.NotEditing) + { + OnKeyPress(e.KeyCode); + e.Handled = true; + } + break; + } + + // Ensure text is on screen when they are typing + if (this.mode != EditingMode.NotEditing) + { + Point p = Point.Truncate(TextPositionToPoint(new Position(linePos, textPos))); + Rectangle bounds = Utility.RoundRectangle(DocumentWorkspace.VisibleDocumentRectangleF); + bounds.Inflate(-(int)font.Height, -(int)font.Height); + + if (!bounds.Contains(p)) + { + PointF newCenterPt = Utility.GetRectangleCenter((RectangleF)bounds); + + // horizontally off + if (p.X > bounds.Right || p.Y < bounds.Left) + { + newCenterPt.X = p.X; + } + + // vertically off + if (p.Y > bounds.Bottom || p.Y < bounds.Top) + { + newCenterPt.Y = p.Y; + } + + DocumentWorkspace.DocumentCenterPointF = newCenterPt; + } + } + + base.OnKeyDown (e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.ControlKey: + TimeSpan heldDuration = (DateTime.Now - this.controlKeyDownTime); + + // If the user taps Ctrl, then we should toggle the visiblity of the moveNub + if (heldDuration < this.controlKeyDownThreshold) + { + this.enableNub = !this.enableNub; + } + + this.controlKeyDown = false; + break; + } + + base.OnKeyUp(e); + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + switch (e.KeyChar) + { + case (char)13: // Enter + if (tracking) + { + e.Handled = true; + } + break; + + case (char)27: // Escape + if (tracking) + { + e.Handled = true; + } + else + { + if (mode == EditingMode.Editing) + { + SaveHistoryMemento(); + } + else if (mode == EditingMode.EmptyEdit) + { + RedrawText(false); + } + + if (mode != EditingMode.NotEditing) + { + e.Handled = true; + StopEditing(); + } + } + + break; + } + + if (!e.Handled && mode != EditingMode.NotEditing && !tracking) + { + e.Handled = true; + + if (mode == EditingMode.EmptyEdit) + { + mode = EditingMode.Editing; + CompoundHistoryMemento cha = new CompoundHistoryMemento(Name, Image, new List()); + this.currentHA = cha; + HistoryStack.PushNewMemento(cha); + } + + if (!char.IsControl(e.KeyChar)) + { + InsertCharIntoString(e.KeyChar); + textPos++; + RedrawText(true); + } + } + + base.OnKeyPress (e); + } + + protected override void OnKeyPress(Keys keyData) + { + bool keyHandled = true; + Keys key = keyData & Keys.KeyCode; + Keys modifier = keyData & Keys.Modifiers; + + if (tracking) + { + keyHandled = false; + } + else if (modifier == Keys.Alt) + { + // ignore so they can use Alt+#### to type special characters + } + else if (mode != EditingMode.NotEditing) + { + switch (key) + { + case Keys.Back: + if (modifier == Keys.Control) + { + PerformControlBackspace(); + } + else + { + PerformBackspace(); + } + + break; + + case Keys.Delete: + if (modifier == Keys.Control) + { + PerformControlDelete(); + } + else + { + PerformDelete(); + } + + break; + + case Keys.Enter: + PerformEnter(); + break; + + case Keys.Left: + if (modifier == Keys.Control) + { + PerformControlLeft(); + } + else + { + PerformLeft(); + } + + break; + + case Keys.Right: + if (modifier == Keys.Control) + { + PerformControlRight(); + } + else + { + PerformRight(); + } + + break; + + case Keys.Up: + PerformUp(); + break; + + case Keys.Down: + PerformDown(); + break; + + case Keys.Home: + if (modifier == Keys.Control) + { + linePos = 0; + } + + textPos = 0; + break; + + case Keys.End: + if (modifier == Keys.Control) + { + linePos = lines.Count - 1; + } + + textPos = ((string)lines[linePos]).Length; + break; + + default: + keyHandled = false; + break; + } + + this.startTime = DateTime.Now; + + if (this.mode != EditingMode.NotEditing && keyHandled) + { + RedrawText(true); + } + } + + if (!keyHandled) + { + base.OnKeyPress(keyData); + } + } + + private PointF TextPositionToPoint(Position p) + { + PointF pf = new PointF(0,0); + + Size sz = StringSize(((string)lines[p.Line]).Substring(0, p.Offset)); + Size fullSz = StringSize((string)lines[p.Line]); + + switch (alignment) + { + case TextAlignment.Left: + pf = new PointF(clickPoint.X + sz.Width, clickPoint.Y + (sz.Height * p.Line)); + break; + + case TextAlignment.Center: + pf = new PointF(clickPoint.X + (sz.Width - (fullSz.Width/2)), clickPoint.Y + (sz.Height * p.Line)); + break; + + case TextAlignment.Right: + pf = new PointF(clickPoint.X + (sz.Width - fullSz.Width), clickPoint.Y + (sz.Height * p.Line)); + break; + + default: + throw new InvalidEnumArgumentException("Invalid Alignment"); + } + + return pf; + } + + private int FindOffsetPosition(float offset, string line, int lno) + { + for (int i = 0; i < line.Length; i++) + { + PointF pf = TextPositionToPoint(new Position(lno, i)); + float dx = pf.X - clickPoint.X; + + if (dx >= offset) + { + return i; + } + } + + return line.Length; + } + + private Position PointToTextPosition(PointF pf) + { + float dx = pf.X - clickPoint.X; + float dy = pf.Y - clickPoint.Y; + int line = (int)Math.Floor(dy / (float)this.sizes[0].Height); + + if (line < 0) + { + line = 0; + } + else if (line >= lines.Count) + { + line = lines.Count - 1; + } + + int offset = FindOffsetPosition(dx, (string)lines[line], line); + Position p = new Position(line, offset); + + if (p.Offset >= ((string)lines[p.Line]).Length) + { + p.Offset = ((string)lines[p.Line]).Length; + } + + return p; + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (tracking) + { + Point newMouseXY = new Point(e.X, e.Y); + Size delta = new Size(newMouseXY.X - startMouseXY.X, newMouseXY.Y - startMouseXY.Y); + this.clickPoint = new Point(this.startClickPoint.X + delta.Width, this.startClickPoint.Y + delta.Height); + RedrawText(false); + UpdateStatusText(); + } + else + { + bool touchingNub = this.moveNub.IsPointTouching(new Point(e.X, e.Y), false); + + if (touchingNub && this.moveNub.Visible) + { + this.Cursor = this.handCursor; + } + else + { + this.Cursor = this.textToolCursor; + } + } + + base.OnMouseMove (e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + if (tracking) + { + OnMouseMove(e); + tracking = false; + UpdateStatusText(); + } + + base.OnMouseUp (e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown (e); + + bool touchingMoveNub = this.moveNub.IsPointTouching(new Point(e.X, e.Y), false); + + if (this.mode != EditingMode.NotEditing && (e.Button == MouseButtons.Right || touchingMoveNub)) + { + this.tracking = true; + this.startMouseXY = new Point(e.X, e.Y); + this.startClickPoint = this.clickPoint; + this.Cursor = this.handCursorMouseDown; + UpdateStatusText(); + } + else if (e.Button == MouseButtons.Left) + { + if (saved != null) + { + Rectangle bounds = Utility.GetRegionBounds(saved.Region); + bounds.Inflate(font.Height, font.Height); + + if (lines != null && bounds.Contains(e.X, e.Y)) + { + Position p = PointToTextPosition(new PointF(e.X, e.Y + (font.Height / 2))); + linePos = p.Line; + textPos = p.Offset; + RedrawText(true); + return; + } + } + + switch (mode) + { + case EditingMode.Editing: + SaveHistoryMemento(); + StopEditing(); + break; + + case EditingMode.EmptyEdit: + RedrawText(false); + StopEditing(); + break; + } + + clickPoint = new Point(e.X, e.Y); + StartEditing(); + RedrawText(true); + } + } + + protected override void OnPulse() + { + base.OnPulse(); + + if (!pulseEnabled) + { + return; + } + + TimeSpan ts = (DateTime.Now - startTime); + long ms = Utility.TicksToMs(ts.Ticks); + + bool pulseCursorState; + + if (0 == ((ms / cursorInterval) % 2)) + { + pulseCursorState = true; + } + else + { + pulseCursorState = false; + } + + pulseCursorState &= this.Focused; + + if (IsFormActive) + { + pulseCursorState &= ((ModifierKeys & Keys.Control) == 0); + } + + if (pulseCursorState != lastPulseCursorState) + { + RedrawText(pulseCursorState); + lastPulseCursorState = pulseCursorState; + } + + if (IsFormActive && (ModifierKeys & Keys.Control) != 0) + { + // hide the nub while Ctrl is held down + this.moveNub.Visible = false; + } + else + { + this.moveNub.Visible = true; + } + + // don't show the nub while the user is moving the text around + this.moveNub.Visible &= !tracking; + + // don't show the nub when the user has tapped Ctrl + this.moveNub.Visible &= this.enableNub; + + // Oscillate between 25% and 100% alpha over a period of 2 seconds + // Alpha value of 100% is sustained for a large duration of this period + const int period = 10000 * 2000; // 10000 ticks per ms, 2000ms per second + long tick = ts.Ticks % period; + double sin = Math.Sin(((double)tick / (double)period) * (2.0 * Math.PI)); + // sin is [-1, +1] + + sin = Math.Min(0.5, sin); + // sin is [-1, +0.5] + + sin += 1.0; + // sin is [0, 1.5] + + sin /= 2.0; + // sin is [0, 0.75] + + sin += 0.25; + // sin is [0.25, 1] + + if (this.moveNub != null) + { + int newAlpha = (int)(sin * 255.0); + int clampedAlpha = Utility.Clamp(newAlpha, 0, 255); + this.moveNub.Alpha = clampedAlpha; + } + + PlaceMoveNub(); + } + + protected override void OnPasteQuery(IDataObject data, out bool canHandle) + { + base.OnPasteQuery(data, out canHandle); + + if (data.GetDataPresent(DataFormats.StringFormat, true) && + this.Active && + this.mode != EditingMode.NotEditing) + { + canHandle = true; + } + } + + protected override void OnPaste(IDataObject data, out bool handled) + { + base.OnPaste (data, out handled); + + if (data.GetDataPresent(DataFormats.StringFormat, true) && + this.Active && + this.mode != EditingMode.NotEditing) + { + ++this.ignoreRedraw; + string text = (string)data.GetData(DataFormats.StringFormat, true); + + foreach (char c in text) + { + if (c == '\n') + { + this.PerformEnter(); + } + else + { + this.PerformKeyPress(new KeyPressEventArgs(c)); + } + } + + handled = true; + --this.ignoreRedraw; + + this.moveNub.Visible = true; + + this.RedrawText(false); + } + } + + private void InsertCharIntoString(char c) + { + lines[linePos] = ((string)lines[linePos]).Insert(textPos, c.ToString()); + this.sizes = null; + } + + public TextTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.TextToolIcon.png"), + PdnResources.GetString("TextTool.Name"), + PdnResources.GetString("TextTool.HelpText"), + 't', + false, + ToolBarConfigItems.Brush | ToolBarConfigItems.Text | ToolBarConfigItems.AlphaBlending | ToolBarConfigItems.Antialiasing) + { + } + } +} diff --git a/src/tools/ZoomTool.cs b/src/tools/ZoomTool.cs new file mode 100644 index 0000000..0018a06 --- /dev/null +++ b/src/tools/ZoomTool.cs @@ -0,0 +1,243 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +namespace PaintDotNet.Tools +{ + /// + /// Allows the user to click on the image to zoom to that location + /// + internal class ZoomTool + : Tool + { + private bool moveOffsetMode = false; + private MouseButtons mouseDown; + private Point downPt; + private Point lastPt; + private Rectangle rect = Rectangle.Empty; + private Cursor cursorZoomIn; + private Cursor cursorZoomOut; + private Cursor cursorZoom; + private Cursor cursorZoomPan; + private SelectionRenderer outlineRenderer; + private Selection outline; + + public ZoomTool(DocumentWorkspace documentWorkspace) + : base(documentWorkspace, + PdnResources.GetImageResource("Icons.ZoomToolIcon.png"), + PdnResources.GetString("ZoomTool.Name"), + PdnResources.GetString("ZoomTool.HelpText"), + 'z', + false, + ToolBarConfigItems.None) + { + this.mouseDown = MouseButtons.None; + } + + protected override void OnActivate() + { + this.cursorZoom = new Cursor(PdnResources.GetResourceStream("Cursors.ZoomToolCursor.cur")); + this.cursorZoomIn = new Cursor(PdnResources.GetResourceStream("Cursors.ZoomInToolCursor.cur")); + this.cursorZoomOut = new Cursor(PdnResources.GetResourceStream("Cursors.ZoomOutToolCursor.cur")); + this.cursorZoomPan = new Cursor(PdnResources.GetResourceStream("Cursors.ZoomOutToolCursor.cur")); + this.Cursor = this.cursorZoom; + + base.OnActivate(); + + this.outline = new Selection(); + this.outlineRenderer = new SelectionRenderer(this.RendererList, this.outline, this.DocumentWorkspace); + this.outlineRenderer.InvertedTinting = true; + this.outlineRenderer.TintColor = Color.FromArgb(128, 255, 255, 255); + this.outlineRenderer.ResetOutlineWhiteOpacity(); + this.RendererList.Add(this.outlineRenderer, true); + } + + protected override void OnDeactivate() + { + if (cursorZoom != null) + { + cursorZoom.Dispose(); + cursorZoom = null; + } + + if (cursorZoomIn != null) + { + cursorZoomIn.Dispose(); + cursorZoomIn = null; + } + + if (cursorZoomOut != null) + { + cursorZoomOut.Dispose(); + cursorZoomOut = null; + } + + if (cursorZoomPan != null) + { + cursorZoomPan.Dispose(); + cursorZoomPan = null; + } + + this.RendererList.Remove(this.outlineRenderer); + this.outlineRenderer.Dispose(); + this.outlineRenderer = null; + + base.OnDeactivate(); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (mouseDown != MouseButtons.None) + { + this.moveOffsetMode = true; + } + else + { + switch (e.Button) + { + case MouseButtons.Left: + Cursor = cursorZoomIn; + break; + + case MouseButtons.Middle: + Cursor = cursorZoomPan; + break; + + case MouseButtons.Right: + Cursor = cursorZoomOut; + break; + } + + mouseDown = e.Button; + lastPt = new Point(e.X, e.Y); + downPt = lastPt; + OnMouseMove(e); + } + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + if (!e.Handled) + { + if (this.mouseDown != MouseButtons.None) + { + e.Handled = true; + } + } + + base.OnKeyPress(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove (e); + + Point thisPt = new Point(e.X, e.Y); + + if (this.moveOffsetMode) + { + Size delta = new Size(thisPt.X - lastPt.X, thisPt.Y - lastPt.Y); + downPt.X += delta.Width; + downPt.Y += delta.Height; + } + + if ((e.Button == MouseButtons.Left && + mouseDown == MouseButtons.Left && + Utility.Distance(thisPt, downPt) > 10) || // if they've moved the mouse more than 10 pixels since they clicked + !rect.IsEmpty) //don't undraw the rectangle + { + rect = Utility.PointsToRectangle(downPt, thisPt); + rect.Intersect(ActiveLayer.Bounds); + UpdateDrawnRect(); + } + else if (e.Button == MouseButtons.Middle && mouseDown == MouseButtons.Middle) + { + PointF lastScrollPosition = DocumentWorkspace.DocumentScrollPositionF; + lastScrollPosition.X += thisPt.X - lastPt.X; + lastScrollPosition.Y += thisPt.Y - lastPt.Y; + DocumentWorkspace.DocumentScrollPositionF = lastScrollPosition; + Update(); + } + else + { + rect = Rectangle.Empty; + } + + lastPt = thisPt; + } + + protected override void OnMouseUp(MouseEventArgs e) + { + base.OnMouseUp (e); + OnMouseMove(e); + bool resetMouseDown = true; + + Cursor = cursorZoom; + + if (this.moveOffsetMode) + { + this.moveOffsetMode = false; + resetMouseDown = false; + } + else if (mouseDown == MouseButtons.Left || mouseDown == MouseButtons.Right) + { + Rectangle zoomTo = rect; + + rect = Rectangle.Empty; + UpdateDrawnRect(); + + if (e.Button == MouseButtons.Left) + { + if (Utility.Magnitude(new PointF(zoomTo.Width, zoomTo.Height)) < 10) + { + DocumentWorkspace.ZoomIn(); + DocumentWorkspace.RecenterView(new Point(e.X, e.Y)); + } + else + { + DocumentWorkspace.ZoomToRectangle(zoomTo); + } + } + else + { + DocumentWorkspace.ZoomOut(); + DocumentWorkspace.RecenterView(new Point(e.X, e.Y)); + } + + this.outline.Reset(); + } + + if (resetMouseDown) + { + mouseDown = MouseButtons.None; + } + } + + private void UpdateDrawnRect() + { + if (!rect.IsEmpty) + { + this.outline.PerformChanging(); + this.outline.Reset(); + this.outline.SetContinuation(rect, CombineMode.Replace); + this.outlineRenderer.ResetOutlineWhiteOpacity(); + this.outline.CommitContinuation(); + this.outline.PerformChanged(); + Update(); + } + } + } +} diff --git a/src/update.bat b/src/update.bat new file mode 100644 index 0000000..a708109 --- /dev/null +++ b/src/update.bat @@ -0,0 +1 @@ +start setup\release\PaintDotNetSetup /skipConfig diff --git a/src/zeroresx.bat b/src/zeroresx.bat new file mode 100644 index 0000000..9fdfa55 --- /dev/null +++ b/src/zeroresx.bat @@ -0,0 +1,5 @@ +@echo off +rem This will zero out all the resx files. It will cut down on the exe and dll sizes. +type nul > zerobyte +for %%f in (*.resx) do copy /y zerobyte %%f +del zerobyte