├── GoogleImageShell
├── ImageFileType.cs
├── App.config
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── Program.cs
├── ConfigForm.cs
├── UploadForm.cs
├── UploadForm.Designer.cs
├── ShortcutMenu.cs
├── GoogleImageShell.csproj
├── ConfigForm.resx
├── UploadForm.resx
├── ConfigForm.Designer.cs
└── GoogleImages.cs
├── README.md
├── GoogleImageShell.sln
├── LICENSE.txt
└── .gitignore
/GoogleImageShell/ImageFileType.cs:
--------------------------------------------------------------------------------
1 | namespace GoogleImageShell
2 | {
3 | public enum ImageFileType
4 | {
5 | JPG,
6 | GIF,
7 | PNG,
8 | BMP
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/GoogleImageShell/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/GoogleImageShell/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GoogleImageShell/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 |
4 | namespace GoogleImageShell
5 | {
6 | public static class Program
7 | {
8 | [STAThread]
9 | public static void Main(string[] args)
10 | {
11 | Application.EnableVisualStyles();
12 | Application.SetCompatibleTextRenderingDefault(false);
13 | if (args.Length >= 1 && args[0].ToLower() == "search")
14 | {
15 | Application.Run(new UploadForm(args));
16 | }
17 | else
18 | {
19 | Application.Run(new ConfigForm());
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GoogleImageShell
2 |
3 | Search Google Images from the comfort of Windows Explorer!
4 |
5 | 
6 |
7 | ## Requirements
8 |
9 | - Windows 7 or above
10 | - .NET 4.6.1
11 |
12 | ## Installation
13 |
14 | Install the program by running the executable and clicking 'Install'.
15 | To install for all users, run the program as an administrator and check
16 | the "Install/uninstall for all users" box.
17 |
18 | To uninstall, follow the same steps but click 'Uninstall'. If you
19 | installed the program for all users, you must also uninstall it for
20 | all users.
21 |
22 | **Note**: Do not delete, move, or rename the executable file before
23 | uninstalling the program; otherwise the shortcut entry will stop
24 | functioning!
25 |
26 | ## Supported image formats
27 |
28 | - JPG
29 | - GIF
30 | - PNG
31 | - BMP
32 |
33 | ## License
34 |
35 | Distributed under the [MIT License](http://opensource.org/licenses/MIT).
36 |
37 | This program is not affiliated with or endorsed by Google.
38 |
--------------------------------------------------------------------------------
/GoogleImageShell.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26403.7
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleImageShell", "GoogleImageShell\GoogleImageShell.csproj", "{AAF5D166-15DE-4633-8414-2B1A478E1F8E}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {AAF5D166-15DE-4633-8414-2B1A478E1F8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {AAF5D166-15DE-4633-8414-2B1A478E1F8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {AAF5D166-15DE-4633-8414-2B1A478E1F8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {AAF5D166-15DE-4633-8414-2B1A478E1F8E}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Andrew Sun (@crossbowffs)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/GoogleImageShell/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace GoogleImageShell.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/GoogleImageShell/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("GoogleImageShell")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("GoogleImageShell")]
13 | [assembly: AssemblyCopyright("Copyright © crossbowffs 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("aaf5d166-15de-4633-8414-2b1a478e1f8e")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.2.0.0")]
36 | [assembly: AssemblyFileVersion("1.2.0.0")]
37 |
--------------------------------------------------------------------------------
/GoogleImageShell/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace GoogleImageShell.Properties
12 | {
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GoogleImageShell.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/GoogleImageShell/ConfigForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using System.Windows.Forms;
5 |
6 | namespace GoogleImageShell
7 | {
8 | public partial class ConfigForm : Form
9 | {
10 | public ConfigForm()
11 | {
12 | InitializeComponent();
13 | }
14 |
15 | private void ConfigForm_Load(object sender, EventArgs e)
16 | {
17 | Version version = Assembly.GetExecutingAssembly().GetName().Version;
18 | Text += $" v{version.Major}.{version.Minor}.{version.Build}";
19 | foreach (object type in Enum.GetValues(typeof(ImageFileType)))
20 | {
21 | fileTypeListBox.Items.Add(type, true);
22 | }
23 | }
24 |
25 | private void installButton_Click(object sender, EventArgs e)
26 | {
27 | string menuText = menuTextTextBox.Text;
28 | bool includeFileName = includeFileNameCheckBox.Checked;
29 | bool allUsers = allUsersCheckBox.Checked;
30 | bool resizeOnUpload = resizeOnUploadCheckbox.Checked;
31 | ImageFileType[] types = fileTypeListBox.CheckedItems.Cast().ToArray();
32 | Install(menuText, includeFileName, allUsers, resizeOnUpload, types);
33 | }
34 |
35 | private void uninstallButton_Click(object sender, EventArgs e)
36 | {
37 | bool allUsers = allUsersCheckBox.Checked;
38 | ImageFileType[] types = fileTypeListBox.CheckedItems.Cast().ToArray();
39 | Uninstall(allUsers, types);
40 | }
41 |
42 | private static void Install(string menuText, bool includeFileName, bool allUsers, bool resizeOnUpload, ImageFileType[] types)
43 | {
44 | try
45 | {
46 | ShortcutMenu.InstallHandler(menuText, includeFileName, allUsers, resizeOnUpload, types);
47 | }
48 | catch (Exception ex)
49 | {
50 | ErrorMsgBox(
51 | "Installation failed",
52 | "Could not add context menu entries to Windows Explorer.\n\n" +
53 | ex.Message);
54 | return;
55 | }
56 | InfoMsgBox(
57 | "Installation succeeded",
58 | "Context menu entries were added to Windows Explorer. " +
59 | "Remember to reinstall the program if you move or rename it!");
60 | }
61 |
62 | private static void Uninstall(bool allUsers, ImageFileType[] types)
63 | {
64 | try
65 | {
66 | ShortcutMenu.UninstallHandler(allUsers, types);
67 | }
68 | catch (Exception ex)
69 | {
70 | ErrorMsgBox(
71 | "Uninstallation failed",
72 | "Could not remove context menu entries from Windows Explorer.\n\n" +
73 | ex.Message);
74 | return;
75 | }
76 | InfoMsgBox(
77 | "Uninstallation succeeded",
78 | "Context menu entries were removed from Windows Explorer.");
79 | }
80 |
81 | private static void InfoMsgBox(string title, string message)
82 | {
83 | MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
84 | }
85 |
86 | private static void ErrorMsgBox(string title, string message)
87 | {
88 | MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/GoogleImageShell/UploadForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using System.Windows.Forms;
6 |
7 | namespace GoogleImageShell
8 | {
9 | public partial class UploadForm : Form
10 | {
11 | private readonly string _imagePath;
12 | private readonly bool _includeFileName;
13 | private readonly bool _resizeOnUpload;
14 |
15 | private readonly CancellationTokenSource _cancelTokenSource = new CancellationTokenSource();
16 |
17 | public UploadForm(string[] args)
18 | {
19 | InitializeComponent();
20 | for (int i = 1; i < args.Length; ++i)
21 | {
22 | string arg = args[i];
23 | switch (arg)
24 | {
25 | case "-n":
26 | _includeFileName = true;
27 | break;
28 | case "-r":
29 | _resizeOnUpload = true;
30 | break;
31 | default:
32 | _imagePath = arg;
33 | break;
34 | }
35 | }
36 | }
37 |
38 | private void UploadForm_Load(object sender, EventArgs e)
39 | {
40 | Log("Uploading image: " + _imagePath);
41 | Log("Include file name: " + _includeFileName);
42 | Log("Resize on upload: " + _resizeOnUpload);
43 |
44 | Task task = GoogleImages.Search(_imagePath, _includeFileName, _resizeOnUpload, _cancelTokenSource.Token);
45 | task.ContinueWith(OnUploadComplete, TaskScheduler.FromCurrentSynchronizationContext());
46 | }
47 |
48 | private void UploadForm_FormClosing(object sender, FormClosingEventArgs e)
49 | {
50 | _cancelTokenSource.Cancel();
51 | }
52 |
53 | private void cancelButton_Click(object sender, EventArgs e)
54 | {
55 | Close();
56 | }
57 |
58 | private void OnUploadComplete(Task task)
59 | {
60 | if (task.Status == TaskStatus.Faulted)
61 | {
62 | Log("Failed to upload image: " + task.Exception.InnerException);
63 | }
64 | else if (task.Status == TaskStatus.Canceled)
65 | {
66 | Log("Upload canceled by user");
67 | }
68 | else if (task.Status == TaskStatus.RanToCompletion)
69 | {
70 | Log("Image uploaded successfully, opening results page");
71 | if (TryOpenBrowser(task))
72 | {
73 | Close();
74 | return;
75 | }
76 | }
77 | else
78 | {
79 | Log("Unexpected task result status: " + task.Status);
80 | }
81 | cancelButton.Text = "Close";
82 | progressBar.Style = ProgressBarStyle.Continuous;
83 | progressBar.Value = 0;
84 | }
85 |
86 | private bool TryOpenBrowser(Task task)
87 | {
88 | try
89 | {
90 | Process.Start(task.Result);
91 | return true;
92 | }
93 | catch (Exception ex)
94 | {
95 | Log("Failed to open browser: " + ex);
96 | return false;
97 | }
98 | }
99 |
100 | private void Log(string text)
101 | {
102 | logTextBox.AppendText(text);
103 | logTextBox.AppendText(Environment.NewLine);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/GoogleImageShell/UploadForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace GoogleImageShell
2 | {
3 | partial class UploadForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.progressBar = new System.Windows.Forms.ProgressBar();
32 | this.cancelButton = new System.Windows.Forms.Button();
33 | this.logTextBox = new System.Windows.Forms.TextBox();
34 | this.SuspendLayout();
35 | //
36 | // progressBar
37 | //
38 | this.progressBar.Location = new System.Drawing.Point(12, 176);
39 | this.progressBar.Name = "progressBar";
40 | this.progressBar.Size = new System.Drawing.Size(300, 23);
41 | this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
42 | this.progressBar.TabIndex = 0;
43 | //
44 | // cancelButton
45 | //
46 | this.cancelButton.Location = new System.Drawing.Point(318, 176);
47 | this.cancelButton.Name = "cancelButton";
48 | this.cancelButton.Size = new System.Drawing.Size(104, 23);
49 | this.cancelButton.TabIndex = 1;
50 | this.cancelButton.Text = "Cancel";
51 | this.cancelButton.UseVisualStyleBackColor = true;
52 | this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
53 | //
54 | // logTextBox
55 | //
56 | this.logTextBox.Location = new System.Drawing.Point(12, 12);
57 | this.logTextBox.Multiline = true;
58 | this.logTextBox.Name = "logTextBox";
59 | this.logTextBox.ReadOnly = true;
60 | this.logTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
61 | this.logTextBox.Size = new System.Drawing.Size(410, 158);
62 | this.logTextBox.TabIndex = 2;
63 | this.logTextBox.WordWrap = false;
64 | //
65 | // UploadForm
66 | //
67 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
68 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
69 | this.AutoSize = true;
70 | this.ClientSize = new System.Drawing.Size(434, 211);
71 | this.Controls.Add(this.logTextBox);
72 | this.Controls.Add(this.cancelButton);
73 | this.Controls.Add(this.progressBar);
74 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
75 | this.MaximizeBox = false;
76 | this.Name = "UploadForm";
77 | this.ShowIcon = false;
78 | this.Text = "GoogleImageShell";
79 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.UploadForm_FormClosing);
80 | this.Load += new System.EventHandler(this.UploadForm_Load);
81 | this.ResumeLayout(false);
82 | this.PerformLayout();
83 |
84 | }
85 |
86 | #endregion
87 |
88 | private System.Windows.Forms.ProgressBar progressBar;
89 | private System.Windows.Forms.Button cancelButton;
90 | private System.Windows.Forms.TextBox logTextBox;
91 | }
92 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 |
83 | # Visual Studio profiler
84 | *.psess
85 | *.vsp
86 | *.vspx
87 | *.sap
88 |
89 | # TFS 2012 Local Workspace
90 | $tf/
91 |
92 | # Guidance Automation Toolkit
93 | *.gpState
94 |
95 | # ReSharper is a .NET coding add-in
96 | _ReSharper*/
97 | *.[Rr]e[Ss]harper
98 | *.DotSettings.user
99 |
100 | # JustCode is a .NET coding add-in
101 | .JustCode
102 |
103 | # TeamCity is a build add-in
104 | _TeamCity*
105 |
106 | # DotCover is a Code Coverage Tool
107 | *.dotCover
108 |
109 | # NCrunch
110 | _NCrunch_*
111 | .*crunch*.local.xml
112 | nCrunchTemp_*
113 |
114 | # MightyMoose
115 | *.mm.*
116 | AutoTest.Net/
117 |
118 | # Web workbench (sass)
119 | .sass-cache/
120 |
121 | # Installshield output folder
122 | [Ee]xpress/
123 |
124 | # DocProject is a documentation generator add-in
125 | DocProject/buildhelp/
126 | DocProject/Help/*.HxT
127 | DocProject/Help/*.HxC
128 | DocProject/Help/*.hhc
129 | DocProject/Help/*.hhk
130 | DocProject/Help/*.hhp
131 | DocProject/Help/Html2
132 | DocProject/Help/html
133 |
134 | # Click-Once directory
135 | publish/
136 |
137 | # Publish Web Output
138 | *.[Pp]ublish.xml
139 | *.azurePubxml
140 | # TODO: Comment the next line if you want to checkin your web deploy settings
141 | # but database connection strings (with potential passwords) will be unencrypted
142 | *.pubxml
143 | *.publishproj
144 |
145 | # NuGet Packages
146 | *.nupkg
147 | # The packages folder can be ignored because of Package Restore
148 | **/packages/*
149 | # except build/, which is used as an MSBuild target.
150 | !**/packages/build/
151 | # Uncomment if necessary however generally it will be regenerated when needed
152 | #!**/packages/repositories.config
153 |
154 | # Windows Azure Build Output
155 | csx/
156 | *.build.csdef
157 |
158 | # Windows Azure Emulator
159 | efc/
160 | rfc/
161 |
162 | # Windows Store app package directory
163 | AppPackages/
164 |
165 | # Visual Studio cache files
166 | # files ending in .cache can be ignored
167 | *.[Cc]ache
168 | # but keep track of directories ending in .cache
169 | !*.[Cc]ache/
170 |
171 | # Others
172 | ClientBin/
173 | [Ss]tyle[Cc]op.*
174 | ~$*
175 | *~
176 | *.dbmdl
177 | *.dbproj.schemaview
178 | *.pfx
179 | *.publishsettings
180 | node_modules/
181 | orleans.codegen.cs
182 |
183 | # RIA/Silverlight projects
184 | Generated_Code/
185 |
186 | # Backup & report files from converting an old project file
187 | # to a newer Visual Studio version. Backup files are not needed,
188 | # because we have git ;-)
189 | _UpgradeReport_Files/
190 | Backup*/
191 | UpgradeLog*.XML
192 | UpgradeLog*.htm
193 |
194 | # SQL Server files
195 | *.mdf
196 | *.ldf
197 |
198 | # Business Intelligence projects
199 | *.rdl.data
200 | *.bim.layout
201 | *.bim_*.settings
202 |
203 | # Microsoft Fakes
204 | FakesAssemblies/
205 |
206 | # GhostDoc plugin setting file
207 | *.GhostDoc.xml
208 |
209 | # Node.js Tools for Visual Studio
210 | .ntvs_analysis.dat
211 |
212 | # Visual Studio 6 build log
213 | *.plg
214 |
215 | # Visual Studio 6 workspace options file
216 | *.opt
217 |
218 | # Visual Studio LightSwitch build output
219 | **/*.HTMLClient/GeneratedArtifacts
220 | **/*.DesktopClient/GeneratedArtifacts
221 | **/*.DesktopClient/ModelManifest.xml
222 | **/*.Server/GeneratedArtifacts
223 | **/*.Server/ModelManifest.xml
224 | _Pvt_Extensions
225 |
226 | # Paket dependency manager
227 | .paket/paket.exe
228 |
229 | # FAKE - F# Make
230 | .fake/
231 |
--------------------------------------------------------------------------------
/GoogleImageShell/ShortcutMenu.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Reflection;
5 |
6 | namespace GoogleImageShell
7 | {
8 | public static class ShortcutMenu
9 | {
10 | private const string ShellKeyPathFormat = @"Software\Classes\SystemFileAssociations\{0}\shell";
11 | private const string VerbName = "GoogleImageShell";
12 | private const string CommandKey = "command";
13 | private static readonly Dictionary FileTypeMap = new Dictionary
14 | {
15 | {ImageFileType.JPG, new[] {".jpg", ".jpe", ".jpeg", ".jfif"}},
16 | {ImageFileType.GIF, new[] {".gif"}},
17 | {ImageFileType.PNG, new[] {".png"}},
18 | {ImageFileType.BMP, new[] {".bmp"}}
19 | };
20 |
21 | ///
22 | /// Creates a shell command to run this program.
23 | ///
24 | /// Whether to include the image file name when uploading
25 | /// Whether to resize large images when uploading
26 | /// The shell command string
27 | private static string CreateProgramCommand(bool includeFileName, bool resizeOnUpload)
28 | {
29 | string exePath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;
30 | string command = exePath + " search \"%1\"";
31 | if (includeFileName)
32 | {
33 | command += " -n";
34 | }
35 | if (resizeOnUpload)
36 | {
37 | command += " -r";
38 | }
39 | return command;
40 | }
41 |
42 | ///
43 | /// Opens the shell key corresponding to this program,
44 | /// with read/write permissions.
45 | ///
46 | /// true if installing for all users, false if for current user
47 | /// File extension (".jpg", ".png", etc)
48 | /// Registry key object for the specified user/file type
49 | private static RegistryKey GetShellKey(bool allUsers, string fileType)
50 | {
51 | RegistryKey hiveKey = allUsers ? Registry.LocalMachine : Registry.CurrentUser;
52 | string shellPath = string.Format(ShellKeyPathFormat, fileType);
53 | RegistryKey shellKey = hiveKey.CreateSubKey(shellPath);
54 | return shellKey;
55 | }
56 |
57 | ///
58 | /// Adds the program to the Windows Explorer context menu.
59 | ///
60 | /// The text to display on the context menu
61 | /// Whether to include the image file name when uploading
62 | /// Whether to install for all users
63 | /// Whether to resize large images when uploading
64 | /// Image file types to install the handler for
65 | public static void InstallHandler(string menuText, bool includeFileName, bool allUsers, bool resizeOnUpload, ImageFileType[] types)
66 | {
67 | string command = CreateProgramCommand(includeFileName, resizeOnUpload);
68 | foreach (ImageFileType fileType in types)
69 | {
70 | foreach (string typeExt in FileTypeMap[fileType])
71 | {
72 | using (RegistryKey shellKey = GetShellKey(allUsers, typeExt))
73 | using (RegistryKey verbKey = shellKey.CreateSubKey(VerbName))
74 | using (RegistryKey cmdKey = verbKey.CreateSubKey(CommandKey))
75 | {
76 | verbKey.SetValue("", menuText);
77 | cmdKey.SetValue("", command);
78 | }
79 | }
80 | }
81 | }
82 |
83 | ///
84 | /// Removes the program from the Windows Explorer context menu.
85 | ///
86 | /// Whether to uninstall for all users
87 | /// Image file types to uninstall the handler for
88 | public static void UninstallHandler(bool allUsers, ImageFileType[] types)
89 | {
90 | foreach (ImageFileType fileType in types)
91 | {
92 | foreach (string typeExt in FileTypeMap[fileType])
93 | {
94 | using (RegistryKey shellKey = GetShellKey(allUsers, typeExt))
95 | {
96 | shellKey?.DeleteSubKeyTree(VerbName, false);
97 | }
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/GoogleImageShell/GoogleImageShell.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AAF5D166-15DE-4633-8414-2B1A478E1F8E}
8 | WinExe
9 | Properties
10 | GoogleImageShell
11 | GoogleImageShell
12 | v4.6.1
13 | 512
14 | true
15 | false
16 | publish\
17 | true
18 | Disk
19 | false
20 | Foreground
21 | 7
22 | Days
23 | false
24 | false
25 | true
26 | 0
27 | 1.0.0.%2a
28 | false
29 | true
30 |
31 |
32 | AnyCPU
33 | true
34 | full
35 | false
36 | bin\Debug\
37 | DEBUG;TRACE
38 | prompt
39 | 4
40 | default
41 |
42 |
43 | AnyCPU
44 | pdbonly
45 | true
46 | bin\Release\
47 | TRACE
48 | prompt
49 | 4
50 |
51 |
52 | GoogleImageShell.Program
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Form
63 |
64 |
65 | ConfigForm.cs
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | Form
74 |
75 |
76 | UploadForm.cs
77 |
78 |
79 | ConfigForm.cs
80 |
81 |
82 | ResXFileCodeGenerator
83 | Resources.Designer.cs
84 | Designer
85 |
86 |
87 | True
88 | Resources.resx
89 |
90 |
91 | UploadForm.cs
92 |
93 |
94 | SettingsSingleFileGenerator
95 | Settings.Designer.cs
96 |
97 |
98 | True
99 | Settings.settings
100 | True
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | False
109 | Microsoft .NET Framework 4.6.1 %28x86 and x64%29
110 | true
111 |
112 |
113 | False
114 | .NET Framework 3.5 SP1
115 | false
116 |
117 |
118 |
119 |
126 |
--------------------------------------------------------------------------------
/GoogleImageShell/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/GoogleImageShell/ConfigForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/GoogleImageShell/UploadForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/GoogleImageShell/ConfigForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace GoogleImageShell
2 | {
3 | partial class ConfigForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.installButton = new System.Windows.Forms.Button();
32 | this.uninstallButton = new System.Windows.Forms.Button();
33 | this.menuTextTextBox = new System.Windows.Forms.TextBox();
34 | this.menuTextLabel = new System.Windows.Forms.Label();
35 | this.includeFileNameCheckBox = new System.Windows.Forms.CheckBox();
36 | this.allUsersCheckBox = new System.Windows.Forms.CheckBox();
37 | this.fileTypeListBox = new System.Windows.Forms.CheckedListBox();
38 | this.fileTypeLabel = new System.Windows.Forms.Label();
39 | this.resizeOnUploadCheckbox = new System.Windows.Forms.CheckBox();
40 | this.SuspendLayout();
41 | //
42 | // installButton
43 | //
44 | this.installButton.Location = new System.Drawing.Point(12, 195);
45 | this.installButton.Name = "installButton";
46 | this.installButton.Size = new System.Drawing.Size(175, 30);
47 | this.installButton.TabIndex = 0;
48 | this.installButton.Text = "Install";
49 | this.installButton.UseVisualStyleBackColor = true;
50 | this.installButton.Click += new System.EventHandler(this.installButton_Click);
51 | //
52 | // uninstallButton
53 | //
54 | this.uninstallButton.Location = new System.Drawing.Point(197, 195);
55 | this.uninstallButton.Name = "uninstallButton";
56 | this.uninstallButton.Size = new System.Drawing.Size(175, 30);
57 | this.uninstallButton.TabIndex = 1;
58 | this.uninstallButton.Text = "Uninstall";
59 | this.uninstallButton.UseVisualStyleBackColor = true;
60 | this.uninstallButton.Click += new System.EventHandler(this.uninstallButton_Click);
61 | //
62 | // menuTextTextBox
63 | //
64 | this.menuTextTextBox.Location = new System.Drawing.Point(110, 12);
65 | this.menuTextTextBox.Name = "menuTextTextBox";
66 | this.menuTextTextBox.Size = new System.Drawing.Size(262, 20);
67 | this.menuTextTextBox.TabIndex = 0;
68 | this.menuTextTextBox.Text = "Search on Google Images";
69 | //
70 | // menuTextLabel
71 | //
72 | this.menuTextLabel.AutoSize = true;
73 | this.menuTextLabel.Location = new System.Drawing.Point(12, 15);
74 | this.menuTextLabel.Name = "menuTextLabel";
75 | this.menuTextLabel.Size = new System.Drawing.Size(92, 13);
76 | this.menuTextLabel.TabIndex = 1;
77 | this.menuTextLabel.Text = "Context menu text";
78 | //
79 | // includeFileNameCheckBox
80 | //
81 | this.includeFileNameCheckBox.AutoSize = true;
82 | this.includeFileNameCheckBox.Checked = true;
83 | this.includeFileNameCheckBox.CheckState = System.Windows.Forms.CheckState.Checked;
84 | this.includeFileNameCheckBox.Location = new System.Drawing.Point(15, 38);
85 | this.includeFileNameCheckBox.Name = "includeFileNameCheckBox";
86 | this.includeFileNameCheckBox.Size = new System.Drawing.Size(152, 17);
87 | this.includeFileNameCheckBox.TabIndex = 2;
88 | this.includeFileNameCheckBox.Text = "Include file name in search";
89 | this.includeFileNameCheckBox.UseVisualStyleBackColor = true;
90 | //
91 | // allUsersCheckBox
92 | //
93 | this.allUsersCheckBox.AutoSize = true;
94 | this.allUsersCheckBox.Location = new System.Drawing.Point(15, 85);
95 | this.allUsersCheckBox.Name = "allUsersCheckBox";
96 | this.allUsersCheckBox.Size = new System.Drawing.Size(307, 17);
97 | this.allUsersCheckBox.TabIndex = 3;
98 | this.allUsersCheckBox.Text = "Install/uninstall for all users (requires administrator privileges)";
99 | this.allUsersCheckBox.UseVisualStyleBackColor = true;
100 | //
101 | // fileTypeListBox
102 | //
103 | this.fileTypeListBox.CheckOnClick = true;
104 | this.fileTypeListBox.FormattingEnabled = true;
105 | this.fileTypeListBox.Location = new System.Drawing.Point(12, 125);
106 | this.fileTypeListBox.Name = "fileTypeListBox";
107 | this.fileTypeListBox.Size = new System.Drawing.Size(360, 64);
108 | this.fileTypeListBox.TabIndex = 4;
109 | //
110 | // fileTypeLabel
111 | //
112 | this.fileTypeLabel.AutoSize = true;
113 | this.fileTypeLabel.Location = new System.Drawing.Point(12, 109);
114 | this.fileTypeLabel.Name = "fileTypeLabel";
115 | this.fileTypeLabel.Size = new System.Drawing.Size(168, 13);
116 | this.fileTypeLabel.TabIndex = 5;
117 | this.fileTypeLabel.Text = "Install/uninstall for these file types:";
118 | //
119 | // resizeOnUploadCheckbox
120 | //
121 | this.resizeOnUploadCheckbox.AutoSize = true;
122 | this.resizeOnUploadCheckbox.Checked = true;
123 | this.resizeOnUploadCheckbox.CheckState = System.Windows.Forms.CheckState.Checked;
124 | this.resizeOnUploadCheckbox.Location = new System.Drawing.Point(15, 62);
125 | this.resizeOnUploadCheckbox.Name = "resizeOnUploadCheckbox";
126 | this.resizeOnUploadCheckbox.Size = new System.Drawing.Size(202, 17);
127 | this.resizeOnUploadCheckbox.TabIndex = 6;
128 | this.resizeOnUploadCheckbox.Text = "Resize large images before uploading";
129 | this.resizeOnUploadCheckbox.UseVisualStyleBackColor = true;
130 | //
131 | // ConfigForm
132 | //
133 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
134 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
135 | this.AutoSize = true;
136 | this.ClientSize = new System.Drawing.Size(384, 234);
137 | this.Controls.Add(this.resizeOnUploadCheckbox);
138 | this.Controls.Add(this.fileTypeLabel);
139 | this.Controls.Add(this.fileTypeListBox);
140 | this.Controls.Add(this.allUsersCheckBox);
141 | this.Controls.Add(this.includeFileNameCheckBox);
142 | this.Controls.Add(this.menuTextLabel);
143 | this.Controls.Add(this.menuTextTextBox);
144 | this.Controls.Add(this.uninstallButton);
145 | this.Controls.Add(this.installButton);
146 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
147 | this.MaximizeBox = false;
148 | this.Name = "ConfigForm";
149 | this.ShowIcon = false;
150 | this.Text = "GoogleImageShell";
151 | this.Load += new System.EventHandler(this.ConfigForm_Load);
152 | this.ResumeLayout(false);
153 | this.PerformLayout();
154 |
155 | }
156 |
157 | #endregion
158 |
159 | private System.Windows.Forms.Button installButton;
160 | private System.Windows.Forms.Button uninstallButton;
161 | private System.Windows.Forms.TextBox menuTextTextBox;
162 | private System.Windows.Forms.Label menuTextLabel;
163 | private System.Windows.Forms.CheckBox includeFileNameCheckBox;
164 | private System.Windows.Forms.CheckBox allUsersCheckBox;
165 | private System.Windows.Forms.CheckedListBox fileTypeListBox;
166 | private System.Windows.Forms.Label fileTypeLabel;
167 | private System.Windows.Forms.CheckBox resizeOnUploadCheckbox;
168 | }
169 | }
--------------------------------------------------------------------------------
/GoogleImageShell/GoogleImages.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Drawing;
3 | using System.Drawing.Imaging;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Net.Http;
8 | using System.Net.Http.Headers;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 |
12 | namespace GoogleImageShell
13 | {
14 | public static class GoogleImages
15 | {
16 | private const int MinImageDimension = 200;
17 | private const int MaxImageDimension = 800;
18 |
19 | ///
20 | /// Determines whether the input image should be resized,
21 | /// and if so, the optimal dimensions after resizing.
22 | ///
23 | /// Original size of the image
24 | /// Dimensions after resizing
25 | /// true if the image should be resized; false otherwise
26 | private static bool ShouldResize(Size originalSize, out Size newSize)
27 | {
28 | // Compute resize ratio (at LEAST ratioMin, at MOST ratioMax).
29 | // ratioMin is used to prevent the image from getting too small.
30 | // Note that ratioMax is calculated on the LARGER image dimension,
31 | // whereas ratioMin is calculated on the SMALLER image dimension.
32 | int origW = originalSize.Width;
33 | int origH = originalSize.Height;
34 | double ratioMax = Math.Min(MaxImageDimension / (double)origW, MaxImageDimension / (double)origH);
35 | double ratioMin = Math.Max(MinImageDimension / (double)origW, MinImageDimension / (double)origH);
36 | double ratio = Math.Max(ratioMax, ratioMin);
37 |
38 | // If resizing it would make it bigger, then don't bother
39 | if (ratio >= 1)
40 | {
41 | newSize = originalSize;
42 | return false;
43 | }
44 |
45 | int newW = (int)(origW * ratio);
46 | int newH = (int)(origH * ratio);
47 | newSize = new Size(newW, newH);
48 | return true;
49 | }
50 |
51 | ///
52 | /// Loads an image from disk into a byte array.
53 | ///
54 | /// Path to the image file
55 | /// Whether to allow resizing
56 | /// The loaded image, represented as a byte array
57 | private static byte[] LoadImageData(string imagePath, bool resize)
58 | {
59 | // Resize the image if user enabled the option
60 | // and the image is reasonably large
61 | if (resize)
62 | {
63 | try
64 | {
65 | using (Bitmap bmp = new Bitmap(imagePath))
66 | {
67 | if (ShouldResize(bmp.Size, out Size newSize))
68 | {
69 | using (var newBmp = new Bitmap(newSize.Width, newSize.Height))
70 | {
71 | using (Graphics g = Graphics.FromImage(newBmp))
72 | {
73 | g.DrawImage(bmp, new Rectangle(0, 0, newSize.Width, newSize.Height));
74 | }
75 |
76 | // Save as JPEG (format doesn't have to match file extension,
77 | // Google will take care of figuring out the correct format)
78 | using (var ms = new MemoryStream())
79 | {
80 | newBmp.Save(ms, ImageFormat.Jpeg);
81 | return ms.ToArray();
82 | }
83 | }
84 | }
85 | }
86 | }
87 | catch (Exception)
88 | {
89 | // Ignore exceptions (out of memory, invalid format, etc)
90 | // and fall back to just reading the raw file bytes
91 | }
92 | }
93 |
94 | // No resizing required or image is too small,
95 | // just load the bytes from disk directly
96 | return File.ReadAllBytes(imagePath);
97 | }
98 |
99 | ///
100 | /// Converts a byte array into base-64 format, using
101 | /// a format compatible with Google Images.
102 | ///
103 | /// Raw bytes to encode
104 | /// Base-64 encoded string
105 | private static string BinaryToBase64Compat(byte[] content)
106 | {
107 | // Uploaded image needs to be encoded in base-64,
108 | // with `+` replaced by `-` and `/` replaced by `_`
109 | string base64 = Convert.ToBase64String(content).Replace('+', '-').Replace('/', '_');
110 | return base64;
111 | }
112 |
113 | ///
114 | /// Asynchronously uploads the specified image to Google Images,
115 | /// and returns the URL of the results page.
116 | ///
117 | /// Path to the image file
118 | /// Whether to send the image file name to Google
119 | /// Whether to resize large images
120 | /// Allows for cancellation of the upload
121 | /// String containing the URL of the results page
122 | public static async Task Search(string imagePath, bool includeFileName, bool resizeOnUpload, CancellationToken cancelToken)
123 | {
124 | // Load the image, resizing it if necessary
125 | byte[] data = LoadImageData(imagePath, resizeOnUpload);
126 |
127 | // Prevent auto redirect (we want to open the
128 | // redirect destination directly in the browser)
129 | var handler = new HttpClientHandler();
130 | handler.AllowAutoRedirect = false;
131 |
132 | using (var client = new HttpClient(handler))
133 | {
134 | var form = new MultipartFormDataContentCompat();
135 | form.Add(new StringContent(BinaryToBase64Compat(data)), "image_content");
136 | if (includeFileName)
137 | {
138 | form.Add(new StringContent(Path.GetFileName(imagePath)), "filename");
139 | }
140 | var response = await client.PostAsync("https://images.google.com/searchbyimage/upload", form, cancelToken);
141 | if (response.StatusCode != HttpStatusCode.Redirect)
142 | {
143 | throw new IOException("Expected redirect to results page, got " + (int)response.StatusCode);
144 | }
145 | string resultUrl = response.Headers.Location.ToString();
146 | return resultUrl;
147 | }
148 | }
149 |
150 | ///
151 | /// Google Images has some oddities in the way it requires
152 | /// forms data to be uploaded. The main three that I could
153 | /// find are:
154 | ///
155 | /// 1. Content-Disposition name parameters must be quoted
156 | /// 2. Content-Type boundary parameter must NOT be quoted
157 | /// 3. Image base-64 encoding replaces `+` -> `-`, `/` -> `_`
158 | ///
159 | /// This class transparently handles the first two quirks.
160 | ///
161 | private class MultipartFormDataContentCompat : MultipartContent
162 | {
163 | public MultipartFormDataContentCompat() : base("form-data")
164 | {
165 | FixBoundaryParameter();
166 | }
167 |
168 | public MultipartFormDataContentCompat(string boundary) : base("form-data", boundary)
169 | {
170 | FixBoundaryParameter();
171 | }
172 |
173 | public override void Add(HttpContent content)
174 | {
175 | base.Add(content);
176 | AddContentDisposition(content, null, null);
177 | }
178 |
179 | public void Add(HttpContent content, string name)
180 | {
181 | base.Add(content);
182 | AddContentDisposition(content, name, null);
183 | }
184 |
185 | public void Add(HttpContent content, string name, string fileName)
186 | {
187 | base.Add(content);
188 | AddContentDisposition(content, name, fileName);
189 | }
190 |
191 | private void AddContentDisposition(HttpContent content, string name, string fileName)
192 | {
193 | var headers = content.Headers;
194 | if (headers.ContentDisposition == null)
195 | {
196 | headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
197 | {
198 | Name = QuoteString(name),
199 | FileName = QuoteString(fileName)
200 | };
201 | }
202 | }
203 |
204 | private void FixBoundaryParameter()
205 | {
206 | var boundary = Headers.ContentType.Parameters.Single(p => p.Name == "boundary");
207 | boundary.Value = boundary.Value.Trim('"');
208 | }
209 |
210 | private static string QuoteString(string str)
211 | {
212 | return '"' + str + '"';
213 | }
214 | }
215 | }
216 | }
217 |
--------------------------------------------------------------------------------