diff --git a/.gitignore b/.gitignore index 3e212e9026..6946e9478d 100644 --- a/.gitignore +++ b/.gitignore @@ -301,6 +301,8 @@ __pycache__/ # Cake - Uncomment if you are using it # tools/** # !tools/packages.config +ImageResizer/tools/** +!ImageResizer/tools/packages.config # Tabs Studio *.tss diff --git a/PowerToys.sln b/PowerToys.sln index e00270c903..34d6e63965 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -123,6 +123,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowWalker", "src\modules {74485049-C722-400F-ABE5-86AC52D929B3} = {74485049-C722-400F-ABE5-86AC52D929B3} EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "imageresizer", "imageresizer", "{6C7F47CC-2151-44A3-A546-41C70025132C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageResizerUI", "src\modules\imageresizer\ui\ImageResizerUI.csproj", "{2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerExt", "src\modules\imageresizer\dll\ImageResizerExt.vcxproj", "{0B43679E-EDFA-4DA0-AD30-F4628B308B1B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageResizerUITest", "src\modules\imageresizer\tests\ImageResizerUITest.csproj", "{E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "action_runner", "src\action_runner\action_runner.vcxproj", "{D29DDD63-E2CF-4657-9FD5-2AEDE4257E5D}" ProjectSection(ProjectDependencies) = postProject {17DA04DF-E393-4397-9CF0-84DABE11032E} = {17DA04DF-E393-4397-9CF0-84DABE11032E} @@ -222,6 +230,18 @@ Global {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Debug|x64.Build.0 = Debug|x64 {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x64.ActiveCfg = Release|x64 {0485F45C-EA7A-4BB5-804B-3E8D14699387}.Release|x64.Build.0 = Release|x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.ActiveCfg = Debug|x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Debug|x64.Build.0 = Debug|x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.ActiveCfg = Release|x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34}.Release|x64.Build.0 = Release|x64 + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.ActiveCfg = Debug|x64 + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Debug|x64.Build.0 = Debug|x64 + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.ActiveCfg = Release|x64 + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B}.Release|x64.Build.0 = Release|x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.ActiveCfg = Debug|x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Debug|x64.Build.0 = Debug|x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.ActiveCfg = Release|x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8}.Release|x64.Build.0 = Release|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.ActiveCfg = Debug|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Debug|x64.Build.0 = Debug|x64 {0B593A6C-4143-4337-860E-DB5710FB87DB}.Release|x64.ActiveCfg = Release|x64 @@ -301,6 +321,10 @@ Global {2151F984-E006-4A9F-92EF-C6DDE3DC8413} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {64A80062-4D8B-4229-8A38-DFA1D7497749} = {BEEAB7F2-FFF6-45AB-9CDB-B04CC0734B88} {0485F45C-EA7A-4BB5-804B-3E8D14699387} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} + {6C7F47CC-2151-44A3-A546-41C70025132C} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} = {6C7F47CC-2151-44A3-A546-41C70025132C} + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B} = {6C7F47CC-2151-44A3-A546-41C70025132C} + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8} = {6C7F47CC-2151-44A3-A546-41C70025132C} {0B593A6C-4143-4337-860E-DB5710FB87DB} = {1AFB6476-670D-4E80-A464-657E01DFF482} {031AC72E-FA28-4AB7-B690-6F7B9C28AA73} = {1AFB6476-670D-4E80-A464-657E01DFF482} {8DC78AF7-DC3E-4C57-A8FB-7E347DE74A03} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} diff --git a/installer/MSIX/PackagingLayout.xml b/installer/MSIX/PackagingLayout.xml index 636c95201e..5749c1e6ab 100644 --- a/installer/MSIX/PackagingLayout.xml +++ b/installer/MSIX/PackagingLayout.xml @@ -17,6 +17,13 @@ + + + + + + + @@ -41,6 +48,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/MSIX/appxmanifest.xml b/installer/MSIX/appxmanifest.xml index 3904a45ed2..7dc1e1be9e 100644 --- a/installer/MSIX/appxmanifest.xml +++ b/installer/MSIX/appxmanifest.xml @@ -39,6 +39,9 @@ + + + @@ -49,6 +52,9 @@ + + + diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index d2a91443aa..ffa4d939e3 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -22,7 +22,7 @@ Property="PREVIOUSVERSIONSINSTALLED" IncludeMinimum="yes" IncludeMaximum="no" /> - + @@ -37,6 +37,7 @@ + @@ -66,6 +67,9 @@ + + + @@ -144,7 +148,7 @@ BinaryKey="PTCustomActions" DllEntry="TelemetryLogUninstallFailCA" /> - + - + - EXISTINGPOWERRENAMEEXTPATH + EXISTINGPOWERRENAMEEXTPATH OR EXISTINGIMAGERESIZERPATH @@ -177,7 +181,25 @@ - + + + + + + + + + + + + + + + + + + + @@ -274,9 +296,87 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -397,6 +497,8 @@ + + @@ -405,4 +507,27 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/README.md b/installer/README.md index cd319ed1e9..2ff0e7f074 100644 --- a/installer/README.md +++ b/installer/README.md @@ -20,7 +20,7 @@ For the first-time installation, you'll need to generate a self-signed certifica **Note:** if you delete the folder, you will have to regenerate the key #### Elevate `Developer PowerShell for VS` permissions due to unsigned file -`msix_reinstall.ps1` is unsigned, you'll need to elevate your prompt. +`reinstall_msix.ps1` is unsigned, you'll need to elevate your prompt. 1. Open `Developer PowerShell for VS` as admin 2. Run `Set-ExecutionPolicy -executionPolicy Unrestricted` @@ -31,10 +31,10 @@ In order to install the MSIX package without using the Microsoft Store, sideload 1. Make sure you've built the `Release` configuration of `powertoys.sln` 2. Open `Developer PowerShell for VS` 3. Navigate to your repo's `installer\MSIX` -4. Run `.\msix_reinstall.ps1` from the devenv powershell +4. Run `.\reinstall_msix.ps1` from the devenv powershell -### What msix_reinstall.ps1 does -`msix_reinstall.ps1` removes the current PowerToys installation, restarts explorer.exe (to update PowerRename shell extension), builds `PowerToys-x64.msix` package, signs it with a PowerToys_TemporaryKey.pfx, and finally installs it. +### What reinstall_msix.ps1 does +`reinstall_msix.ps1` removes the current PowerToys installation, restarts explorer.exe (to update PowerRename and ImageResizer shell extension), builds `PowerToys-x64.msix` package, signs it with a PowerToys_TemporaryKey.pfx, and finally installs it. ## Cleanup - Removing all .msi/.msix PowerToys installations ```ps diff --git a/src/modules/imageresizer/README.md b/src/modules/imageresizer/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/modules/imageresizer/codeAnalysis/GlobalSuppressions.cs b/src/modules/imageresizer/codeAnalysis/GlobalSuppressions.cs new file mode 100644 index 0000000000..94ba50d7d7 --- /dev/null +++ b/src/modules/imageresizer/codeAnalysis/GlobalSuppressions.cs @@ -0,0 +1,47 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1009:ClosingParenthesisMustBeSpacedCorrectly", Justification = "All current violations are due to Tuple shorthand and so valid.")] + +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:PrefixLocalCallsWithThis", Justification = "We follow the C# Core Coding Style which avoids using `this` unless absolutely necessary.")] + +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1200:UsingDirectivesMustBePlacedWithinNamespace", Justification = "We follow the C# Core Coding Style which puts using statements outside the namespace.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "It is not a priority and have hight impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "It is not a priority and have hight impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1203:ConstantsMustAppearBeforeFields", Justification = "It is not a priority and have hight impact in code changes.")] +[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "It is not a priority and have hight impact in code changes.")] + +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:FieldNamesMustNotBeginWithUnderscore", Justification = "We follow the C# Core Coding Style which uses underscores as prefixes rather than using `this.`.")] + +[assembly: SuppressMessage("StyleCop.CSharp.SpecialRules", "SA0001:XmlCommentAnalysisDisabled", Justification = "Not enabled as we don't want or need XML documentation.")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:DocumentationTextMustEndWithAPeriod", Justification = "Not enabled as we don't want or need XML documentation.")] + +[assembly: SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Scope = "member", Target = "Microsoft.Templates.Core.Locations.TemplatesSynchronization.#SyncStatusChanged", Justification = "Using an Action does not allow the required notation")] + +// Non general supressions +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "The WebBrowser is loading source code to be shown to the user. No localization required.", MessageId = "System.Windows.Controls.WebBrowser.NavigateToString(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.CodeViewer.#UpdateCodeView(System.Func`2,System.String,System.String,System.Boolean)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "This is part of the markdown processing", MessageId = "System.Windows.Documents.Run.#ctor(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Controls.Markdown.#ImageInlineEvaluator(System.Text.RegularExpressions.Match)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer especification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.ITemplateInfoExtensions.#GetQueryableProperties(Microsoft.TemplateEngine.Abstractions.ITemplateInfo)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We need to have the names of these keys in lowercase to be able to compare with the keys becoming form the template json. ContainsKey does not allow StringComparer especification to IgnoreCase", Scope = "member", Target = "Microsoft.Templates.Core.Composition.CompositionQuery.#Match(System.Collections.Generic.IEnumerable`1,Microsoft.Templates.Core.Composition.QueryablePropertyDictionary)")] +[assembly: SuppressMessage("Usage", "VSTHRD103:Call async methods when in an async method", Justification = "Resource DictionaryWriter does not implement flush async", Scope = "member", Target = "~M:Microsoft.Templates.Core.PostActions.Catalog.Merge.MergeResourceDictionaryPostAction.ExecuteInternalAsync~System.Threading.Tasks.Task")] + +// Threading supressions +[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.Controls.Notification.OnClose")] +[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel.OnDelete")] +[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.WizardNavigation.GoBack")] +[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.WizardNavigation.GoForward")] +[assembly: SuppressMessage("Microsoft.VisualStudio.Threading.Analyzers", "VSTHRD100:Avoid async void methods", Justification = "Event handlers needs async void", Scope = "member", Target = "~M:Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel.OnDelete(Microsoft.Templates.UI.ViewModels.Common.SavedTemplateViewModel)")] + +// Localization suppressions +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#CreateJunction(System.String,System.String,System.Boolean)", Justification = "Only used for local generation")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#DeleteJunction(System.String)", Justification = "Only used for local generation")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#InternalGetTarget(Microsoft.Win32.SafeHandles.SafeFileHandle)", Justification = "Only used for local generation")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.ThrowLastWin32Error(System.String)", Scope = "member", Target = "Microsoft.Templates.Core.Locations.JunctionNativeMethods.#OpenReparsePoint(System.String,Microsoft.Templates.Core.Locations.JunctionNativeMethods+EFileAccess)", Justification = "Only used for local generation")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Documents.InlineCollection.Add(System.String)", Scope = "member", Target = "Microsoft.Templates.UI.Extensions.TextBlockExtensions.#OnSequentialFlowStepChanged(System.Windows.DependencyObject,System.Windows.DependencyPropertyChangedEventArgs)", Justification = "No text here")] diff --git a/src/modules/imageresizer/dll/ContextMenuHandler.cpp b/src/modules/imageresizer/dll/ContextMenuHandler.cpp new file mode 100644 index 0000000000..da77e123f9 --- /dev/null +++ b/src/modules/imageresizer/dll/ContextMenuHandler.cpp @@ -0,0 +1,407 @@ +// ContextMenuHandler.cpp : Implementation of CContextMenuHandler + +#include "stdafx.h" +#include "ContextMenuHandler.h" +#include "HDropIterator.h" +#include "Settings.h" +#include "common/icon_helpers.h" +#include "trace.h" + +extern HINSTANCE g_hInst_imageResizer; + +CContextMenuHandler::CContextMenuHandler() +{ + m_pidlFolder = NULL; + m_pdtobj = NULL; + app_name = GET_RESOURCE_STRING(IDS_RESIZE_PICTURES); +} + +CContextMenuHandler::~CContextMenuHandler() +{ + Uninitialize(); +} + +void CContextMenuHandler::Uninitialize() +{ + CoTaskMemFree((LPVOID)m_pidlFolder); + m_pidlFolder = NULL; + + if (m_pdtobj) + { + m_pdtobj->Release(); + m_pdtobj = NULL; + } +} + +HRESULT CContextMenuHandler::Initialize(_In_opt_ PCIDLIST_ABSOLUTE pidlFolder, _In_opt_ IDataObject* pdtobj, _In_opt_ HKEY hkeyProgID) +{ + Uninitialize(); + + if (!CSettings::GetEnabled()) + { + return E_FAIL; + } + + if (pidlFolder) + { + m_pidlFolder = ILClone(pidlFolder); + } + + if (pdtobj) + { + m_pdtobj = pdtobj; + m_pdtobj->AddRef(); + } + + return S_OK; +} + +HRESULT CContextMenuHandler::QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) +{ + if (uFlags & CMF_DEFAULTONLY) + { + return S_OK; + } + if (!CSettings::GetEnabled()) + { + return E_FAIL; + } + // NB: We just check the first item. We could iterate through more if the first one doesn't meet the criteria + HDropIterator i(m_pdtobj); + i.First(); +// Suppressing C26812 warning as the issue is in the shtypes.h library +#pragma warning(suppress : 26812) + PERCEIVED type; + PERCEIVEDFLAG flag; + LPTSTR pszPath = i.CurrentItem(); + LPTSTR pszExt = PathFindExtension(pszPath); + + // TODO: Instead, detect whether there's a WIC codec installed that can handle this file + AssocGetPerceivedType(pszExt, &type, &flag, NULL); + + free(pszPath); + bool dragDropFlag = false; + // If selected file is an image... + if (type == PERCEIVED_TYPE_IMAGE) + { + HRESULT hr = E_UNEXPECTED; + wchar_t strResizePictures[64] = { 0 }; + // If handling drag-and-drop... + if (m_pidlFolder) + { + // Suppressing C6031 warning since return value is not required. +#pragma warning(suppress : 6031) + // Load 'Resize pictures here' string + LoadString(g_hInst_imageResizer, IDS_RESIZE_PICTURES_HERE, strResizePictures, ARRAYSIZE(strResizePictures)); + dragDropFlag = true; + } + else + { + // Suppressing C6031 warning since return value is not required. +#pragma warning(suppress : 6031) + // Load 'Resize pictures' string + LoadString(g_hInst_imageResizer, IDS_RESIZE_PICTURES, strResizePictures, ARRAYSIZE(strResizePictures)); + } + + MENUITEMINFO mii; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_STRING | MIIM_FTYPE | MIIM_ID | MIIM_STATE; + mii.wID = idCmdFirst + ID_RESIZE_PICTURES; + mii.fType = MFT_STRING; + mii.dwTypeData = (PWSTR)strResizePictures; + mii.fState = MFS_ENABLED; + HICON hIcon = (HICON)LoadImage(g_hInst_imageResizer, MAKEINTRESOURCE(IDI_RESIZE_PICTURES), IMAGE_ICON, 16, 16, 0); + if (hIcon) + { + mii.fMask |= MIIM_BITMAP; + if (m_hbmpIcon == NULL) + { + m_hbmpIcon = CreateBitmapFromIcon(hIcon); + } + mii.hbmpItem = m_hbmpIcon; + DestroyIcon(hIcon); + } + + if (dragDropFlag) + { + // Insert the menu entry at indexMenu+1 since the first entry should be "Copy here" + indexMenu++; + } + else + { + // indexMenu gets the first possible menu item index based on the location of the shellex registry key. + // If the registry entry is under SystemFileAssociations for the image formats, ShellImagePreview (in Windows by default) will be at indexMenu=0 + // Shell ImagePreview consists of 4 menu items, a separator, Rotate right, Rotate left, and another separator + // Check if the entry at indexMenu is a separator, insert the new menu item at indexMenu+1 if true + MENUITEMINFO miiExisting; + miiExisting.dwTypeData = NULL; + miiExisting.fMask = MIIM_TYPE; + miiExisting.cbSize = sizeof(MENUITEMINFO); + GetMenuItemInfo(hmenu, indexMenu, TRUE, &miiExisting); + if (miiExisting.fType == MFT_SEPARATOR) + { + indexMenu++; + } + } + + if (!InsertMenuItem(hmenu, indexMenu, TRUE, &mii)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + else + { + hr = MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1); + } + return hr; + } + + return S_OK; +} + +HRESULT CContextMenuHandler::GetCommandString(UINT_PTR idCmd, UINT uType, _In_ UINT* pReserved, LPSTR pszName, UINT cchMax) +{ + if (idCmd == ID_RESIZE_PICTURES) + { + if (uType == GCS_VERBW) + { + wcscpy_s((LPWSTR)pszName, cchMax, RESIZE_PICTURES_VERBW); + } + } + else + { + return E_INVALIDARG; + } + + return S_OK; +} + +HRESULT CContextMenuHandler::InvokeCommand(_In_ CMINVOKECOMMANDINFO* pici) +{ + BOOL fUnicode = FALSE; + Trace::Invoked(); + HRESULT hr = E_FAIL; + if (pici->cbSize == sizeof(CMINVOKECOMMANDINFOEX) && pici->fMask & CMIC_MASK_UNICODE) + { + fUnicode = TRUE; + } + + if (!fUnicode && HIWORD(pici->lpVerb)) + { + } + else if (fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW)) + { + if (wcscmp(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW, RESIZE_PICTURES_VERBW) == 0) + { + hr = ResizePictures(pici, nullptr); + } + } + else if (LOWORD(pici->lpVerb) == ID_RESIZE_PICTURES) + { + hr = ResizePictures(pici, nullptr); + } + Trace::InvokedRet(hr); + return hr; +} + +// This function is used for both MSI and MSIX. If pici is null and psiItemArray is not null then this is called by Invoke(MSIX). If pici is not null and psiItemArray is null then this is called by InvokeCommand(MSI). +HRESULT CContextMenuHandler::ResizePictures(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray) +{ + // Set the application path based on the location of the dll + std::wstring path = get_module_folderpath(g_hInst_imageResizer); + path = path + L"\\ImageResizer.exe"; + LPTSTR lpApplicationName = (LPTSTR)path.c_str(); + // Create an anonymous pipe to stream filenames + SECURITY_ATTRIBUTES sa; + HANDLE hReadPipe; + HANDLE hWritePipe; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + HRESULT hr = E_FAIL; + if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) + { + Trace::InvokedRet(hr); + return hr; + } + if (!SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT, 0)) + { + Trace::InvokedRet(hr); + return hr; + } + CAtlFile writePipe(hWritePipe); + + CString commandLine; + commandLine.Format(_T("\"%s\""), lpApplicationName); + + // Set the output directory + if (m_pidlFolder) + { + TCHAR szFolder[MAX_PATH]; + SHGetPathFromIDList(m_pidlFolder, szFolder); + + commandLine.AppendFormat(_T(" /d \"%s\""), szFolder); + } + + int nSize = commandLine.GetLength() + 1; + LPTSTR lpszCommandLine = new TCHAR[nSize]; + _tcscpy_s(lpszCommandLine, nSize, commandLine); + + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + startupInfo.hStdInput = hReadPipe; + startupInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + if (pici) + { + startupInfo.wShowWindow = pici->nShow; + } + else + { + startupInfo.wShowWindow = SW_SHOWNORMAL; + } + + PROCESS_INFORMATION processInformation; + + // Start the resizer + CreateProcess( + NULL, + lpszCommandLine, + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &startupInfo, + &processInformation); + delete[] lpszCommandLine; + if (!CloseHandle(processInformation.hProcess)) + { + Trace::InvokedRet(hr); + return hr; + } + if (!CloseHandle(processInformation.hThread)) + { + Trace::InvokedRet(hr); + return hr; + } + + // psiItemArray is NULL if called from InvokeCommand. This part is used for the MSI installer. It is not NULL if it is called from Invoke (MSIX). + if (!psiItemArray) + { + // Stream the input files + HDropIterator i(m_pdtobj); + for (i.First(); !i.IsDone(); i.Next()) + { + CString fileName(i.CurrentItem()); + fileName.Append(_T("\r\n")); + + writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR)); + } + } + else + { + //m_pdtobj will be NULL when invoked from the MSIX build as Initialize is never called (IShellExtInit functions aren't called in case of MSIX). + DWORD fileCount = 0; + // Gets the list of files currently selected using the IShellItemArray + psiItemArray->GetCount(&fileCount); + // Iterate over the list of files + for (DWORD i = 0; i < fileCount; i++) + { + IShellItem* shellItem; + psiItemArray->GetItemAt(i, &shellItem); + LPWSTR itemName; + // Retrieves the entire file system path of the file from its shell item + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &itemName); + CString fileName(itemName); + fileName.Append(_T("\r\n")); + // Write the file path into the input stream for image resizer + writePipe.Write(fileName, fileName.GetLength() * sizeof(TCHAR)); + } + } + + writePipe.Close(); + hr = S_OK; + Trace::InvokedRet(hr); + return hr; +} + +HRESULT __stdcall CContextMenuHandler::GetTitle(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszName) +{ + return SHStrDup(app_name.c_str(), ppszName); +} + +HRESULT __stdcall CContextMenuHandler::GetIcon(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszIcon) +{ + // Since ImageResizer is registered as a COM SurrogateServer the current module filename would be dllhost.exe. To get the icon we need the path of ImageResizerExt.dll, which can be obtained by passing the HINSTANCE of the dll + std::wstring iconResourcePath = get_module_filename(g_hInst_imageResizer); + iconResourcePath += L",-"; + iconResourcePath += std::to_wstring(IDI_RESIZE_PICTURES); + return SHStrDup(iconResourcePath.c_str(), ppszIcon); +} + +HRESULT __stdcall CContextMenuHandler::GetToolTip(IShellItemArray* /*psiItemArray*/, LPWSTR* ppszInfotip) +{ + *ppszInfotip = nullptr; + return E_NOTIMPL; +} + +HRESULT __stdcall CContextMenuHandler::GetCanonicalName(GUID* pguidCommandName) +{ + *pguidCommandName = __uuidof(this); + return S_OK; +} + +HRESULT __stdcall CContextMenuHandler::GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) +{ + if (!CSettings::GetEnabled()) + { + *pCmdState = ECS_HIDDEN; + return S_OK; + } + // Hide if the file is not an image + *pCmdState = ECS_HIDDEN; + // Suppressing C26812 warning as the issue is in the shtypes.h library +#pragma warning(suppress : 26812) + PERCEIVED type; + PERCEIVEDFLAG flag; + IShellItem* shellItem; + //Check extension of first item in the list (the item which is right-clicked on) + psiItemArray->GetItemAt(0, &shellItem); + LPTSTR pszPath; + // Retrieves the entire file system path of the file from its shell item + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszPath); + LPTSTR pszExt = PathFindExtension(pszPath); + + // TODO: Instead, detect whether there's a WIC codec installed that can handle this file + AssocGetPerceivedType(pszExt, &type, &flag, NULL); + + free(pszPath); + // If selected file is an image... + if (type == PERCEIVED_TYPE_IMAGE) + { + *pCmdState = ECS_ENABLED; + } + return S_OK; +} + +HRESULT __stdcall CContextMenuHandler::GetFlags(EXPCMDFLAGS* pFlags) +{ + *pFlags = ECF_DEFAULT; + return S_OK; +} + +HRESULT __stdcall CContextMenuHandler::EnumSubCommands(IEnumExplorerCommand** ppEnum) +{ + *ppEnum = nullptr; + return E_NOTIMPL; +} + +// psiItemArray contains the list of files that have been selected when the context menu entry is invoked +HRESULT __stdcall CContextMenuHandler::Invoke(IShellItemArray* psiItemArray, IBindCtx* /*pbc*/) +{ + Trace::Invoked(); + HRESULT hr = ResizePictures(nullptr, psiItemArray); + Trace::InvokedRet(hr); + return hr; +} diff --git a/src/modules/imageresizer/dll/ContextMenuHandler.h b/src/modules/imageresizer/dll/ContextMenuHandler.h new file mode 100644 index 0000000000..975ef17497 --- /dev/null +++ b/src/modules/imageresizer/dll/ContextMenuHandler.h @@ -0,0 +1,57 @@ +#pragma once + +#define ID_RESIZE_PICTURES 0 +#define RESIZE_PICTURES_VERBW L"resize" +#include "stdafx.h" +#include "resource.h" +#include "ImageResizerExt_i.h" + +#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA) +#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms." +#endif + +using namespace ATL; + +class ATL_NO_VTABLE __declspec(uuid("51B4D7E5-7568-4234-B4BB-47FB3C016A69")) CContextMenuHandler : + public CComObjectRootEx, + public CComCoClass, + public IShellExtInit, + public IContextMenu, + public IExplorerCommand +{ + BEGIN_COM_MAP(CContextMenuHandler) + COM_INTERFACE_ENTRY(IShellExtInit) + COM_INTERFACE_ENTRY(IContextMenu) + COM_INTERFACE_ENTRY(IExplorerCommand) + END_COM_MAP() + DECLARE_REGISTRY_RESOURCEID(IDR_CONTEXTMENUHANDLER) + DECLARE_NOT_AGGREGATABLE(CContextMenuHandler) + +public: + CContextMenuHandler(); + ~CContextMenuHandler(); + HRESULT STDMETHODCALLTYPE Initialize(_In_opt_ PCIDLIST_ABSOLUTE pidlFolder, _In_opt_ IDataObject* pdtobj, _In_opt_ HKEY hkeyProgID); + HRESULT STDMETHODCALLTYPE QueryContextMenu(_In_ HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags); + HRESULT STDMETHODCALLTYPE GetCommandString(UINT_PTR idCmd, UINT uType, _In_ UINT* pReserved, LPSTR pszName, UINT cchMax); + HRESULT STDMETHODCALLTYPE InvokeCommand(_In_ CMINVOKECOMMANDINFO* pici); + + // Inherited via IExplorerCommand + virtual HRESULT __stdcall GetTitle(IShellItemArray* psiItemArray, LPWSTR* ppszName) override; + virtual HRESULT __stdcall GetIcon(IShellItemArray* psiItemArray, LPWSTR* ppszIcon) override; + virtual HRESULT __stdcall GetToolTip(IShellItemArray* psiItemArray, LPWSTR* ppszInfotip) override; + virtual HRESULT __stdcall GetCanonicalName(GUID* pguidCommandName) override; + virtual HRESULT __stdcall GetState(IShellItemArray* psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE* pCmdState) override; + virtual HRESULT __stdcall Invoke(IShellItemArray* psiItemArray, IBindCtx* pbc) override; + virtual HRESULT __stdcall GetFlags(EXPCMDFLAGS* pFlags) override; + virtual HRESULT __stdcall EnumSubCommands(IEnumExplorerCommand** ppEnum) override; + +private: + void Uninitialize(); + HRESULT ResizePictures(CMINVOKECOMMANDINFO* pici, IShellItemArray* psiItemArray); + PCIDLIST_ABSOLUTE m_pidlFolder; + IDataObject* m_pdtobj; + HBITMAP m_hbmpIcon = nullptr; + std::wstring app_name; +}; + +OBJECT_ENTRY_AUTO(__uuidof(ContextMenuHandler), CContextMenuHandler) \ No newline at end of file diff --git a/src/modules/imageresizer/dll/ContextMenuHandler.rgs b/src/modules/imageresizer/dll/ContextMenuHandler.rgs new file mode 100644 index 0000000000..e7d37400e1 --- /dev/null +++ b/src/modules/imageresizer/dll/ContextMenuHandler.rgs @@ -0,0 +1,3 @@ +HKCR +{ +} diff --git a/src/modules/imageresizer/dll/HDropIterator.cpp b/src/modules/imageresizer/dll/HDropIterator.cpp new file mode 100644 index 0000000000..dad26d2877 --- /dev/null +++ b/src/modules/imageresizer/dll/HDropIterator.cpp @@ -0,0 +1,49 @@ +#include "StdAfx.h" +#include "HDropIterator.h" + +HDropIterator::HDropIterator(IDataObject* pdtobj) +{ + _current = 0; + + FORMATETC formatetc = { + CF_HDROP, + NULL, + DVASPECT_CONTENT, + -1, + TYMED_HGLOBAL + }; + + pdtobj->GetData(&formatetc, &m_medium); + + _listCount = DragQueryFile((HDROP)m_medium.hGlobal, 0xFFFFFFFF, NULL, 0); +} + +HDropIterator::~HDropIterator() +{ + ReleaseStgMedium(&m_medium); +} + +void HDropIterator::First() +{ + _current = 0; +} + +void HDropIterator::Next() +{ + _current++; +} + +bool HDropIterator::IsDone() const +{ + return _current >= _listCount; +} + +LPTSTR HDropIterator::CurrentItem() const +{ + UINT cch = DragQueryFile((HDROP)m_medium.hGlobal, _current, NULL, 0) + 1; + LPTSTR pszPath = (LPTSTR)malloc(sizeof(TCHAR) * cch); + + DragQueryFile((HDROP)m_medium.hGlobal, _current, pszPath, cch); + + return pszPath; +} diff --git a/src/modules/imageresizer/dll/HDropIterator.h b/src/modules/imageresizer/dll/HDropIterator.h new file mode 100644 index 0000000000..8f1d26151d --- /dev/null +++ b/src/modules/imageresizer/dll/HDropIterator.h @@ -0,0 +1,17 @@ +#pragma once + +class HDropIterator +{ +public: + HDropIterator(IDataObject *pDataObject); + ~HDropIterator(); + void First(); + void Next(); + bool IsDone() const; + LPTSTR CurrentItem() const; + +private: + UINT _listCount; + STGMEDIUM m_medium; + UINT _current; +}; diff --git a/src/modules/imageresizer/dll/ImageResizerExt.cpp b/src/modules/imageresizer/dll/ImageResizerExt.cpp new file mode 100644 index 0000000000..72ddebe8ca --- /dev/null +++ b/src/modules/imageresizer/dll/ImageResizerExt.cpp @@ -0,0 +1,54 @@ +#include "stdafx.h" +#include "resource.h" +#include "ImageResizerExt_i.h" +#include "dllmain.h" + +__control_entrypoint(DllExport) STDAPI DllCanUnloadNow() +{ + return _AtlModule.DllCanUnloadNow(); +} + +_Check_return_ STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID* ppv) +{ + return _AtlModule.DllGetClassObject(rclsid, riid, ppv); +} + +STDAPI DllRegisterServer() +{ + return _AtlModule.DllRegisterServer(); +} + +STDAPI DllUnregisterServer() +{ + return _AtlModule.DllUnregisterServer(); +} + +STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine) +{ + HRESULT hr = E_FAIL; + static const wchar_t szUserSwitch[] = L"user"; + + if (pszCmdLine != NULL) + { + if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0) + { + ATL::AtlSetPerUserRegistration(true); + } + } + + if (bInstall) + { + hr = DllRegisterServer(); + + if (FAILED(hr)) + { + DllUnregisterServer(); + } + } + else + { + hr = DllUnregisterServer(); + } + + return hr; +} diff --git a/src/modules/imageresizer/dll/ImageResizerExt.def b/src/modules/imageresizer/dll/ImageResizerExt.def new file mode 100644 index 0000000000..cd58c84c28 --- /dev/null +++ b/src/modules/imageresizer/dll/ImageResizerExt.def @@ -0,0 +1,8 @@ +LIBRARY + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + DllInstall PRIVATE diff --git a/src/modules/imageresizer/dll/ImageResizerExt.idl b/src/modules/imageresizer/dll/ImageResizerExt.idl new file mode 100644 index 0000000000..9e349cda32 --- /dev/null +++ b/src/modules/imageresizer/dll/ImageResizerExt.idl @@ -0,0 +1,15 @@ +import "shobjidl.idl"; + +[ + uuid(09082E28-A5CA-47A7-8571-A2236C411E91), + version(3.1) +] +library ImageResizerExtLib +{ + [uuid(51B4D7E5-7568-4234-B4BB-47FB3C016A69)] + coclass ContextMenuHandler + { + [default] interface IShellExtInit; + interface IContextMenu; + }; +}; diff --git a/src/modules/imageresizer/dll/ImageResizerExt.rc b/src/modules/imageresizer/dll/ImageResizerExt.rc new file mode 100644 index 0000000000..10edb3b47a Binary files /dev/null and b/src/modules/imageresizer/dll/ImageResizerExt.rc differ diff --git a/src/modules/imageresizer/dll/ImageResizerExt.rgs b/src/modules/imageresizer/dll/ImageResizerExt.rgs new file mode 100644 index 0000000000..e7d37400e1 --- /dev/null +++ b/src/modules/imageresizer/dll/ImageResizerExt.rgs @@ -0,0 +1,3 @@ +HKCR +{ +} diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj new file mode 100644 index 0000000000..9ebaa7c2f5 --- /dev/null +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj @@ -0,0 +1,311 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {0B43679E-EDFA-4DA0-AD30-F4628B308B1B} + 10.0 + AtlProj + + + + DynamicLibrary + true + Static + v142 + Unicode + + + DynamicLibrary + false + Static + v142 + Unicode + + + DynamicLibrary + true + Static + v142 + Unicode + + + DynamicLibrary + false + Static + v142 + Unicode + + + + + + + + + + + + + + + + + + + true + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\ + + + true + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\ + + + true + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\ + + + true + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\ + + + + Use + Level3 + Disabled + WIN32;_WINDOWS;_DEBUG;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + stdcpplatest + + + false + Win32 + _DEBUG;%(PreprocessorDefinitions) + ImageResizerExt_i.h + ImageResizerExt_i.c + ImageResizerExt_p.c + true + $(IntDir)ImageResizerExt.tlb + + true + + + 0x0409 + $(IntDir);%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + + + Windows + .\ImageResizerExt.def + true + true + true + + + + + Use + Level3 + Disabled + _WINDOWS;_DEBUG;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) + stdcpplatest + + + false + _DEBUG;%(PreprocessorDefinitions) + ImageResizerExt_i.h + ImageResizerExt_i.c + ImageResizerExt_p.c + true + $(IntDir)ImageResizerExt.tlb + + true + + + 0x0409 + $(IntDir);%(AdditionalIncludeDirectories) + _DEBUG;%(PreprocessorDefinitions) + + + Windows + .\ImageResizerExt.def + true + true + true + + + + + Use + Level3 + MaxSpeed + WIN32;_WINDOWS;NDEBUG;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + stdcpplatest + + + false + Win32 + NDEBUG;%(PreprocessorDefinitions) + ImageResizerExt_i.h + ImageResizerExt_i.c + ImageResizerExt_p.c + true + $(IntDir)ImageResizerExt.tlb + + true + + + 0x0409 + $(IntDir);%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + + + Windows + .\ImageResizerExt.def + true + true + true + true + true + + + + + Use + Level3 + MaxSpeed + _WINDOWS;NDEBUG;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) + stdcpplatest + + + false + NDEBUG;%(PreprocessorDefinitions) + ImageResizerExt_i.h + ImageResizerExt_i.c + ImageResizerExt_p.c + true + $(IntDir)ImageResizerExt.tlb + + true + + + 0x0409 + $(IntDir);%(AdditionalIncludeDirectories) + NDEBUG;%(PreprocessorDefinitions) + + + Windows + .\ImageResizerExt.def + true + true + true + true + true + + + + + + + false + + + false + + + false + + + false + + + + + + false + + + false + + + false + + + false + + + + + + Create + Create + Create + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + {74485049-c722-400f-abe5-86ac52d929b3} + + + + + + + + + \ No newline at end of file diff --git a/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters new file mode 100644 index 0000000000..b88c6f562e --- /dev/null +++ b/src/modules/imageresizer/dll/ImageResizerExt.vcxproj.filters @@ -0,0 +1,102 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {87bc9b81-f7fa-45d9-87cc-c99e55473868} + False + + + + + Source Files + + + Source Files + + + Source Files + + + Generated Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Header Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Generated Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + Source Files + + + Resource Files + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/imageresizer/dll/Settings.cpp b/src/modules/imageresizer/dll/Settings.cpp new file mode 100644 index 0000000000..f8b446c43d --- /dev/null +++ b/src/modules/imageresizer/dll/Settings.cpp @@ -0,0 +1,66 @@ +#include "stdafx.h" +#include +#include "Settings.h" + +const wchar_t c_rootRegPath[] = L"Software\\Microsoft\\ImageResizer"; +const wchar_t c_enabled[] = L"Enabled"; +const bool c_enabledDefault = true; + +bool CSettings::GetEnabled() +{ + return GetRegBoolValue(c_enabled, c_enabledDefault); +} + +bool CSettings::SetEnabled(_In_ bool enabled) +{ + return SetRegBoolValue(c_enabled, enabled); +} + +bool CSettings::SetRegBoolValue(_In_ PCWSTR valueName, _In_ bool value) +{ + DWORD dwValue = value ? 1 : 0; + return SetRegDWORDValue(valueName, dwValue); +} + +bool CSettings::GetRegBoolValue(_In_ PCWSTR valueName, _In_ bool defaultValue) +{ + DWORD value = GetRegDWORDValue(valueName, (defaultValue == 0) ? false : true); + return (value == 0) ? false : true; +} + +bool CSettings::SetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD value) +{ + return (SUCCEEDED(HRESULT_FROM_WIN32(SHSetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, REG_DWORD, &value, sizeof(value))))); +} + +DWORD CSettings::GetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD defaultValue) +{ + DWORD retVal = defaultValue; + DWORD type = REG_DWORD; + DWORD dwEnabled = 0; + DWORD cb = sizeof(dwEnabled); + if (SHGetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, &type, &dwEnabled, &cb) == ERROR_SUCCESS) + { + retVal = dwEnabled; + } + + return retVal; +} + +bool CSettings::SetRegStringValue(_In_ PCWSTR valueName, _In_ PCWSTR value) +{ + ULONG cb = (DWORD)((wcslen(value) + 1) * sizeof(*value)); + return (SUCCEEDED(HRESULT_FROM_WIN32(SHSetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, REG_SZ, (const BYTE*)value, cb)))); +} + +bool CSettings::GetRegStringValue(_In_ PCWSTR valueName, __out_ecount(cchBuf) PWSTR value, DWORD cchBuf) +{ + if (cchBuf > 0) + { + value[0] = L'\0'; + } + + DWORD type = REG_SZ; + ULONG cb = cchBuf * sizeof(*value); + return (SUCCEEDED(HRESULT_FROM_WIN32(SHGetValue(HKEY_CURRENT_USER, c_rootRegPath, valueName, &type, value, &cb) == ERROR_SUCCESS))); +} \ No newline at end of file diff --git a/src/modules/imageresizer/dll/Settings.h b/src/modules/imageresizer/dll/Settings.h new file mode 100644 index 0000000000..aa00f9b30d --- /dev/null +++ b/src/modules/imageresizer/dll/Settings.h @@ -0,0 +1,16 @@ +#pragma once + +class CSettings +{ +public: + static bool GetEnabled(); + static bool SetEnabled(_In_ bool enabled); + +private: + static bool GetRegBoolValue(_In_ PCWSTR valueName, _In_ bool defaultValue); + static bool SetRegBoolValue(_In_ PCWSTR valueName, _In_ bool value); + static bool SetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD value); + static DWORD GetRegDWORDValue(_In_ PCWSTR valueName, _In_ DWORD defaultValue); + static bool SetRegStringValue(_In_ PCWSTR valueName, _In_ PCWSTR value); + static bool GetRegStringValue(_In_ PCWSTR valueName, __out_ecount(cchBuf) PWSTR value, DWORD cchBuf); +}; \ No newline at end of file diff --git a/src/modules/imageresizer/dll/dllmain.cpp b/src/modules/imageresizer/dll/dllmain.cpp new file mode 100644 index 0000000000..3205f32f64 --- /dev/null +++ b/src/modules/imageresizer/dll/dllmain.cpp @@ -0,0 +1,117 @@ +#include "stdafx.h" +#include "resource.h" +#include "ImageResizerExt_i.h" +#include "dllmain.h" +#include +#include +#include "Settings.h" +#include "trace.h" +#include + +CImageResizerExtModule _AtlModule; +HINSTANCE g_hInst_imageResizer = 0; + +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + g_hInst_imageResizer = hInstance; + Trace::RegisterProvider(); + break; + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return _AtlModule.DllMain(dwReason, lpReserved); +} + +class ImageResizerModule : public PowertoyModuleIface +{ +private: + // Enabled by default + bool m_enabled = true; + std::wstring app_name; + +public: + // Constructor + ImageResizerModule() + { + app_name = GET_RESOURCE_STRING(IDS_IMAGERESIZER); + }; + + // Destroy the powertoy and free memory + virtual void destroy() override + { + delete this; + } + + // Return the display name of the powertoy, this will be cached by the runner + virtual const wchar_t* get_name() override + { + return app_name.c_str(); + } + + // Return array of the names of all events that this powertoy listens for, with + // nullptr as the last element of the array. Nullptr can also be retured for empty + // list. + virtual const wchar_t** get_events() override + { + static const wchar_t* events[] = { nullptr }; + return events; + } + + // Return JSON with the configuration options. + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + + // Create a Settings object. + PowerToysSettings::Settings settings(hinstance, get_name()); + settings.set_description(GET_RESOURCE_STRING(IDS_SETTINGS_DESCRIPTION)); + return settings.serialize_to_buffer(buffer, buffer_size); + } + + // Signal from the Settings editor to call a custom action. + // This can be used to spawn more complex editors. + virtual void call_custom_action(const wchar_t* action) override {} + + // Called by the runner to pass the updated settings values as a serialized JSON. + virtual void set_config(const wchar_t* config) override {} + + // Enable the powertoy + virtual void enable() + { + m_enabled = true; + CSettings::SetEnabled(m_enabled); + Trace::EnableImageResizer(m_enabled); + } + + // Disable the powertoy + virtual void disable() + { + m_enabled = false; + CSettings::SetEnabled(m_enabled); + Trace::EnableImageResizer(m_enabled); + } + + // Returns if the powertoys is enabled + virtual bool is_enabled() override + { + return m_enabled; + } + + // Handle incoming event, data is event-specific + virtual intptr_t signal_event(const wchar_t* name, intptr_t data) override + { + return 0; + } + + virtual void register_system_menu_helper(PowertoySystemMenuIface* helper) override {} + virtual void signal_system_menu_action(const wchar_t* name) override {} +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new ImageResizerModule(); +} \ No newline at end of file diff --git a/src/modules/imageresizer/dll/dllmain.h b/src/modules/imageresizer/dll/dllmain.h new file mode 100644 index 0000000000..375bde005f --- /dev/null +++ b/src/modules/imageresizer/dll/dllmain.h @@ -0,0 +1,8 @@ +class CImageResizerExtModule : public ATL::CAtlDllModuleT +{ +public: + DECLARE_LIBID(LIBID_ImageResizerExtLib) + DECLARE_REGISTRY_APPID_RESOURCEID(IDR_IMAGERESIZEREXT, "{0C866E7B-65CB-4E7D-B1DD-D014F000E8D8}") +}; + +extern class CImageResizerExtModule _AtlModule; diff --git a/src/modules/imageresizer/dll/resource.h b/src/modules/imageresizer/dll/resource.h new file mode 100644 index 0000000000..07b7247fa8 --- /dev/null +++ b/src/modules/imageresizer/dll/resource.h @@ -0,0 +1,22 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ImageResizerExt.rc +// +#define IDS_RESIZE_PICTURES 100 +#define IDS_RESIZE_PICTURES_HERE 101 +#define IDR_IMAGERESIZEREXT 102 +#define IDR_CONTEXTMENUHANDLER 104 +#define IDI_RESIZE_PICTURES 105 +#define IDS_IMAGERESIZER 106 +#define IDS_SETTINGS_DESCRIPTION 107 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 201 +#define _APS_NEXT_COMMAND_VALUE 32768 +#define _APS_NEXT_CONTROL_VALUE 201 +#define _APS_NEXT_SYMED_VALUE 103 +#endif +#endif diff --git a/src/modules/imageresizer/dll/stdafx.cpp b/src/modules/imageresizer/dll/stdafx.cpp new file mode 100644 index 0000000000..a8b5e27d40 --- /dev/null +++ b/src/modules/imageresizer/dll/stdafx.cpp @@ -0,0 +1,2 @@ +#include "stdafx.h" +#pragma comment(lib, "windowsapp") \ No newline at end of file diff --git a/src/modules/imageresizer/dll/stdafx.h b/src/modules/imageresizer/dll/stdafx.h new file mode 100644 index 0000000000..5d108dd86e --- /dev/null +++ b/src/modules/imageresizer/dll/stdafx.h @@ -0,0 +1,23 @@ +#pragma once + +#ifndef STRICT +#define STRICT +#endif + +#define _ATL_APARTMENT_THREADED +#define _ATL_NO_AUTOMATIC_NAMESPACE +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS +#define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW + +#include "targetver.h" +#include "resource.h" + +#include +#include +#include +#include +#include +#include + +#include +#include diff --git a/src/modules/imageresizer/dll/targetver.h b/src/modules/imageresizer/dll/targetver.h new file mode 100644 index 0000000000..1530e99ea6 --- /dev/null +++ b/src/modules/imageresizer/dll/targetver.h @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/src/modules/imageresizer/dll/trace.cpp b/src/modules/imageresizer/dll/trace.cpp new file mode 100644 index 0000000000..8df63836f5 --- /dev/null +++ b/src/modules/imageresizer/dll/trace.cpp @@ -0,0 +1,49 @@ +#include "stdafx.h" +#include "trace.h" + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +void Trace::RegisterProvider() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +void Trace::EnableImageResizer(_In_ bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "ImageResizer_EnableImageResizer", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + + +void Trace::Invoked() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "ImageResizer_Invoked", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +void Trace::InvokedRet(_In_ HRESULT hr) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "ImageResizer_InvokedRet", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingHResult(hr), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/imageresizer/dll/trace.h b/src/modules/imageresizer/dll/trace.h new file mode 100644 index 0000000000..fcb39a3757 --- /dev/null +++ b/src/modules/imageresizer/dll/trace.h @@ -0,0 +1,11 @@ +#pragma once + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + static void EnableImageResizer(_In_ bool enabled) noexcept; + static void Invoked() noexcept; + static void InvokedRet(_In_ HRESULT hr) noexcept; +}; \ No newline at end of file diff --git a/src/modules/imageresizer/tests/App.config b/src/modules/imageresizer/tests/App.config new file mode 100644 index 0000000000..2a2d449901 --- /dev/null +++ b/src/modules/imageresizer/tests/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/modules/imageresizer/tests/ImageResizerUITest.csproj b/src/modules/imageresizer/tests/ImageResizerUITest.csproj new file mode 100644 index 0000000000..5cfaf9011d --- /dev/null +++ b/src/modules/imageresizer/tests/ImageResizerUITest.csproj @@ -0,0 +1,115 @@ + + + + + Debug + x64 + {E0CC7526-D85E-43AC-844F-D5DF0D2F5AB8} + Library + Properties + ImageResizer + ImageResizer.Test + v4.7.2 + 512 + + + true + + + true + full + false + $(SolutionDir)$(Platform)\$(Configuration) + DEBUG;TRACE + prompt + 4 + false + ..\..\..\codeAnalysis\Rules.ruleset + + + pdbonly + true + $(SolutionDir)$(Platform)\$(Configuration) + TRACE + prompt + 4 + false + ..\..\..\codeAnalysis\Rules.ruleset + + + + + + + + + {2be46397-4dfa-414c-9bd4-41e4bbf8cb34} + ImageResizerUI + + + + + StyleCop.json + + + Designer + + + + + GlobalSuppressions.cs + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + Always + + + + + 4.13.1 + + + 1.1.118 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 4.5.0 + + + 2.4.1 + + + 2.4.1 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + \ No newline at end of file diff --git a/src/modules/imageresizer/tests/Models/CustomSizeTests.cs b/src/modules/imageresizer/tests/Models/CustomSizeTests.cs new file mode 100644 index 0000000000..dda9bcfb3f --- /dev/null +++ b/src/modules/imageresizer/tests/Models/CustomSizeTests.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using ImageResizer.Properties; +using Xunit; + +namespace ImageResizer.Models +{ + public class CustomSizeTests + { + [Fact] + public void Name_works() + { + var size = new CustomSize(); + + size.Name = "Ignored"; + + Assert.Equal(Resources.Input_Custom, size.Name); + } + } +} diff --git a/src/modules/imageresizer/tests/Models/ResizeBatchTests.cs b/src/modules/imageresizer/tests/Models/ResizeBatchTests.cs new file mode 100644 index 0000000000..cfc2c430e5 --- /dev/null +++ b/src/modules/imageresizer/tests/Models/ResizeBatchTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Moq; +using Moq.Protected; +using Xunit; + +namespace ImageResizer.Models +{ + public class ResizeBatchTests + { + private static readonly string EOL = Environment.NewLine; + + [Fact] + public void FromCommandLine_works() + { + var standardInput = + "Image1.jpg" + EOL + + "Image2.jpg"; + var args = new[] + { + "/d", "OutputDir", + "Image3.jpg", + }; + + var result = ResizeBatch.FromCommandLine( + new StringReader(standardInput), + args); + + Assert.Equal(new List { "Image1.jpg", "Image2.jpg", "Image3.jpg" }, result.Files); + + Assert.Equal("OutputDir", result.DestinationDirectory); + } + + [Fact] + public void Process_executes_in_parallel() + { + var batch = CreateBatch(_ => Thread.Sleep(50)); + batch.Files.AddRange( + Enumerable.Range(0, Environment.ProcessorCount) + .Select(i => "Image" + i + ".jpg")); + + var stopwatch = Stopwatch.StartNew(); + batch.Process(CancellationToken.None, (_, __) => { }); + stopwatch.Stop(); + + Assert.InRange(stopwatch.ElapsedMilliseconds, 50, 99); + } + + [Fact] + public void Process_aggregates_errors() + { + var batch = CreateBatch(file => throw new Exception("Error: " + file)); + batch.Files.Add("Image1.jpg"); + batch.Files.Add("Image2.jpg"); + + var errors = batch.Process(CancellationToken.None, (_, __) => { }).ToList(); + + Assert.Equal(2, errors.Count); + + var errorFiles = new List(); + + foreach (var error in errors) + { + errorFiles.Add(error.File); + Assert.Equal("Error: " + error.File, error.Error); + } + + foreach (var file in batch.Files) + { + Assert.Contains(file, errorFiles); + } + } + + [Fact] + public void Process_reports_progress() + { + var batch = CreateBatch(_ => { }); + batch.Files.Add("Image1.jpg"); + batch.Files.Add("Image2.jpg"); + var calls = new ConcurrentBag<(int i, double count)>(); + + batch.Process( + CancellationToken.None, + (i, count) => calls.Add((i, count))); + + Assert.Equal(2, calls.Count); + Assert.Contains(calls, c => c.i == 1 && c.count == 2); + Assert.Contains(calls, c => c.i == 2 && c.count == 2); + } + + private static ResizeBatch CreateBatch(Action executeAction) + { + var mock = new Mock { CallBase = true }; + mock.Protected().Setup("Execute", ItExpr.IsAny()).Callback(executeAction); + + return mock.Object; + } + } +} diff --git a/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs b/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs new file mode 100644 index 0000000000..abbe55c5c9 --- /dev/null +++ b/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs @@ -0,0 +1,453 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.ObjectModel; +using System.IO; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using ImageResizer.Properties; +using ImageResizer.Test; +using Xunit; + +namespace ImageResizer.Models +{ + public class ResizeOperationTests : IDisposable + { + private readonly TestDirectory _directory = new TestDirectory(); + + [Fact] + public void Execute_copies_frame_metadata() + { + var operation = new ResizeOperation("Test.jpg", _directory, Settings()); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => Assert.Equal("Test", ((BitmapMetadata)image.Frames[0].Metadata).Comment)); + } + + [Fact] + public void Execute_keeps_date_modified() + { + var operation = new ResizeOperation("Test.png", _directory, Settings(s => s.KeepDateModified = true)); + + operation.Execute(); + + Assert.Equal(File.GetLastWriteTimeUtc("Test.png"), File.GetLastWriteTimeUtc(_directory.File())); + } + + [Fact] + public void Execute_keeps_date_modified_when_replacing_originals() + { + var path = Path.Combine(_directory, "Test.png"); + File.Copy("Test.png", path); + + var originalDateModified = File.GetLastWriteTimeUtc(path); + + var operation = new ResizeOperation( + path, + null, + Settings( + s => + { + s.KeepDateModified = true; + s.Replace = true; + })); + + operation.Execute(); + + Assert.Equal(originalDateModified, File.GetLastWriteTimeUtc(_directory.File())); + } + + [Fact] + public void Execute_replaces_originals() + { + var path = Path.Combine(_directory, "Test.png"); + File.Copy("Test.png", path); + + var operation = new ResizeOperation(path, null, Settings(s => s.Replace = true)); + + operation.Execute(); + + AssertEx.Image(_directory.File(), image => Assert.Equal(96, image.Frames[0].PixelWidth)); + } + + [Fact] + public void Execute_transforms_each_frame() + { + var operation = new ResizeOperation("Test.gif", _directory, Settings()); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(2, image.Frames.Count); + AssertEx.All(image.Frames, frame => Assert.Equal(96, frame.PixelWidth)); + }); + } + + [Fact] + public void Execute_uses_fallback_encoder() + { + var operation = new ResizeOperation( + "Test.ico", + _directory, + Settings(s => s.FallbackEncoder = new PngBitmapEncoder().CodecInfo.ContainerFormat)); + + operation.Execute(); + + Assert.Contains("Test (Test).png", _directory.FileNames); + } + + [Fact] + public void Transform_ignores_orientation_when_landscape_to_portrait() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.IgnoreOrientation = true; + x.SelectedSize.Width = 96; + x.SelectedSize.Height = 192; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(192, image.Frames[0].PixelWidth); + Assert.Equal(96, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_ignores_orientation_when_portrait_to_landscape() + { + var operation = new ResizeOperation( + "TestPortrait.png", + _directory, + Settings( + x => + { + x.IgnoreOrientation = true; + x.SelectedSize.Width = 192; + x.SelectedSize.Height = 96; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(96, image.Frames[0].PixelWidth); + Assert.Equal(192, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_ignores_ignore_orientation_when_auto() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.IgnoreOrientation = true; + x.SelectedSize.Width = 96; + x.SelectedSize.Height = 0; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(96, image.Frames[0].PixelWidth); + Assert.Equal(48, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_ignores_ignore_orientation_when_percent() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.IgnoreOrientation = true; + x.SelectedSize.Width = 50; + x.SelectedSize.Height = 200; + x.SelectedSize.Unit = ResizeUnit.Percent; + x.SelectedSize.Fit = ResizeFit.Stretch; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(96, image.Frames[0].PixelWidth); + Assert.Equal(192, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_honors_shrink_only() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.ShrinkOnly = true; + x.SelectedSize.Width = 288; + x.SelectedSize.Height = 288; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(192, image.Frames[0].PixelWidth); + Assert.Equal(96, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_ignores_shrink_only_when_percent() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.ShrinkOnly = true; + x.SelectedSize.Width = 133.3; + x.SelectedSize.Unit = ResizeUnit.Percent; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(256, image.Frames[0].PixelWidth); + Assert.Equal(128, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_honors_shrink_only_when_auto_height() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.ShrinkOnly = true; + x.SelectedSize.Width = 288; + x.SelectedSize.Height = 0; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => Assert.Equal(192, image.Frames[0].PixelWidth)); + } + + [Fact] + public void Transform_honors_shrink_only_when_auto_width() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.ShrinkOnly = true; + x.SelectedSize.Width = 0; + x.SelectedSize.Height = 288; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => Assert.Equal(96, image.Frames[0].PixelHeight)); + } + + [Fact] + public void Transform_honors_unit() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + x => + { + x.SelectedSize.Width = 1; + x.SelectedSize.Height = 1; + x.SelectedSize.Unit = ResizeUnit.Inch; + })); + + operation.Execute(); + + AssertEx.Image(_directory.File(), image => Assert.Equal(image.Frames[0].DpiX, image.Frames[0].PixelWidth, 0)); + } + + [Fact] + public void Transform_honors_fit_when_Fit() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings(x => x.SelectedSize.Fit = ResizeFit.Fit)); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(96, image.Frames[0].PixelWidth); + Assert.Equal(48, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_honors_fit_when_Fill() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings(x => x.SelectedSize.Fit = ResizeFit.Fill)); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(Colors.White, image.Frames[0].GetFirstPixel()); + Assert.Equal(96, image.Frames[0].PixelWidth); + Assert.Equal(96, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void Transform_honors_fit_when_Stretch() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings(x => x.SelectedSize.Fit = ResizeFit.Stretch)); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => + { + Assert.Equal(Colors.Black, image.Frames[0].GetFirstPixel()); + Assert.Equal(96, image.Frames[0].PixelWidth); + Assert.Equal(96, image.Frames[0].PixelHeight); + }); + } + + [Fact] + public void GetDestinationPath_uniquifies_output_filename() + { + File.WriteAllBytes(Path.Combine(_directory, "Test (Test).png"), new byte[0]); + + var operation = new ResizeOperation("Test.png", _directory, Settings()); + + operation.Execute(); + + Assert.Contains("Test (Test) (1).png", _directory.FileNames); + } + + [Fact] + public void GetDestinationPath_uniquifies_output_filename_again() + { + File.WriteAllBytes(Path.Combine(_directory, "Test (Test).png"), new byte[0]); + File.WriteAllBytes(Path.Combine(_directory, "Test (Test) (1).png"), new byte[0]); + + var operation = new ResizeOperation("Test.png", _directory, Settings()); + + operation.Execute(); + + Assert.Contains("Test (Test) (2).png", _directory.FileNames); + } + + [Fact] + public void GetDestinationPath_uses_fileName_format() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings(s => s.FileName = "%1_%2_%3_%4_%5_%6")); + + operation.Execute(); + + Assert.Contains("Test_Test_96_96_96_48.png", _directory.FileNames); + } + + [Fact] + public void Execute_handles_directories_in_fileName_format() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings(s => s.FileName = @"Directory\%1 (%2)")); + + operation.Execute(); + + Assert.True(File.Exists(_directory + @"\Directory\Test (Test).png")); + } + + public void Dispose() + => _directory.Dispose(); + + private Settings Settings(Action action = null) + { + var settings = new Settings + { + Sizes = new ObservableCollection + { + new ResizeSize + { + Name = "Test", + Width = 96, + Height = 96, + }, + }, + SelectedSizeIndex = 0, + }; + action?.Invoke(settings); + + return settings; + } + } +} diff --git a/src/modules/imageresizer/tests/Models/ResizeSizeTests.cs b/src/modules/imageresizer/tests/Models/ResizeSizeTests.cs new file mode 100644 index 0000000000..28df20c6f7 --- /dev/null +++ b/src/modules/imageresizer/tests/Models/ResizeSizeTests.cs @@ -0,0 +1,272 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Collections.Generic; +using System.ComponentModel; +using ImageResizer.Properties; +using ImageResizer.Test; +using Xunit; +using Xunit.Extensions; + +namespace ImageResizer.Models +{ + public class ResizeSizeTests + { + [Fact] + public void Name_works() + { + var size = new ResizeSize(); + + var e = AssertEx.Raises( + h => size.PropertyChanged += h, + h => size.PropertyChanged -= h, + () => size.Name = "Test"); + + Assert.Equal("Test", size.Name); + Assert.Equal(nameof(ResizeSize.Name), e.Arguments.PropertyName); + } + + [Fact] + public void Name_replaces_tokens() + { + var args = new List<(string, string)> + { + ("$small$", Resources.Small), + ("$medium$", Resources.Medium), + ("$large$", Resources.Large), + ("$phone$", Resources.Phone), + }; + foreach (var (name, expected) in args) + { + var size = new ResizeSize(); + + size.Name = name; + + Assert.Equal(expected, size.Name); + } + } + + [Fact] + public void Fit_works() + { + var size = new ResizeSize(); + + var e = AssertEx.Raises( + h => size.PropertyChanged += h, + h => size.PropertyChanged -= h, + () => size.Fit = ResizeFit.Stretch); + + Assert.Equal(ResizeFit.Stretch, size.Fit); + Assert.Equal(nameof(ResizeSize.Fit), e.Arguments.PropertyName); + } + + [Fact] + public void Width_works() + { + var size = new ResizeSize(); + + var e = AssertEx.Raises( + h => size.PropertyChanged += h, + h => size.PropertyChanged -= h, + () => size.Width = 42); + + Assert.Equal(42, size.Width); + Assert.Equal(nameof(ResizeSize.Width), e.Arguments.PropertyName); + } + + [Fact] + public void Height_works() + { + var size = new ResizeSize(); + + var e = AssertEx.Raises( + h => size.PropertyChanged += h, + h => size.PropertyChanged -= h, + () => size.Height = 42); + + Assert.Equal(42, size.Height); + Assert.Equal(nameof(ResizeSize.Height), e.Arguments.PropertyName); + } + + [Fact] + public void HasAuto_returns_true_when_Width_unset() + { + var size = new ResizeSize + { + Width = 0, + Height = 42, + }; + + Assert.True(size.HasAuto); + } + + [Fact] + public void HasAuto_returns_true_when_Height_unset() + { + var size = new ResizeSize + { + Width = 42, + Height = 0, + }; + + Assert.True(size.HasAuto); + } + + [Fact] + public void HasAuto_returns_false_when_Width_and_Height_set() + { + var size = new ResizeSize + { + Width = 42, + Height = 42, + }; + + Assert.False(size.HasAuto); + } + + [Fact] + public void Unit_works() + { + var size = new ResizeSize(); + + var e = AssertEx.Raises( + h => size.PropertyChanged += h, + h => size.PropertyChanged -= h, + () => size.Unit = ResizeUnit.Inch); + + Assert.Equal(ResizeUnit.Inch, size.Unit); + Assert.Equal(nameof(ResizeSize.Unit), e.Arguments.PropertyName); + } + + [Fact] + public void GetPixelWidth_works() + { + var size = new ResizeSize + { + Width = 1, + Unit = ResizeUnit.Inch, + }; + + var result = size.GetPixelWidth(100, 96); + + Assert.Equal(96, result); + } + + [Fact] + public void GetPixelHeight_works() + { + var size = new ResizeSize + { + Height = 1, + Unit = ResizeUnit.Inch, + }; + + var result = size.GetPixelHeight(100, 96); + + Assert.Equal(96, result); + } + + [Theory] + [InlineData(ResizeFit.Fit)] + [InlineData(ResizeFit.Fill)] + public void GetPixelHeight_uses_Width_when_scale_by_percent(ResizeFit fit) + { + var size = new ResizeSize + { + Fit = fit, + Width = 100, + Height = 50, + Unit = ResizeUnit.Percent, + }; + + var result = size.GetPixelHeight(100, 96); + + Assert.Equal(100, result); + } + + [Fact] + public void ConvertToPixels_works_when_auto_and_fit() + { + var size = new ResizeSize + { + Width = 0, + Fit = ResizeFit.Fit, + }; + + var result = size.GetPixelWidth(100, 96); + + Assert.Equal(double.PositiveInfinity, result); + } + + [Fact] + public void ConvertToPixels_works_when_auto_and_not_fit() + { + var size = new ResizeSize + { + Width = 0, + Fit = ResizeFit.Fill, + }; + + var result = size.GetPixelWidth(100, 96); + + Assert.Equal(100, result); + } + + [Fact] + public void ConvertToPixels_works_when_inches() + { + var size = new ResizeSize + { + Width = 0.5, + Unit = ResizeUnit.Inch, + }; + + var result = size.GetPixelWidth(100, 96); + + Assert.Equal(48, result); + } + + [Fact] + public void ConvertToPixels_works_when_centimeters() + { + var size = new ResizeSize + { + Width = 1, + Unit = ResizeUnit.Centimeter, + }; + + var result = size.GetPixelWidth(100, 96); + + Assert.Equal(38, result, 0); + } + + [Fact] + public void ConvertToPixels_works_when_percent() + { + var size = new ResizeSize + { + Width = 50, + Unit = ResizeUnit.Percent, + }; + + var result = size.GetPixelWidth(200, 96); + + Assert.Equal(100, result); + } + + [Fact] + public void ConvertToPixels_works_when_pixels() + { + var size = new ResizeSize + { + Width = 50, + Unit = ResizeUnit.Pixel, + }; + + var result = size.GetPixelWidth(100, 96); + + Assert.Equal(50, result); + } + } +} diff --git a/src/modules/imageresizer/tests/Properties/AppFixture.cs b/src/modules/imageresizer/tests/Properties/AppFixture.cs new file mode 100644 index 0000000000..2dffb384b8 --- /dev/null +++ b/src/modules/imageresizer/tests/Properties/AppFixture.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1636:FileHeaderCopyrightTextMustMatch", Justification = "File created under PowerToys.")] + +namespace ImageResizer.Properties +{ + public class AppFixture : IDisposable + { + public AppFixture() + { + // new App() needs to be created since Settings.Reload() uses App.Current to update properties on the UI thread. App() can be created only once otherwise it results in System.InvalidOperationException : Cannot create more than one System.Windows.Application instance in the same AppDomain. + imageResizerApp = new App(); + } + + public void Dispose() + { + imageResizerApp = null; + } + + private App imageResizerApp; + } +} diff --git a/src/modules/imageresizer/tests/Properties/SettingsTests.cs b/src/modules/imageresizer/tests/Properties/SettingsTests.cs new file mode 100644 index 0000000000..54de9aaf36 --- /dev/null +++ b/src/modules/imageresizer/tests/Properties/SettingsTests.cs @@ -0,0 +1,280 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using ImageResizer.Models; +using ImageResizer.Test; +using Xunit; +using Xunit.Abstractions; +using Xunit.Extensions; + +namespace ImageResizer.Properties +{ + public class SettingsTests : IClassFixture, IDisposable + { + public SettingsTests() + { + // Change settings.json path to a temp file + Settings.SettingsPath = ".\\test_settings.json"; + } + + public void Dispose() + { + if (System.IO.File.Exists(Settings.SettingsPath)) + { + System.IO.File.Delete(Settings.SettingsPath); + } + } + + [Fact] + public void AllSizes_propagates_Sizes_collection_events() + { + var settings = new Settings + { + Sizes = new ObservableCollection(), + CustomSize = new CustomSize(), + }; + var ncc = (INotifyCollectionChanged)settings.AllSizes; + + var result = AssertEx.Raises( + h => ncc.CollectionChanged += h, + h => ncc.CollectionChanged -= h, + () => settings.Sizes.Add(new ResizeSize())); + + Assert.Equal(NotifyCollectionChangedAction.Add, result.Arguments.Action); + } + + [Fact] + public void AllSizes_propagates_Sizes_property_events() + { + var settings = new Settings + { + Sizes = new ObservableCollection(), + CustomSize = new CustomSize(), + }; + + Assert.PropertyChanged( + (INotifyPropertyChanged)settings.AllSizes, + "Item[]", + () => settings.Sizes.Add(new ResizeSize())); + } + + [Fact] + public void AllSizes_contains_Sizes() + { + var settings = new Settings + { + Sizes = new ObservableCollection { new ResizeSize() }, + CustomSize = new CustomSize(), + }; + + Assert.Contains(settings.Sizes[0], settings.AllSizes); + } + + [Fact] + public void AllSizes_contains_CustomSize() + { + var settings = new Settings + { + Sizes = new ObservableCollection(), + CustomSize = new CustomSize(), + }; + + Assert.Contains(settings.CustomSize, settings.AllSizes); + } + + [Fact] + public void AllSizes_handles_property_events_for_CustomSize() + { + var originalCustomSize = new CustomSize(); + var settings = new Settings + { + Sizes = new ObservableCollection(), + CustomSize = originalCustomSize, + }; + var ncc = (INotifyCollectionChanged)settings.AllSizes; + + var result = AssertEx.Raises( + h => ncc.CollectionChanged += h, + h => ncc.CollectionChanged -= h, + () => settings.CustomSize = new CustomSize()); + + Assert.Equal(NotifyCollectionChangedAction.Replace, result.Arguments.Action); + Assert.Equal(1, result.Arguments.NewItems.Count); + Assert.Equal(settings.CustomSize, result.Arguments.NewItems[0]); + Assert.Equal(0, result.Arguments.NewStartingIndex); + Assert.Equal(1, result.Arguments.OldItems.Count); + Assert.Equal(originalCustomSize, result.Arguments.OldItems[0]); + Assert.Equal(0, result.Arguments.OldStartingIndex); + } + + [Fact] + public void FileNameFormat_works() + { + var settings = new Settings { FileName = "{T}%1e%2s%3t%4%5%6%7" }; + + var result = settings.FileNameFormat; + + Assert.Equal("{{T}}{0}e{1}s{2}t{3}{4}{5}%7", result); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void SelectedSize_returns_CustomSize_when_out_of_range(int index) + { + var settings = new Settings + { + SelectedSizeIndex = index, + Sizes = new ObservableCollection(), + CustomSize = new CustomSize(), + }; + + var result = settings.SelectedSize; + + Assert.Same(settings.CustomSize, result); + } + + [Fact] + public void SelectedSize_returns_Size_when_in_range() + { + var settings = new Settings + { + SelectedSizeIndex = 0, + Sizes = new ObservableCollection + { + new ResizeSize(), + }, + }; + + var result = settings.SelectedSize; + + Assert.Same(settings.Sizes[0], result); + } + + [Fact] + public void IDataErrorInfo_Error_returns_empty() + { + var settings = new Settings(); + + var result = ((IDataErrorInfo)settings).Error; + + Assert.Empty(result); + } + + [Theory] + [InlineData(0)] + [InlineData(101)] + public void IDataErrorInfo_Item_JpegQualityLevel_returns_error_when_out_of_range(int value) + { + var settings = new Settings { JpegQualityLevel = value }; + + var result = ((IDataErrorInfo)settings)["JpegQualityLevel"]; + + Assert.Equal( + string.Format(Resources.ValueMustBeBetween, 1, 100), + result); + } + + [Theory] + [InlineData(1)] + [InlineData(100)] + public void IDataErrorInfo_Item_JpegQualityLevel_returns_empty_when_in_range(int value) + { + var settings = new Settings { JpegQualityLevel = value }; + + var result = ((IDataErrorInfo)settings)["JpegQualityLevel"]; + + Assert.Empty(result); + } + + [Fact] + public void IDataErrorInfo_Item_returns_empty_when_not_JpegQualityLevel() + { + var settings = new Settings(); + + var result = ((IDataErrorInfo)settings)["Unknown"]; + + Assert.Empty(result); + } + + [Fact] + public void Reload_createsFile_when_FileNotFound() + { + // Arrange + var settings = new Settings(); + + // Assert + Assert.False(System.IO.File.Exists(Settings.SettingsPath)); + + // Act + settings.Reload(); + + // Assert + Assert.True(System.IO.File.Exists(Settings.SettingsPath)); + } + + [Fact] + public void Save_creates_file() + { + // Arrange + var settings = new Settings(); + + // Assert + Assert.False(System.IO.File.Exists(Settings.SettingsPath)); + + // Act + settings.Save(); + + // Assert + Assert.True(System.IO.File.Exists(Settings.SettingsPath)); + } + + [Fact] + public void Save_json_is_readable_by_Reload() + { + // Arrange + var settings = new Settings(); + + // Assert + Assert.False(System.IO.File.Exists(Settings.SettingsPath)); + + // Act + settings.Save(); + settings.Reload(); // If the JSON file created by Save() is not readable this function will throw an error + + // Assert + Assert.True(System.IO.File.Exists(Settings.SettingsPath)); + } + + [Fact] + public void Reload_raises_PropertyChanged_() + { + // Arrange + var settings = new Settings(); + settings.Save(); // To create the settings file + + // Act + var action = new System.Action(settings.Reload); + + // Assert + Assert.PropertyChanged(settings, "ShrinkOnly", action); + Assert.PropertyChanged(settings, "Replace", action); + Assert.PropertyChanged(settings, "IgnoreOrientation", action); + Assert.PropertyChanged(settings, "JpegQualityLevel", action); + Assert.PropertyChanged(settings, "PngInterlaceOption", action); + Assert.PropertyChanged(settings, "TiffCompressOption", action); + Assert.PropertyChanged(settings, "FileName", action); + Assert.PropertyChanged(settings, "Sizes", action); + Assert.PropertyChanged(settings, "KeepDateModified", action); + Assert.PropertyChanged(settings, "FallbackEncoder", action); + Assert.PropertyChanged(settings, "CustomSize", action); + Assert.PropertyChanged(settings, "SelectedSizeIndex", action); + } + } +} diff --git a/src/modules/imageresizer/tests/Test.gif b/src/modules/imageresizer/tests/Test.gif new file mode 100644 index 0000000000..b09c588a60 Binary files /dev/null and b/src/modules/imageresizer/tests/Test.gif differ diff --git a/src/modules/imageresizer/tests/Test.ico b/src/modules/imageresizer/tests/Test.ico new file mode 100644 index 0000000000..c6bbbb621a Binary files /dev/null and b/src/modules/imageresizer/tests/Test.ico differ diff --git a/src/modules/imageresizer/tests/Test.jpg b/src/modules/imageresizer/tests/Test.jpg new file mode 100644 index 0000000000..70e13bf528 Binary files /dev/null and b/src/modules/imageresizer/tests/Test.jpg differ diff --git a/src/modules/imageresizer/tests/Test.png b/src/modules/imageresizer/tests/Test.png new file mode 100644 index 0000000000..e4f09732cf Binary files /dev/null and b/src/modules/imageresizer/tests/Test.png differ diff --git a/src/modules/imageresizer/tests/Test/AssertEx.cs b/src/modules/imageresizer/tests/Test/AssertEx.cs new file mode 100644 index 0000000000..449c29383f --- /dev/null +++ b/src/modules/imageresizer/tests/Test/AssertEx.cs @@ -0,0 +1,87 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Windows.Media.Imaging; +using Xunit; + +namespace ImageResizer.Test +{ + internal static class AssertEx + { + public static void All(IEnumerable collection, Action action) + { + foreach (var item in collection) + { + action(item); + } + } + + public static void Image(string path, Action action) + { + using (var stream = File.OpenRead(path)) + { + var image = BitmapDecoder.Create( + stream, + BitmapCreateOptions.PreservePixelFormat, + BitmapCacheOption.None); + + action(image); + } + } + + public static RaisedEvent Raises( + Action attach, + Action detach, + Action testCode) + where T : NotifyCollectionChangedEventArgs + { + RaisedEvent raisedEvent = null; + NotifyCollectionChangedEventHandler handler = (sender, e) + => raisedEvent = new RaisedEvent(sender, e); + attach(handler); + testCode(); + detach(handler); + + Assert.NotNull(raisedEvent); + + return raisedEvent; + } + + public static RaisedEvent Raises( + Action attach, + Action detach, + Action testCode) + where T : PropertyChangedEventArgs + { + RaisedEvent raisedEvent = null; + PropertyChangedEventHandler handler = (sender, e) + => raisedEvent = new RaisedEvent(sender, e); + attach(handler); + testCode(); + detach(handler); + + Assert.NotNull(raisedEvent); + + return raisedEvent; + } + + public class RaisedEvent + { + public RaisedEvent(object sender, TArgs args) + { + Sender = sender; + Arguments = args; + } + + public object Sender { get; } + + public TArgs Arguments { get; } + } + } +} diff --git a/src/modules/imageresizer/tests/Test/BitmapSourceExtensions.cs b/src/modules/imageresizer/tests/Test/BitmapSourceExtensions.cs new file mode 100644 index 0000000000..c226ff6637 --- /dev/null +++ b/src/modules/imageresizer/tests/Test/BitmapSourceExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace ImageResizer.Test +{ + internal static class BitmapSourceExtensions + { + public static Color GetFirstPixel(this BitmapSource source) + { + var pixel = new byte[4]; + new FormatConvertedBitmap( + new CroppedBitmap(source, new Int32Rect(0, 0, 1, 1)), + PixelFormats.Bgra32, + destinationPalette: null, + alphaThreshold: 0) + .CopyPixels(pixel, 4, 0); + + return Color.FromArgb(pixel[3], pixel[2], pixel[1], pixel[0]); + } + } +} diff --git a/src/modules/imageresizer/tests/Test/TestDirectory.cs b/src/modules/imageresizer/tests/Test/TestDirectory.cs new file mode 100644 index 0000000000..32c50d38e9 --- /dev/null +++ b/src/modules/imageresizer/tests/Test/TestDirectory.cs @@ -0,0 +1,57 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using Xunit; +using IOPath = System.IO.Path; + +namespace ImageResizer +{ + public class TestDirectory : IDisposable + { + private readonly string _path; + + public TestDirectory() + { + _path = IOPath.Combine( + AppDomain.CurrentDomain.BaseDirectory, + IOPath.GetRandomFileName()); + Directory.CreateDirectory(_path); + } + + private IEnumerable Files + => Directory.EnumerateFiles(_path); + + public IEnumerable FileNames + => Files.Select(IOPath.GetFileName); + + public string File() + => Assert.Single(Files); + + public void Dispose() + { + var stopwatch = Stopwatch.StartNew(); + while (stopwatch.ElapsedMilliseconds < 30000) + { + try + { + Directory.Delete(_path, recursive: true); + break; + } + catch + { + Thread.Sleep(150); + } + } + } + + public static implicit operator string(TestDirectory directory) + => directory._path; + } +} diff --git a/src/modules/imageresizer/tests/TestPortrait.png b/src/modules/imageresizer/tests/TestPortrait.png new file mode 100644 index 0000000000..dbc49c6741 Binary files /dev/null and b/src/modules/imageresizer/tests/TestPortrait.png differ diff --git a/src/modules/imageresizer/tests/Views/TimeRemainingConverterTests.cs b/src/modules/imageresizer/tests/Views/TimeRemainingConverterTests.cs new file mode 100644 index 0000000000..d3c12827e5 --- /dev/null +++ b/src/modules/imageresizer/tests/Views/TimeRemainingConverterTests.cs @@ -0,0 +1,46 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Globalization; +using ImageResizer.Properties; +using Xunit; +using Xunit.Extensions; + +namespace ImageResizer.Views +{ + public class TimeRemainingConverterTests + { + [Theory] + [InlineData("HourMinute", 1, 1, 0)] + [InlineData("HourMinutes", 1, 2, 0)] + [InlineData("HoursMinute", 2, 1, 0)] + [InlineData("HoursMinutes", 2, 2, 0)] + [InlineData("MinuteSecond", 0, 1, 1)] + [InlineData("MinuteSeconds", 0, 1, 2)] + [InlineData("MinutesSecond", 0, 2, 1)] + [InlineData("MinutesSeconds", 0, 2, 2)] + [InlineData("Second", 0, 0, 1)] + [InlineData("Seconds", 0, 0, 2)] + public void Convert_works(string resource, int hours, int minutes, int seconds) + { + var timeRemaining = new TimeSpan(hours, minutes, seconds); + var converter = new TimeRemainingConverter(); + + var result = converter.Convert( + timeRemaining, + targetType: null, + parameter: null, + CultureInfo.InvariantCulture); + + Assert.Equal( + string.Format( + Resources.ResourceManager.GetString("Progress_TimeRemaining_" + resource), + hours, + minutes, + seconds), + result); + } + } +} diff --git a/src/modules/imageresizer/ui/App.config b/src/modules/imageresizer/ui/App.config new file mode 100644 index 0000000000..2a2d449901 --- /dev/null +++ b/src/modules/imageresizer/ui/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/modules/imageresizer/ui/App.xaml b/src/modules/imageresizer/ui/App.xaml new file mode 100644 index 0000000000..0f47c91079 --- /dev/null +++ b/src/modules/imageresizer/ui/App.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/imageresizer/ui/App.xaml.cs b/src/modules/imageresizer/ui/App.xaml.cs new file mode 100644 index 0000000000..c2493148b1 --- /dev/null +++ b/src/modules/imageresizer/ui/App.xaml.cs @@ -0,0 +1,45 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Text; +using System.Windows; +using GalaSoft.MvvmLight.Threading; +using ImageResizer.Models; +using ImageResizer.Properties; +using ImageResizer.Utilities; +using ImageResizer.ViewModels; +using ImageResizer.Views; + +namespace ImageResizer +{ + public partial class App : Application + { + static App() + { + Console.InputEncoding = Encoding.Unicode; + DispatcherHelper.Initialize(); + } + + protected override void OnStartup(StartupEventArgs e) + { + var batch = ResizeBatch.FromCommandLine(Console.In, e.Args); + + // TODO: Add command-line parameters that can be used in lieu of the input page (issue #14) + var mainWindow = new MainWindow(new MainViewModel(batch, Settings.Default)); + mainWindow.Show(); + + // Temporary workaround for issue #1273 + BecomeForegroundWindow(new System.Windows.Interop.WindowInteropHelper(mainWindow).Handle); + } + + private void BecomeForegroundWindow(IntPtr hWnd) + { + Win32Helpers.INPUT input = new Win32Helpers.INPUT { type = Win32Helpers.INPUTTYPE.INPUT_MOUSE, data = { } }; + Win32Helpers.INPUT[] inputs = new Win32Helpers.INPUT[] { input }; + Win32Helpers.SendInput(1, inputs, Win32Helpers.INPUT.Size); + Win32Helpers.SetForegroundWindow(hWnd); + } + } +} diff --git a/src/modules/imageresizer/ui/Extensions/BitmapEncoderExtensions.cs b/src/modules/imageresizer/ui/Extensions/BitmapEncoderExtensions.cs new file mode 100644 index 0000000000..78bcdb1098 --- /dev/null +++ b/src/modules/imageresizer/ui/Extensions/BitmapEncoderExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace System.Windows.Media.Imaging +{ + internal static class BitmapEncoderExtensions + { + public static bool CanEncode(this BitmapEncoder encoder) + { + try + { + var temp = encoder.CodecInfo; + } + catch (NotSupportedException) + { + return false; + } + + return true; + } + } +} diff --git a/src/modules/imageresizer/ui/Extensions/ICollectionExtensions.cs b/src/modules/imageresizer/ui/Extensions/ICollectionExtensions.cs new file mode 100644 index 0000000000..05008f920b --- /dev/null +++ b/src/modules/imageresizer/ui/Extensions/ICollectionExtensions.cs @@ -0,0 +1,17 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace System.Collections.Generic +{ + internal static class ICollectionExtensions + { + public static void AddRange(this ICollection collection, IEnumerable items) + { + foreach (var item in items) + { + collection.Add(item); + } + } + } +} diff --git a/src/modules/imageresizer/ui/Extensions/TimeSpanExtensions.cs b/src/modules/imageresizer/ui/Extensions/TimeSpanExtensions.cs new file mode 100644 index 0000000000..f0bc48074a --- /dev/null +++ b/src/modules/imageresizer/ui/Extensions/TimeSpanExtensions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace System +{ + internal static class TimeSpanExtensions + { + public static TimeSpan Multiply(this TimeSpan timeSpan, double scalar) + => new TimeSpan((long)(timeSpan.Ticks * scalar)); + } +} diff --git a/src/modules/imageresizer/ui/ImageResizerUI.csproj b/src/modules/imageresizer/ui/ImageResizerUI.csproj new file mode 100644 index 0000000000..44f1cac9c2 --- /dev/null +++ b/src/modules/imageresizer/ui/ImageResizerUI.csproj @@ -0,0 +1,237 @@ + + + + + + + ImageResizer + Microsoft Corp. + Copyright (C) 2019 Microsoft Corp. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Debug + x64 + {2BE46397-4DFA-414C-9BD4-41E4BBF8CB34} + WinExe + ImageResizer + ImageResizer + v4.7.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + true + + + x64 + true + full + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\ + DEBUG;TRACE + prompt + 4 + false + ..\..\..\codeAnalysis\Rules.ruleset + + + x64 + pdbonly + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\ + TRACE + prompt + 4 + false + ..\..\..\codeAnalysis\Rules.ruleset + + + Resources\ImageResizer.ico + + + + + + + + + + + + MSBuild:Compile + Designer + + + GlobalSuppressions.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + AdvancedWindow.xaml + + + + + ProgressPage.xaml + + + + ResultsPage.xaml + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + InputPage.xaml + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + + + + + + + + + + + + + + + 5.4.1.1 + + + 12.0.3 + + + 1.1.118 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + $(TargetFrameworkSDKToolsDirectory)$(Platform)\ + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Models/CustomSize.cs b/src/modules/imageresizer/ui/Models/CustomSize.cs new file mode 100644 index 0000000000..60a2397dda --- /dev/null +++ b/src/modules/imageresizer/ui/Models/CustomSize.cs @@ -0,0 +1,31 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using ImageResizer.Properties; +using Newtonsoft.Json; + +namespace ImageResizer.Models +{ + public class CustomSize : ResizeSize + { + [JsonIgnore] + public override string Name + { + get => Resources.Input_Custom; + set { /* no-op */ } + } + + public CustomSize(ResizeFit fit, double width, double height, ResizeUnit unit) + { + Fit = fit; + Width = width; + Height = height; + Unit = unit; + } + + public CustomSize() + { + } + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeBatch.cs b/src/modules/imageresizer/ui/Models/ResizeBatch.cs new file mode 100644 index 0000000000..f9e5e3e3b3 --- /dev/null +++ b/src/modules/imageresizer/ui/Models/ResizeBatch.cs @@ -0,0 +1,85 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using ImageResizer.Properties; + +namespace ImageResizer.Models +{ + public class ResizeBatch + { + public string DestinationDirectory { get; set; } + + public ICollection Files { get; } = new List(); + + public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args) + { + var batch = new ResizeBatch(); + + // NB: We read these from stdin since there are limits on the number of args you can have + string file; + while ((file = standardInput.ReadLine()) != null) + { + batch.Files.Add(file); + } + + for (var i = 0; i < args.Length; i++) + { + if (args[i] == "/d") + { + batch.DestinationDirectory = args[++i]; + continue; + } + + batch.Files.Add(args[i]); + } + + return batch; + } + + public IEnumerable Process( + CancellationToken cancellationToken, + Action reportProgress) + { + double total = Files.Count; + var completed = 0; + var errors = new ConcurrentBag(); + + // TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async + // APIs and a custom SynchronizationContext + Parallel.ForEach( + Files, + new ParallelOptions + { + CancellationToken = cancellationToken, + MaxDegreeOfParallelism = Environment.ProcessorCount, + }, + (file, state, i) => + { + try + { + Execute(file); + } + catch (Exception ex) + { + errors.Add(new ResizeError { File = Path.GetFileName(file), Error = ex.Message }); + } + + Interlocked.Increment(ref completed); + + reportProgress(completed, total); + }); + + return errors; + } + + protected virtual void Execute(string file) + => new ResizeOperation(file, DestinationDirectory, Settings.Default).Execute(); + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeError.cs b/src/modules/imageresizer/ui/Models/ResizeError.cs new file mode 100644 index 0000000000..2d63a30349 --- /dev/null +++ b/src/modules/imageresizer/ui/Models/ResizeError.cs @@ -0,0 +1,13 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace ImageResizer.Models +{ + public class ResizeError + { + public string File { get; set; } + + public string Error { get; set; } + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeFit.cs b/src/modules/imageresizer/ui/Models/ResizeFit.cs new file mode 100644 index 0000000000..74073f1ea0 --- /dev/null +++ b/src/modules/imageresizer/ui/Models/ResizeFit.cs @@ -0,0 +1,13 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace ImageResizer.Models +{ + public enum ResizeFit + { + Fill, + Fit, + Stretch, + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeOperation.cs b/src/modules/imageresizer/ui/Models/ResizeOperation.cs new file mode 100644 index 0000000000..16a1981193 --- /dev/null +++ b/src/modules/imageresizer/ui/Models/ResizeOperation.cs @@ -0,0 +1,211 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using ImageResizer.Properties; +using ImageResizer.Utilities; +using Microsoft.VisualBasic.FileIO; + +namespace ImageResizer.Models +{ + internal class ResizeOperation + { + private readonly string _file; + private readonly string _destinationDirectory; + private readonly Settings _settings; + + public ResizeOperation(string file, string destinationDirectory, Settings settings) + { + _file = file; + _destinationDirectory = destinationDirectory; + _settings = settings; + } + + public void Execute() + { + string path; + using (var inputStream = File.OpenRead(_file)) + { + var decoder = BitmapDecoder.Create( + inputStream, + BitmapCreateOptions.PreservePixelFormat, + BitmapCacheOption.None); + + var encoder = BitmapEncoder.Create(decoder.CodecInfo.ContainerFormat); + if (!encoder.CanEncode()) + { + encoder = BitmapEncoder.Create(_settings.FallbackEncoder); + } + + ConfigureEncoder(encoder); + + if (decoder.Metadata != null) + { + try + { + encoder.Metadata = decoder.Metadata; + } + catch (InvalidOperationException) + { + } + } + + if (decoder.Palette != null) + { + encoder.Palette = decoder.Palette; + } + + foreach (var originalFrame in decoder.Frames) + { + encoder.Frames.Add( + BitmapFrame.Create( + Transform(originalFrame), + thumbnail: null, + (BitmapMetadata)originalFrame.Metadata, // TODO: Add an option to strip any metadata that doesn't affect rendering (issue #3) + colorContexts: null)); + } + + path = GetDestinationPath(encoder); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + using (var outputStream = File.Open(path, FileMode.CreateNew, FileAccess.Write)) + { + encoder.Save(outputStream); + } + } + + if (_settings.KeepDateModified) + { + File.SetLastWriteTimeUtc(path, File.GetLastWriteTimeUtc(_file)); + } + + if (_settings.Replace) + { + var backup = GetBackupPath(); + File.Replace(path, _file, backup, ignoreMetadataErrors: true); + FileSystem.DeleteFile(backup, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin); + } + } + + private void ConfigureEncoder(BitmapEncoder encoder) + { + switch (encoder) + { + case JpegBitmapEncoder jpegEncoder: + jpegEncoder.QualityLevel = MathHelpers.Clamp(_settings.JpegQualityLevel, 1, 100); + break; + + case PngBitmapEncoder pngBitmapEncoder: + pngBitmapEncoder.Interlace = _settings.PngInterlaceOption; + break; + + case TiffBitmapEncoder tiffEncoder: + tiffEncoder.Compression = _settings.TiffCompressOption; + break; + } + } + + private BitmapSource Transform(BitmapSource source) + { + var originalWidth = source.PixelWidth; + var originalHeight = source.PixelHeight; + var width = _settings.SelectedSize.GetPixelWidth(originalWidth, source.DpiX); + var height = _settings.SelectedSize.GetPixelHeight(originalHeight, source.DpiY); + + if (_settings.IgnoreOrientation + && !_settings.SelectedSize.HasAuto + && _settings.SelectedSize.Unit != ResizeUnit.Percent + && originalWidth < originalHeight != (width < height)) + { + var temp = width; + width = height; + height = temp; + } + + var scaleX = width / originalWidth; + var scaleY = height / originalHeight; + + if (_settings.SelectedSize.Fit == ResizeFit.Fit) + { + scaleX = Math.Min(scaleX, scaleY); + scaleY = scaleX; + } + else if (_settings.SelectedSize.Fit == ResizeFit.Fill) + { + scaleX = Math.Max(scaleX, scaleY); + scaleY = scaleX; + } + + if (_settings.ShrinkOnly + && _settings.SelectedSize.Unit != ResizeUnit.Percent + && (scaleX >= 1 || scaleY >= 1)) + { + return source; + } + + var scaledBitmap = new TransformedBitmap(source, new ScaleTransform(scaleX, scaleY)); + if (_settings.SelectedSize.Fit == ResizeFit.Fill + && (scaledBitmap.PixelWidth > width + || scaledBitmap.PixelHeight > height)) + { + var x = (int)(((originalWidth * scaleX) - width) / 2); + var y = (int)(((originalHeight * scaleY) - height) / 2); + + return new CroppedBitmap(scaledBitmap, new Int32Rect(x, y, (int)width, (int)height)); + } + + return scaledBitmap; + } + + private string GetDestinationPath(BitmapEncoder encoder) + { + var directory = _destinationDirectory ?? Path.GetDirectoryName(_file); + var originalFileName = Path.GetFileNameWithoutExtension(_file); + + var supportedExtensions = encoder.CodecInfo.FileExtensions.Split(','); + var extension = Path.GetExtension(_file); + if (!supportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + { + extension = supportedExtensions.FirstOrDefault(); + } + + var fileName = string.Format( + _settings.FileNameFormat, + originalFileName, + _settings.SelectedSize.Name, + _settings.SelectedSize.Width, + _settings.SelectedSize.Height, + encoder.Frames[0].PixelWidth, + encoder.Frames[0].PixelHeight); + var path = Path.Combine(directory, fileName + extension); + var uniquifier = 1; + while (File.Exists(path)) + { + path = Path.Combine(directory, fileName + " (" + uniquifier++ + ")" + extension); + } + + return path; + } + + private string GetBackupPath() + { + var directory = Path.GetDirectoryName(_file); + var fileName = Path.GetFileNameWithoutExtension(_file); + var extension = Path.GetExtension(_file); + + var path = Path.Combine(directory, fileName + ".bak" + extension); + var uniquifier = 1; + while (File.Exists(path)) + { + path = Path.Combine(directory, fileName + " (" + uniquifier++ + ")" + ".bak" + extension); + } + + return path; + } + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeSize.cs b/src/modules/imageresizer/ui/Models/ResizeSize.cs new file mode 100644 index 0000000000..fffc805ada --- /dev/null +++ b/src/modules/imageresizer/ui/Models/ResizeSize.cs @@ -0,0 +1,154 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Collections.Generic; +using System.Diagnostics; +using GalaSoft.MvvmLight; +using ImageResizer.Properties; +using Newtonsoft.Json; + +namespace ImageResizer.Models +{ + [JsonObject(MemberSerialization.OptIn)] + public class ResizeSize : ObservableObject + { + private static readonly IDictionary _tokens; + + private string _name; + private ResizeFit _fit = ResizeFit.Fit; + private double _width; + private double _height; + private bool _showHeight = true; + private ResizeUnit _unit = ResizeUnit.Pixel; + + static ResizeSize() + => _tokens = new Dictionary + { + ["$small$"] = Resources.Small, + ["$medium$"] = Resources.Medium, + ["$large$"] = Resources.Large, + ["$phone$"] = Resources.Phone, + }; + + public ResizeSize(string name, ResizeFit fit, double width, double height, ResizeUnit unit) + { + Name = name; + Fit = fit; + Width = width; + Height = height; + Unit = unit; + } + + public ResizeSize() + { + } + + [JsonProperty(PropertyName = "name")] + public virtual string Name + { + get => _name; + set => Set(nameof(Name), ref _name, ReplaceTokens(value)); + } + + [JsonProperty(PropertyName = "fit")] + public ResizeFit Fit + { + get => _fit; + set + { + if (Set(nameof(Fit), ref _fit, value)) + { + UpdateShowHeight(); + } + } + } + + [JsonProperty(PropertyName = "width")] + public double Width + { + get => _width; + set => Set(nameof(Width), ref _width, value); + } + + [JsonProperty(PropertyName = "height")] + public double Height + { + get => _height; + set => Set(nameof(Height), ref _height, value); + } + + public bool ShowHeight + => _showHeight; + + public bool HasAuto + => Width == 0 || Height == 0; + + [JsonProperty(PropertyName = "unit")] + public ResizeUnit Unit + { + get => _unit; + set + { + if (Set(nameof(Unit), ref _unit, value)) + { + UpdateShowHeight(); + } + } + } + + public double GetPixelWidth(int originalWidth, double dpi) + => ConvertToPixels(Width, Unit, originalWidth, dpi); + + public double GetPixelHeight(int originalHeight, double dpi) + => ConvertToPixels( + Fit != ResizeFit.Stretch && Unit == ResizeUnit.Percent + ? Width + : Height, + Unit, + originalHeight, + dpi); + + private static string ReplaceTokens(string text) + => (text != null && _tokens.TryGetValue(text, out var result)) + ? result + : text; + + private void UpdateShowHeight() + => Set( + nameof(ShowHeight), + ref _showHeight, + Fit == ResizeFit.Stretch || Unit != ResizeUnit.Percent); + + private double ConvertToPixels(double value, ResizeUnit unit, int originalValue, double dpi) + { + if (value == 0) + { + if (Fit == ResizeFit.Fit) + { + return double.PositiveInfinity; + } + + Debug.Assert(Fit == ResizeFit.Fill || Fit == ResizeFit.Stretch, "Unexpected ResizeFit value: " + Fit); + + return originalValue; + } + + switch (unit) + { + case ResizeUnit.Inch: + return value * dpi; + + case ResizeUnit.Centimeter: + return value * dpi / 2.54; + + case ResizeUnit.Percent: + return value / 100 * originalValue; + + default: + Debug.Assert(unit == ResizeUnit.Pixel, "Unexpected unit value: " + unit); + return value; + } + } + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeUnit.cs b/src/modules/imageresizer/ui/Models/ResizeUnit.cs new file mode 100644 index 0000000000..f3c6a59dc2 --- /dev/null +++ b/src/modules/imageresizer/ui/Models/ResizeUnit.cs @@ -0,0 +1,14 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace ImageResizer.Models +{ + public enum ResizeUnit + { + Centimeter, + Inch, + Percent, + Pixel, + } +} diff --git a/src/modules/imageresizer/ui/Properties/InternalsVisibleTo.cs b/src/modules/imageresizer/ui/Properties/InternalsVisibleTo.cs new file mode 100644 index 0000000000..2fe9c52886 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/InternalsVisibleTo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("ImageResizer.Test")] diff --git a/src/modules/imageresizer/ui/Properties/Resources.Designer.cs b/src/modules/imageresizer/ui/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..500cac4970 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.Designer.cs @@ -0,0 +1,738 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ImageResizer.Properties { + 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", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ImageResizer.Properties.Resources", typeof(Resources).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)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to About. + /// + public static string Advanced_About { + get { + return ResourceManager.GetString("Advanced_About", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Create a new size. + /// + public static string Advanced_CreateSize { + get { + return ResourceManager.GetString("Advanced_CreateSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete. + /// + public static string Advanced_DeleteSize { + get { + return ResourceManager.GetString("Advanced_DeleteSize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Encoding. + /// + public static string Advanced_Encoding { + get { + return ResourceManager.GetString("Advanced_Encoding", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Fallback encoder:. + /// + public static string Advanced_FallbackEncoder { + get { + return ResourceManager.GetString("Advanced_FallbackEncoder", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to File. + /// + public static string Advanced_File { + get { + return ResourceManager.GetString("Advanced_File", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Filename:. + /// + public static string Advanced_FileName { + get { + return ResourceManager.GetString("Advanced_FileName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Original filename. + /// + public static string Advanced_FileNameToken1 { + get { + return ResourceManager.GetString("Advanced_FileNameToken1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Size name. + /// + public static string Advanced_FileNameToken2 { + get { + return ResourceManager.GetString("Advanced_FileNameToken2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Selected width. + /// + public static string Advanced_FileNameToken3 { + get { + return ResourceManager.GetString("Advanced_FileNameToken3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Selected height. + /// + public static string Advanced_FileNameToken4 { + get { + return ResourceManager.GetString("Advanced_FileNameToken4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Actual width (pixels). + /// + public static string Advanced_FileNameToken5 { + get { + return ResourceManager.GetString("Advanced_FileNameToken5", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Actual height (pixels). + /// + public static string Advanced_FileNameToken6 { + get { + return ResourceManager.GetString("Advanced_FileNameToken6", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The following parameters can be used.. + /// + public static string Advanced_FileNameTokens { + get { + return ResourceManager.GetString("Advanced_FileNameTokens", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _JPEG quality level:. + /// + public static string Advanced_JpegQualityLevel { + get { + return ResourceManager.GetString("Advanced_JpegQualityLevel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Use original date modified. + /// + public static string Advanced_KeepDateModified { + get { + return ResourceManager.GetString("Advanced_KeepDateModified", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _PNG interlacing:. + /// + public static string Advanced_PngInterlaceOption { + get { + return ResourceManager.GetString("Advanced_PngInterlaceOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sizes. + /// + public static string Advanced_Sizes { + get { + return ResourceManager.GetString("Advanced_Sizes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _TIFF compression:. + /// + public static string Advanced_TiffCompressOption { + get { + return ResourceManager.GetString("Advanced_TiffCompressOption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Advanced Options. + /// + public static string Advanced_Title { + get { + return ResourceManager.GetString("Advanced_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All Files. + /// + public static string AllFilesFilter { + get { + return ResourceManager.GetString("AllFilesFilter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string Cancel { + get { + return ResourceManager.GetString("Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to © 2019 Brice Lambson. All rights reserved.. + /// + public static string Copyright { + get { + return ResourceManager.GetString("Copyright", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Image Resizer for Windows. + /// + public static string ImageResizer { + get { + return ResourceManager.GetString("ImageResizer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (auto). + /// + public static string Input_Auto { + get { + return ResourceManager.GetString("Input_Auto", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Select a size.. + /// + public static string Input_Content { + get { + return ResourceManager.GetString("Input_Content", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Custom. + /// + public static string Input_Custom { + get { + return ResourceManager.GetString("Input_Custom", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ign_ore the orientation of pictures. + /// + public static string Input_IgnoreOrientation { + get { + return ResourceManager.GetString("Input_IgnoreOrientation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Resize your pictures. + /// + public static string Input_MainInstruction { + get { + return ResourceManager.GetString("Input_MainInstruction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R_esize the original pictures (don't create copies). + /// + public static string Input_Replace { + get { + return ResourceManager.GetString("Input_Replace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Resize. + /// + public static string Input_Resize { + get { + return ResourceManager.GetString("Input_Resize", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Advanced options.... + /// + public static string Input_ShowAdvanced { + get { + return ResourceManager.GetString("Input_ShowAdvanced", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Make pictures smaller but not larger. + /// + public static string Input_ShrinkOnly { + get { + return ResourceManager.GetString("Input_ShrinkOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Large. + /// + public static string Large { + get { + return ResourceManager.GetString("Large", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Medium. + /// + public static string Medium { + get { + return ResourceManager.GetString("Medium", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to OK. + /// + public static string OK { + get { + return ResourceManager.GetString("OK", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Phone. + /// + public static string Phone { + get { + return ResourceManager.GetString("Phone", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All Picture Files. + /// + public static string PictureFilter { + get { + return ResourceManager.GetString("PictureFilter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (Default). + /// + public static string PngInterlaceOption_Default { + get { + return ResourceManager.GetString("PngInterlaceOption_Default", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Off. + /// + public static string PngInterlaceOption_Off { + get { + return ResourceManager.GetString("PngInterlaceOption_Off", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to On. + /// + public static string PngInterlaceOption_On { + get { + return ResourceManager.GetString("PngInterlaceOption_On", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Resizing your pictures.... + /// + public static string Progress_MainInstruction { + get { + return ResourceManager.GetString("Progress_MainInstruction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to _Stop. + /// + public static string Progress_Stop { + get { + return ResourceManager.GetString("Progress_Stop", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {0} hour, {1} minute remaining.. + /// + public static string Progress_TimeRemaining_HourMinute { + get { + return ResourceManager.GetString("Progress_TimeRemaining_HourMinute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {0} hour, {1} minutes remaining.. + /// + public static string Progress_TimeRemaining_HourMinutes { + get { + return ResourceManager.GetString("Progress_TimeRemaining_HourMinutes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {0} hours, {1} minute remaining.. + /// + public static string Progress_TimeRemaining_HoursMinute { + get { + return ResourceManager.GetString("Progress_TimeRemaining_HoursMinute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {0} hours, {1} minutes remaining.. + /// + public static string Progress_TimeRemaining_HoursMinutes { + get { + return ResourceManager.GetString("Progress_TimeRemaining_HoursMinutes", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {1} minute, {2} second remaining.. + /// + public static string Progress_TimeRemaining_MinuteSecond { + get { + return ResourceManager.GetString("Progress_TimeRemaining_MinuteSecond", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {1} minute, {2} seconds remaining.. + /// + public static string Progress_TimeRemaining_MinuteSeconds { + get { + return ResourceManager.GetString("Progress_TimeRemaining_MinuteSeconds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {1} minutes, {2} second remaining.. + /// + public static string Progress_TimeRemaining_MinutesSecond { + get { + return ResourceManager.GetString("Progress_TimeRemaining_MinutesSecond", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {1} minutes, {2} seconds remaining.. + /// + public static string Progress_TimeRemaining_MinutesSeconds { + get { + return ResourceManager.GetString("Progress_TimeRemaining_MinutesSeconds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {2} second remaining.. + /// + public static string Progress_TimeRemaining_Second { + get { + return ResourceManager.GetString("Progress_TimeRemaining_Second", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to About {2} seconds remaining.. + /// + public static string Progress_TimeRemaining_Seconds { + get { + return ResourceManager.GetString("Progress_TimeRemaining_Seconds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fill. + /// + public static string ResizeFit_Fill { + get { + return ResourceManager.GetString("ResizeFit_Fill", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to fills. + /// + public static string ResizeFit_Fill_ThirdPersonSingular { + get { + return ResourceManager.GetString("ResizeFit_Fill_ThirdPersonSingular", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fit. + /// + public static string ResizeFit_Fit { + get { + return ResourceManager.GetString("ResizeFit_Fit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to fits within. + /// + public static string ResizeFit_Fit_ThirdPersonSingular { + get { + return ResourceManager.GetString("ResizeFit_Fit_ThirdPersonSingular", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stretch. + /// + public static string ResizeFit_Stretch { + get { + return ResourceManager.GetString("ResizeFit_Stretch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to stretches to. + /// + public static string ResizeFit_Stretch_ThirdPersonSingular { + get { + return ResourceManager.GetString("ResizeFit_Stretch_ThirdPersonSingular", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Centimeters. + /// + public static string ResizeUnit_Centimeter { + get { + return ResourceManager.GetString("ResizeUnit_Centimeter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inches. + /// + public static string ResizeUnit_Inch { + get { + return ResourceManager.GetString("ResizeUnit_Inch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Percent. + /// + public static string ResizeUnit_Percent { + get { + return ResourceManager.GetString("ResizeUnit_Percent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pixels. + /// + public static string ResizeUnit_Pixel { + get { + return ResourceManager.GetString("ResizeUnit_Pixel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close. + /// + public static string Results_Close { + get { + return ResourceManager.GetString("Results_Close", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Can't resize the following pictures. + /// + public static string Results_MainInstruction { + get { + return ResourceManager.GetString("Results_MainInstruction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Small. + /// + public static string Small { + get { + return ResourceManager.GetString("Small", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CCITT3. + /// + public static string TiffCompressOption_Ccitt3 { + get { + return ResourceManager.GetString("TiffCompressOption_Ccitt3", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CCITT4. + /// + public static string TiffCompressOption_Ccitt4 { + get { + return ResourceManager.GetString("TiffCompressOption_Ccitt4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to (Default). + /// + public static string TiffCompressOption_Default { + get { + return ResourceManager.GetString("TiffCompressOption_Default", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to LZW. + /// + public static string TiffCompressOption_Lzw { + get { + return ResourceManager.GetString("TiffCompressOption_Lzw", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to None. + /// + public static string TiffCompressOption_None { + get { + return ResourceManager.GetString("TiffCompressOption_None", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to RLE. + /// + public static string TiffCompressOption_Rle { + get { + return ResourceManager.GetString("TiffCompressOption_Rle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Zip. + /// + public static string TiffCompressOption_Zip { + get { + return ResourceManager.GetString("TiffCompressOption_Zip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Value must be between '{0}' and '{1}'.. + /// + public static string ValueMustBeBetween { + get { + return ResourceManager.GetString("ValueMustBeBetween", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Version. + /// + public static string Version { + get { + return ResourceManager.GetString("Version", resourceCulture); + } + } + } +} diff --git a/src/modules/imageresizer/ui/Properties/Resources.ar.resx b/src/modules/imageresizer/ui/Properties/Resources.ar.resx new file mode 100644 index 0000000000..f4e50bce69 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.ar.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + حول + + + إنشاء حجم جديد + + + حذف + + + الترميز + + + _Fallback encoder: + + + ملف + + + الإسم + + + اسم الملف الأصلي + + + اسم هذا الحجم + + + العرض المحدد + + + الارتفاع المحدد + + + العرض الأصلي (بكسل) + + + الارتفاع الأصلي (بكسل) + + + البارمترات التالية يمكن استخدامها. + + + مدى جودة صورة JPEG: + + + استخدام تاريخ التعديل الأصلي + + + تداخل PNG: + + + الأحجام + + + مستوى ضغط TIFF + + + إعدادات متقدمة + + + كل الملفات + + + إلغاء + + + © كل الحقوق محفوظة ل Brice Lambson 2019 + + + المقياس - ويندوز + + + (تلقائي) + + + حدد المقاس + + + تخصيص + + + تجاهل اتجاه الصور + + + تغيير مقاس صورك + + + تغيير الصورة الأصلية (لا تنشئ نسخاً) + + + إعادة تحجيم + + + خيارات متقدمة + + + غير مقاس الصورة بحيث يتم تصغيرها لا تكبيرها + + + كبير + + + متوسط + + + موافق + + + الجوال + + + كل ملفات الصور + + + (افتراضي) + + + Off + + + On + + + جاري إعادة تحجيم صورك + + + أيقاف + + + باقي حوالي {0} ساعة و {1} دقيقة. + + + باقي حوالي {0} ساعة و {1} دقائق. + + + باقي حوالي {0} ساعات و {1} دقيقة. + + + باقي حوالي {0} ساعات و {1} دقائق. + + + باقي حوالي {1} دقيقة و {2} ثانية. + + + باقي حوالي {1} دقيقة و {2} ثواني. + + + باقي حوالي {1} دقائق و {2} ثواني. + + + باقي حوالي {1} دقائق و {2} ثواني. + + + باقي حوالي {2} ثانية. + + + باقي حوالي {2} ثواني. + + + تعبئة + + + تعبئة + + + ملائمة + + + ملائمة داخل + + + تمديد + + + تمديد إلى + + + سم + + + إنش + + + بالمئة + + + بكسل + + + إغلاق + + + لا يمكن إعادة تحجيم كل من الصور التالية + + + صغير + + + CCITT3 + + + CCITT4 + + + (إفتراضي) + + + LZW + + + لا شيئ + + + RLE + + + ZIP + + + يجب أن تكون القيمة بين "{0}" و "{1}". + + + الإصدار + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.bg.resx b/src/modules/imageresizer/ui/Properties/Resources.bg.resx new file mode 100644 index 0000000000..043b16f224 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.bg.resx @@ -0,0 +1,348 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Относно + + + Създай нов размер + + + Изтрий + + + Енкодинг + + + _Fallback encoder: + + + Фаил + + + _Име на файл: + + + Оригинлно име на файл + + + Размер + + + Избрана ширина + + + Избрана височина + + + Реална ширина (пиксели) + + + Реална височина (пиксели) + + + Следните параметри могат да бъдат избрани. + + + _Ниво на качество JPEG: + + + + _Използвай оригиналната дата на промяна + + + _PNG interlacing: + + + Размери + + + + _TIFF компресиране: + + + Допълнителни настройки + + + Всички файлове + + + Отказ + + + © 2019 Brice Lambson. All rights reserved. + + + Преоразмерител на картини за Windows + + + (авто) + + + Избери размер + + + По избор + + + Пренебрегване на ориентацията + + + Преоразмери картините + + + Преоразмери оригиналните картини (не създава копия) + + + Преоразмеряване + + + Допълнителни настройки + + + Направи картините малки но не по-големи + + + Голям + + + Среден + + + OK + + + Телефон + + + Всички картинни файлове + + + (По подразбиране) + + + Изкл. + + + Вкл. + + + Преоразмеряване на Вашите картини... + + + _Стоп + + + +Остават около {0} час, {1} минута. + + + Остават около {0} час, {1} минути. + + + Остават около {0} часа, {1} минута. + + + Остават около {0} часа, {1} минути. + + + Остават около {1} минута, {2} секунда. + + + Остават около {1} минута, {2} секунди. + + + Остават около {1} минути, {2} секунда. + + + Остават около {1} минути, {2} секунди. + + + Остават около {2} секунда. + + + Остават около {2} секунди. + + + Запълни + + + Запълване + + + Побиране + + + Побиране в + + + Разпъване + + + разпъване до + + + Сантиметри + + + Инчове + + + Проценти + + + Пиксели + + + Затваряне + + + Следните картини не могат да се преоразмерят + + + Малък + + + CCITT3 + + + CCITT4 + + + (По подразбиране) + + + LZW + + + нищо + + + RLE + + + Zip + + + Стойността трябва да бъде между '{0}' и '{1}'. + + + Версия + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.ca.resx b/src/modules/imageresizer/ui/Properties/Resources.ca.resx new file mode 100644 index 0000000000..e4215aafc1 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.ca.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Quant a + + + Crea una mida nova + + + Esborra + + + Codificació + + + _Codificador de fons: + + + Fitxer + + + _Nom del fitxer: + + + Nom de fitxer original + + + Nom de la mida + + + Amplada seleccionada + + + Alçada seleccionada + + + Amplada actual (píxels) + + + Alçada actual (píxels) + + + Podeu utilitzar els paràmetres següents. + + + Nivell de qualitat _JPEG: + + + _Utilitza la data original modificada + + + _PNG: entrellaçat: + + + Mides + + + Compressió _TIFF: + + + Opcions avançades + + + Tots els fitxers + + + Cancel·la + + + © 2019 Brice Lambson. Tots els drets reservats. + + + Image Resizer per a Windows + + + (Auto) + + + _Seleccioneu una mida. + + + Personalitzat + + + Ign_oreu l'orientació de les imatges + + + Canvia la mida de les imatges + + + Canvia la _mida dels originals (sense copiar-los) + + + _Canvia la mida + + + Opcions avançades ... + + + _Imatges més petites però no més grans + + + Gran + + + Mitjà + + + D'acord + + + Telèfon + + + Tots els fitxers d'imatges + + + (Per defecte) + + + Apaga + + + Engega + + + Canviant la mida de les imatges ... + + + _Atura + + + Manca al voltant de {0} hora, {1} minuts. + + + Manca al voltant de {0} hora, {1} minuts. + + + Manca al voltant de {0} hora, {1} minuts. + + + Manca al voltant de {0} hora, {1} minuts. + + + Manca al voltant de {1} minuts, {2} segons. + + + Manca al voltant de {1} minuts, {2} segons. + + + Manca al voltant de {1} minuts, {2} segons. + + + Manca al voltant de {1} minuts, {2} segons. + + + Manca al voltant de {2} segons. + + + Manca al voltant de {2} segons. + + + Omple + + + farcits + + + Encaixa + + + encaixa dins + + + Ajusta + + + ajusta a + + + Centímetres + + + Polzades + + + Per cent + + + Píxels + + + Tanca + + + No es pot canviar la mida de les imatges següents + + + Petit + + + CCITT3 + + + CCITT4 + + + (Per defecte) + + + LZW + + + Cap + + + RLE + + + Zip + + + El valor ha de ser entre '{0}' i '{1}'. + + + Versió + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.cs.resx b/src/modules/imageresizer/ui/Properties/Resources.cs.resx new file mode 100644 index 0000000000..7b7f963e1d --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.cs.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + O programu + + + Vytvořit novou velikost + + + Smazat + + + Kódování + + + Záložní encoder: + + + Soubor + + + Název souboru: + + + Původní název souboru + + + Název velikosti + + + Vyberte šířku + + + Vyberte výšku + + + Aktuální šířka (v pixelech) + + + Aktuální výška (v pixelech) + + + Použity mohou být následující parametry. + + + Kvalita JPEG: + + + Použít původní datum změny + + + Prokládání PNG: + + + Velikosti + + + TIFF komprese: + + + Rozšířená nastavení + + + Všechny soubory + + + Zrušit + + + © 2019 Brice Lambson. Všechna práva vyhrazena + + + Image Resizer pro Windows + + + (automaticky) + + + Vyberte soubor + + + Vlastní + + + Ignorovat _orientaci obrázků + + + Změnit velikost obrázků + + + Změnit velikost původních obrázků (nevytvářet kopie) + + + Změnit velikost + + + Rozšířená nastavení... + + + Obráz_ky pouze zmenšovat + + + Velké + + + Střední + + + OK + + + Mobilní + + + Všechny soubory obrázků + + + (Výchozí) + + + Vyp. + + + Zap. + + + Probíhá změna velikosti obrázků... + + + Zastavit + + + Zbývá přibližně {0} hodin, {1} minut. + + + Zbývá přibližně {0} hodin, {1} minut. + + + Zbývá přibližně {0} hodin, {1} minut. + + + Zbývá přibližně {0} hodin, {1} minut. + + + Zbývá přibližně {1} minut, {2} vteřin. + + + Zbývá přibližně {1} minut, {2} vteřin. + + + Zbývá přibližně {1} minut, {2} vteřin. + + + Zbývá přibližně {1} minut, {2} vteřin. + + + Zbývá přibližně {2} vteřin. + + + Zbývá přibližně {2} vteřin. + + + Vyplnit + + + vyplňuje + + + Napasovat + + + pasuje do + + + Roztažení + + + roztáhnout na + + + centimetrů + + + palců + + + procent + + + pixelů + + + Zavřít + + + Velikost následujících obrázků nelze změnit + + + Malé + + + CCITT3 + + + CCITT4 + + + (Výchozí) + + + LZW + + + Nic + + + RLE + + + Zip + + + Hodnota musí být mezi {0} a {1}. + + + Verze + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.de.resx b/src/modules/imageresizer/ui/Properties/Resources.de.resx new file mode 100644 index 0000000000..3dea8eedc9 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.de.resx @@ -0,0 +1,346 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Über + + + Neue Größe erstellen + + + Entfernen + + + Komprimierung + + + Ausweich-Komprimierung: + + + Datei + + + Dateiname: + + + Ursprungsdateiname + + + Größen-Name + + + Ausgewählte Breite + + + Ausgewählte Höhe + + + Ursprüngliche Breite (Pixel) + + + Ursprüngliche Höhe (Pixel) + + + Die folgenden Parameter können verwendet werden. + + + JPEG-Qualitätsstufe: + + + Ursprüngliches Änderungsdatum verwenden + + + PNG Interlacing + + + Größen + + + TIFF Komprimierung: + + + Erweiterte Optionen + + + Alle Dateien + + + Abbrechen + + + © 2019 Brice Lambson. Alle Rechte vorbehalten. + + + Image Resizer für Windows + + + (auto) + + + Wähle eine Größe + + + Benutzerdefiniert + + + Ignoriere die _Ausrichtung von Bildern + + + Skaliere deine Bilder + + + Skaliere die Originalbilder (erstellt keine Kopien) + + + Größe ände_rn + + + Erweiterte Optionen... + + + Mache Bilder kleiner aber nicht größer + + + Groß + + + Mittel + + + OK + + + Handy + + + Alle Bilddateien + + + (Standard) + + + Aus + + + An + + + Bilder werden skaliert... + + + Stop + + + Etwa {0} Stunde, {1} Minute verbleibend. + + + Etwa {0} Stunde, {1} Minuten verbleibend. + + + Etwa {0} Stunden, {1} Minute verbleibend. + + + Etwa {0} Stunden, {1} Minuten verbleibend. + + + Etwa {1} Minute, {2} Sekunde verbleibend. + + + Etwa {1} Minute, {2} Sekunden verbleibend. + + + Etwa {1} Minuten, {2} Sekunde verbleibend. + + + Etwa {1} Minuten, {2} Sekunden verbleibend. + + + Etwa {2} Sekunde verbleibend. + + + Etwa {2} Sekunden verbleibend. + + + Füllen + + + füllt + + + passend + + + passt in + + + strecken + + + strecken bis + + + Zentimeter + + + Inch + + + Prozent + + + Pixel + + + Schließen + + + Kann die Größe des folgenden Bildes nicht ändern + + + Klein + + + CCITT3 + + + CCITT4 + + + (Standard) + + + LZW + + + Keiner + + + RLE + + + Zip + + + Wert muss zwischen '{0}' und '{1}' liegen. + + + + Version + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.es.resx b/src/modules/imageresizer/ui/Properties/Resources.es.resx new file mode 100644 index 0000000000..582acc2413 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.es.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Acerca de... + + + Crear nuevo tamaño + + + Eliminar + + + Codificación + + + Codificador de _respaldo + + + Fichero + + + _Nombre de fichero + + + Nombre de fichero original + + + Nombre de tamaño + + + Ancho seleccionado + + + Altura deseada + + + Anchura real (pixeles) + + + Altura actual (píxeles) + + + Los siguientes parámetros están disponibles. + + + Calidad _JPEG: + + + _Usar fecha original modificada + + + Entrelazado _PNG: + + + Tamaños + + + Compresión _TIFF: + + + Opciones avanzadas + + + Todos los ficheros + + + Cancelar + + + © 2019 Brice Lambson. Todos los derechos reservados. + + + Image Resizer para Windows + + + (auto) + + + _Escoge un tamaño. + + + Personalizado + + + Ign_orar la orientación + + + Redimensionar las imagenes + + + R_edimensionar las imágenes originales (sin copia) + + + _Redimensionar + + + Opciones avanzadas... + + + Solo _encoger, no agrandar + + + Grande + + + Mediano + + + Aceptar + + + Teléfono + + + Todas las imágenes + + + (Por defecto) + + + Desactivado + + + Activado + + + Redimensionando imágenes... + + + _Parar + + + Falta {0} hora, {1} minuto + + + Falta {0} hora, {1} minutos + + + Faltan {0} horas, {1} minuto + + + Faltan {0} horas, {1} minutos + + + Falta {1} minuto, {2} segundo + + + Falta {1} minuto, {2} segundos + + + Faltan {1} minutos, {2} segundo + + + Faltan {1} minutos, {2} segundos + + + Falta {2} segundo + + + Faltan {2} segundos + + + Llenar + + + llena + + + Ajustar + + + se ajusta + + + Estirar + + + Estira a + + + Centímetros + + + Pulgadas + + + Porcentaje + + + Píxeles + + + Cerrar + + + No se pueden redimensionar las imágenes + + + Pequeño + + + CCITT3 + + + CCITT4 + + + (Por defecto) + + + LZW + + + Ninguno + + + RLE + + + Zip + + + El valor debe estar entre '{0}' y '{1}'. + + + Versión + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.eu-ES.resx b/src/modules/imageresizer/ui/Properties/Resources.eu-ES.resx new file mode 100644 index 0000000000..84867840df --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.eu-ES.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Honi buruz + + + Tamaina berria sortu + + + Ezabatu + + + Kodifikazioa + + + _Badaezpadako kodifikazioa + + + Fitxategia + + + _Fitxategi izena + + + Jatorrizko fitxategi izena + + + Tamaina izena + + + Aukeratutako zabalera + + + Hautatutako altuera + + + Benetako zabalera (pixelak) + + + Benetako altuera (pixelak) + + + Parametro hauek erabilgarri. + + + _JPEG kalitate maila: + + + Erabili jatorrizko data _aldatuta + + + _PNG interlacing: + + + Taminak + + + _TIFF konpresioa: + + + Aukera Aurreratuak + + + Fitxategi Guztiak + + + Ezeztatu + + + © 2019 Brice Lambson. Eskubide guztiak babestuak. + + + Image Resizer Windowserako + + + (auto) + + + _Aukeratu tamaina. + + + Neurrirakoa + + + Orientazioari ez-ikusi egin + + + Tamainaz aldatu + + + Jatorrizko irudiei tamaina aldatu (kopiarik gabe) + + + _Tamaina aldatu + + + Aukera aurreratuak... + + + _Txikitu irudiak, inoiz ez handitu + + + Handia + + + Ertaina + + + Ados + + + Telefonoa + + + Irudi guztiak + + + (Lehenetsia) + + + Ezgaituta + + + Gaituta + + + Tamainaz aldatzen... + + + _Gelditu + + + Ordu {0}, minutu {1} faltan + + + Ordu {0}, {1} minutu faltan + + + {0} ordu, minutu {1} faltan + + + {0} ordu, {1} minutu faltan. + + + Minutu {1}, segundu {2} faltan. + + + Minutu {1}, {2} segundu faltan. + + + {1} minutu, segundu {2} faltan. + + + {1} minutu, {2} segundu faltan. + + + Segundu {2} faltan. + + + {2} segundu faltan. + + + Bete + + + betetzen du + + + Egokitu + + + egokitzen da + + + Luzatu + + + luzatzen da + + + Zentimetroak + + + Hazbete + + + Ehunekoa + + + Pixelak + + + Itxi + + + Irudiak ezin dira tamainaz aldatu + + + Txikia + + + CCITT3 + + + CCITT4 + + + (Lehenetsia) + + + LZW + + + Bat erez + + + RLE + + + Zip + + + Balioa '{0}' eta '{1}' artean behar du + + + Bertsioa + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.fr.resx b/src/modules/imageresizer/ui/Properties/Resources.fr.resx new file mode 100644 index 0000000000..dd175afbbf --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.fr.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + À propos + + + Créer une nouvelle taille + + + Effacer + + + Encodage + + + Encodeur + + + Fichier + + + Nom de fichier + + + Nom de fichier d'origine + + + Nom de la taille + + + Largeur sélectionnée + + + Hauteur sélectionnée + + + Largeur réelle (pixels) + + + Hauteur réelle (pixels) + + + Les paramètres suivants peuvent être utilisés. + + + JPEG : Qualité + + + Conserver la date de dernière modification d'origine. + + + PNG : Entrelacement + + + Tailles + + + TIFF : Compression + + + Paramètres avancés + + + Tous les Fichiers + + + Annuler + + + © 2019 Brice Lambson. Tous droits réservés. + + + Image Resizer pour Windows + + + (auto) + + + Sélectionner une taille + + + Personnalisé + + + Ignorer l'orientation de l'image + + + Redimensionnez vos images + + + Redimensionner les originaux (ne crée pas de copies) + + + Redimensionner + + + Paramètres avancés + + + Le cas échéant, ne pas agrandir les images. + + + Grand + + + Moyen + + + OK + + + Téléphone + + + Toutes les images + + + (Par défaut) + + + Off + + + On + + + Redimensionnement en cours... + + + Stop + + + Il reste environ {0} heure, {1} minute. + + + Il reste environ {0} heure, {1} minutes. + + + Il reste environ {0} heures, {1} minute. + + + Il reste environ {0} heures, {1} minutes. + + + Il reste environ {1} minute, {2} seconde. + + + Il reste environ {1} minute, {2} secondes. + + + Il reste environ {1} minutes, {2} seconde. + + + Il reste environ {1} minutes, {2} secondes. + + + Il reste environ {2} seconde. + + + Il reste environ {2} secondes. + + + Imposer + + + impose + + + Adapter + + + ratio respecté dans + + + Etirer + + + étirer jusqu'à + + + Centimètres + + + Pouces + + + Pourcent + + + Pixels + + + Fermer + + + Redimensionnement des images impossible + + + Petit + + + CCITT3 + + + CCITT4 + + + (Par défaut) + + + LZW + + + Aucun + + + RLE + + + Zip + + + La valeur doit être entre '{0}' et '{1}'. + + + Version + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.he.resx b/src/modules/imageresizer/ui/Properties/Resources.he.resx new file mode 100644 index 0000000000..da3f69e958 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.he.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + אודות + + + צור גודל חדש + + + מחק + + + קידוד + + + מקודד נסיגה: + + + קובץ + + + שם קובץ: + + + שם קובץ מקורי: + + + שם גודל + + + רוחב נבחר + + + גובה נבחר + + + רוחב אפקטיבי (פיקסלים) + + + גובה אפקטיבי (פיקסלים) + + + הפרמטרים הבאים יכולים להיות בשימוש. + + + רמת איכות JPEG + + + השתמש בתאריך השינוי המקורי + + + תשזורת PNG: + + + גדלים + + + דחיסת TIFF: + + + מתקדם + + + כל הקבצים + + + בטל + + + © 2019 ברייס למבסון. כל הזכויות שמורות. + + + משנה גודל תמונות לווינדוס + + + (אוטומטי) + + + בחר גודל. + + + מותאם אישית + + + התעלם מהאוריאנטציה של התמונות + + + שנה את גודל התמונות שלך + + + שנה את גודל התמונות המקוריות (אל תיצור העתקים) + + + שנה גודל + + + מתקדם + + + הקטן את התמונות, אך אל תגדיל אותן + + + גדול + + + בינוני + + + אישור + + + טלפון + + + כל קבצי התמונות + + + (ברירת מחדל) + + + כבוי + + + מופעל + + + שנה את גודל התמונות... + + + עצור + + + נותרה עוד כ{0} שעה ו{1} דקה. + + + נותרה עוד כ{0} שעה ו{1} דקות. + + + נותרה עוד כ{0} שעות ו{1} דקה. + + + נותרה עוד כ{0} שעות ו{1} דקות. + + + נותרה עוד כ{1} דקה ו{2} שניה. + + + נותרה עוד כ{1} דקה ו{2} שניות. + + + נותרה עוד כ{1} דקות ו{2} שניה. + + + נותרה עוד כ{1} דקות ו{2} שניות. + + + נותרה עוד כ{2} שניה. + + + נותרה עוד כ{2} שניות. + + + התאם + + + מתאים + + + התאם + + + מתאים בתוך + + + מתח + + + מתח ל + + + סנטימטרים + + + אינצ'ים + + + אחוז + + + פיקסלים + + + סגור + + + לא ניתן לשנות את גודל התמונות הבאות + + + קטן + + + CCITT3 + + + CCITT4 + + + (ברירת מחדל) + + + LZW + + + כלום + + + RLE + + + Zip + + + הערך חייב להיות בין '{0}' ו-'{1}'. + + + גרסא + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.hu.resx b/src/modules/imageresizer/ui/Properties/Resources.hu.resx new file mode 100644 index 0000000000..711a60f8c3 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.hu.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Névjegy + + + Új méret készítése + + + Törlés + + + Kódolás + + + Tartalék kódolás: + + + Fájl + + + Fájlnév: + + + Eredeti fájlnév + + + Méret név + + + Kiválasztott szélesség + + + Kiválasztott magasság + + + Jelenlegi szélesség (pixelben) + + + Jelenlegi magasság (pixelben) + + + A következő paraméterek használhatók. + + + JPEG minőség szint: + + + Eredeti módosítási dátum megtartása. + + + PNG váltósorosság: + + + Méretek + + + TIFF tömörítés: + + + Haladó beállítások + + + Minden fájl + + + Mégse + + + © 2019 Brice Lambson. Minden jog fenntartva. + + + Képméretező Windowsra + + + (auto) + + + Válassz méretet. + + + Egyedi + + + Képek tájolásának figyelmen kívül hagyása + + + Képek átméretezése + + + Eredeti képek átméretezése (nem készül másolat) + + + Átméretezés + + + Haladó beállítások... + + + Képek kicsinyítése, de nem nagyítása + + + Nagy + + + Közepes + + + Rendben + + + Telefon + + + Minden kép fájl + + + (Alapértelmezett) + + + Ki + + + Be + + + Képek átméretezése... + + + Állj + + + Körülbelül {0} óra {1} perc van hátra. + + + Körülbelül {0} óra {1} perc van hátra. + + + Körülbelül {0} óra {1} perc van hátra. + + + Körülbelül {0} óra {1} perc van hátra. + + + Körülbelül {1} perc {2} másodperc van hátra. + + + Körülbelül {1} perc {2} másodperc van hátra. + + + Körülbelül {1} perc {2} másodperc van hátra. + + + Körülbelül {1} perc {2} másodperc van hátra. + + + Körülbelül {2} másodperc van hátra. + + + Körülbelül {2} másodperc van hátra. + + + Kitöltés + + + kitöltés + + + Illesztés + + + illesztés + + + Nyújtás + + + nyújtás + + + Centiméterre + + + Hüvelykre + + + Százalékra + + + Pixelre + + + Bezár + + + A következő képeket nem sikerült átméretezni + + + Kicsi + + + CCITT3 + + + CCITT4 + + + (Alapértelmezett) + + + LZW + + + Nincs + + + RLE + + + Zip + + + Az értéknek '{0}' és '{1}' között kell lennie. + + + Verzió + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.it.resx b/src/modules/imageresizer/ui/Properties/Resources.it.resx new file mode 100644 index 0000000000..6d022d2005 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.it.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Informazioni su... + + + Crea una nuova dimensione + + + Elimina + + + Codifica + + + _Codificatore Fallback: + + + File + + + _Nome file: + + + Nome file originale + + + Nome dimensione + + + Larghezza selezionata + + + Altezza selezionata + + + Larghezza attuale (pixel) + + + Altezza attuale (pixel) + + + Possono essere usati i seguenti parametri. + + + _Qualità JPEG: + + + _Usa la data originale modificata + + + _Interlacciamento PNG: + + + Dimensioni + + + _Compressione TIFF: + + + Opzioni avanzate + + + Tutti i file + + + Annulla + + + © 2019 Brice Lambson. Tutti i diritti riservati. + + + Image Resizer per Windows + + + (auto) + + + _Scegli una dimensione. + + + A scelta + + + Ignora l'orientamento delle immagini + + + Ridimensiona le tue immagini + + + Ridimensiona le immagini originali (non creare copie) + + + Ridimensiona + + + Opzioni avanzate... + + + _Crea immagini più piccole ma non più grandi + + + Grande + + + Media + + + OK + + + Telefono + + + Tutti i file immagine + + + (Predefinito) + + + Off + + + On + + + Sto ridimensionando le immagini... + + + Stop + + + Circa {0} ora, {1} minuto rimanenti. + + + Circa {0} ora, {1} minuti rimanenti. + + + Circa {0} ore, {1} minuto rimanenti. + + + Circa {0} ore, {1} minuti rimanenti. + + + Circa {1} minuto, {2} secondo rimanenti. + + + Circa {1} minuto, {2} secondi rimanenti. + + + Circa {1} minuti, {2} secondo rimanenti. + + + Circa {1} minuti, {2} secondi rimanenti. + + + Circa {2} secondo rimanente. + + + Circa {2} secondi rimanenti. + + + Riempi + + + riempie + + + Adatta + + + entro + + + Deforma + + + deforma + + + Centimetri + + + Pollici + + + Percento + + + Pixel + + + Chiudi + + + Impossibile ridimensionare le seguenti immagini + + + Piccola + + + CCITT3 + + + CCITT4 + + + (Predefinito) + + + LZW + + + Nessuno + + + RLE + + + Zip + + + Il valore deve essere compreso tra '{0}' e '{1}'. + + + Versione + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.nb-NO.resx b/src/modules/imageresizer/ui/Properties/Resources.nb-NO.resx new file mode 100644 index 0000000000..954e83f023 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.nb-NO.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Om + + + Lag ny størrelse + + + Slett + + + Koding + + + _Fallback encoder: + + + Fil + + + _Filnavn: + + + Originalt filnavn + + + Navn på størrelse + + + Valgte bredde + + + Valgte høyde + + + Faktisk bredde (piksler) + + + Faktisk høyde (piksler) + + + Følgende parametre kan brukes. + + + _JPEG kvalitetsnivå + + + _Bruk original endringsdato + + + _PNG interlasering: + + + Størrelser + + + _TIFF komprimering + + + Avanserte alternativer + + + Alle filer + + + Avbryt + + + © 2019 Brice Lambson. Alle rettigheter er reservert + + + Image Resizer for Windows + + + (auto) + + + _Velg en størrelse. + + + Tilpasset + + + Ign_orer bildes retning + + + Endre bildestørrelse + + + Endr_e originalbildet (ikke lag kopi) + + + _Endre størrelse + + + Avanserte alternativer... + + + _Lag bildene mindre, men ikke større + + + Stor + + + Medium + + + OK + + + Telefon + + + Alle bildefiler + + + (Standard) + + + Av + + + + + + Endrer størrelse på bildene... + + + _Stopp + + + Omtrent {0} time, {1} minutt gjenstår. + + + Omtrent {0} time, {1} minutter gjenstår. + + + Omtrent {0} timer, {1} minutt gjenstår. + + + Omtrent {0} timer, {1} minutter gjenstår. + + + Omtrent {0} minutt, {1} sekund gjenstår. + + + Omtrent {0} minutt, {1} sekunder gjenstår. + + + Omtrent {0} minutter, {1} sekund gjenstår. + + + Omtrent {0} minutter, {1} sekunder gjenstår. + + + Omtrent {2} sekund gjenstår. + + + Omtrent {2} sekunder gjenstår. + + + Fyll + + + fyller + + + Passer + + + passer innenfor + + + Stekk + + + strekkes til + + + Centimeter + + + Tommer + + + Prosent + + + Piksler + + + Lukk + + + Kan ikke endre størrelse på følgende bilder + + + Liten + + + CCIT3 + + + CCIT4 + + + (Standard) + + + LZW + + + Ingen + + + RLE + + + Zip + + + Verdi må være mellom '{0}' og '{1}'. + + + Versjon + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.nl.resx b/src/modules/imageresizer/ui/Properties/Resources.nl.resx new file mode 100644 index 0000000000..b7b042a1c3 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.nl.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Over + + + Nieuw afbeeldingsformaat invoeren + + + Wissen + + + Compressie + + + _Dodge compressie: + + + Bestand + + + _Bestandsnaam: + + + Originele bestandsnaam + + + Naam van het formaat + + + Geselecteerde breedte + + + Geselecteerde hoogte + + + Werkelijke breedte (pixels) + + + Werkelijke hoogte (pixels) + + + De volgende parameters kunnen worden gebruikt: + + + _JPEG kwaliteitsniveau: + + + _Gebruik de originele wijzigingsdatum + + + _PNG interliniëring: + + + Formaten + + + _TIFF compressie: + + + Geavanceerde instellingen + + + Alle bestanden + + + Annuleren + + + © 2019 Brice Lambson. Alle rechten voorbehouden. + + + Fotovergroter/verkleiner voor Windows + + + (auto) + + + _Selecteer een afmeting. + + + Aangepast + + + Negeer de _oriëntering van de afbeelding + + + Afbeeldingsformaat wijzigen + + + Wijzig de originele _afbeeldingen (maak geen kopieën) + + + _Verkleinen/vergroten + + + Geavanceerde instellingen + + + Verklein afbeeldingen, vergroot niet + + + Groot + + + Middel + + + OK + + + Miniatuur + + + Alle afbeeldingsbestanden + + + (Standaard) + + + Uit + + + Aan + + + Verklein/vergroot foto's... + + + _Stop + + + Ongeveer {0} uur en {1} minuut resterend. + + + Ongeveer {0} uur en {1} minuten resterend. + + + Ongeveer {0} uren en {1} minuut resterend. + + + Ongeveer {0} uren en {1} minuten resterend. + + + Ongeveer {1} minuut en {2} seconde resterend. + + + Ongeveer {1} minuut en {2} seconden resterend. + + + Ongeveer {1} minuten en {2} seconde resterend. + + + Ongeveer {1} minuten en {2} seconden resterend. + + + Ongeveer {2} seconde resterend. + + + Ongeveer {2} seconden resterend. + + + Uitvullen + + + vult uit + + + Passend + + + past binnen + + + Uitrekken + + + rekt uit naar + + + Centimeters + + + Duimen + + + Procent + + + Pixels + + + Sluiten + + + De volgende afbeeldingen kunnen niet worden gewijzigd + + + Klein + + + CCITT3 + + + CCITT4 + + + (Standaard) + + + LZW + + + Geen + + + RLE + + + Zip + + + Waarde moet liggen tussen '{0}' en '{1}'. + + + Versie + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.pl.resx b/src/modules/imageresizer/ui/Properties/Resources.pl.resx new file mode 100644 index 0000000000..157542ecd9 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.pl.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + O programie + + + Utwórz nowy rozmiar + + + Usuń + + + Kodowanie + + + Usterka kodera: + + + Plik + + + Nazwa pliku: + + + Oryginalna nazwa pliku + + + Nazwa rozmiaru + + + Wybierz szerokość + + + Wybierz wysokość + + + Obecna szerokość (w pikselach) + + + Obecna wysokość (w pikselach) + + + Następujące parametry mogą zostać użyte. + + + Stopień jakości JPG + + + Użyj oryginalnej daty modyfikacji + + + Przeplot PNG: + + + Rozmiary + + + Kompresja TIFF: + + + Ustawienia zaawansowane + + + Wszystkie pliki + + + Anuluj + + + © 2019 Brice Lambson. Wszystkie prawa zastrzeżone. + + + Image Resizer dla Windows + + + (auto) + + + Wybierz rozmiar + + + Niestandardowy + + + Ignoruj orientację + + + Zmień rozmiar obrazów + + + Zmień rozmiar obrazów (nie twórz kopii) + + + Zmień rozmiar + + + Ustawienia zaawansowane... + + + Tylko zmniejsz rozmiar, nie powiększaj + + + Duży + + + Średni + + + OK + + + Telefon + + + Wszystkie obrazy + + + (Domyślne) + + + Wył. + + + Wł. + + + Zmieniam rozmiar zdjęć... + + + Zatrzymaj + + + Pozostało godzin {0}, minut {1} + + + Pozostało godzin {0}, minut {1} + + + Pozostało godzin {0}, minut {1} + + + Pozostało godzin {0}, minut {1} + + + Pozostało minut {1}, sekund {2} + + + Pozostało minut {1}, sekund {2} + + + Pozostało minut {1}, sekund {2} + + + Pozostało minut {1}, sekund {2} + + + Pozostało sekund {2} + + + Pozostało sekund {2} + + + Wypełnij + + + wypełnia + + + Dopasuj + + + Dopasuj do + + + Rozciągnij + + + Rozciągnij do + + + Centymetrów + + + Cali + + + Procent + + + Pikseli + + + Zamknij + + + Nie można zmienić rozmiaru zdjęć + + + Mały + + + CCITT3 + + + CCITT4 + + + (Domyślna) + + + LZW + + + Brak + + + RLE + + + Zip + + + Wartość musi być pomiędzy '{0}' i '{1}'. + + + Wersja + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.pt-BR.resx b/src/modules/imageresizer/ui/Properties/Resources.pt-BR.resx new file mode 100644 index 0000000000..97f830e365 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.pt-BR.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Sobre + + + Criar novo tamanho + + + Apagar + + + Codificação + + + _Fallback encoder: + + + Arquivo + + + Nome do arquivo: + + + Nome original + + + Nome do tamanho + + + Largura selecionada + + + Altura selecionada + + + Largura atual (pixels) + + + Altura atual (pixels) + + + O seguintes parâmetros podem ser utilizados. + + + _JPEG nível de qualidade: + + + _Utilizar data de modificação original + + + Entrelaçamento _PNG: + + + Tamanhos + + + Compressão _TIFF: + + + Opções Avançadas + + + Todos os arquivos + + + Cancelar + + + © 2019 Brice Lambson. Todos os direitos reservados. + + + Redimensionar Imagens para Windows + + + (auto) + + + _Selecione um tamanho. + + + Personalizado + + + Ign_orar a orientação das imagens + + + Redimensione suas imagens + + + R_edimensionar imagens originais (não criar cópias) + + + _Redimensionar + + + Opções Avançadas... + + + Reduzir i_magens mas não aumentar + + + Grande + + + Médio + + + OK + + + Telefone + + + Todos os arquivos de Imagens + + + (Padrão) + + + Desligado + + + Ligado + + + Redimensionar suas imagens... + + + Parar + + + Aproximadamente {0} hora, {1} minuto restante. + + + Aproximadamente {0} hora, {1} minutos restantes. + + + Aproximadamente {0} horas, {1} minuto restante. + + + Aproximadamente {0} horas, {1} minutos restantes. + + + Aproximadamente {1} minuto, {2} segundo restante. + + + Aproximadamente {1} minuto, {2} segundos restantes. + + + Aproximadamente {1} minutos, {2} segundo restante. + + + Aproximadamente {1} minutos, {2} segundos restantes. + + + Aproximadamente {2} segundo restante. + + + Aproximadamente {2} segundos restantes. + + + Preencher + + + preenche + + + Encaixar + + + encaixar dentro + + + Esticar + + + esticar para + + + Centímetros + + + Polegadas + + + Percentual + + + Pixels + + + Fechar + + + Não posso redimensionar as seguintes imagens + + + Pequeno + + + CCITT3 + + + CCITT4 + + + (Padrão) + + + LZW + + + Nenhum + + + RLE + + + Zip + + + Valor deve ser entre '{0}' e '{1}'. + + + Versão + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.resx b/src/modules/imageresizer/ui/Properties/Resources.resx new file mode 100644 index 0000000000..e0664f82d9 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + About + + + Create a new size + + + Delete + + + Encoding + + + _Fallback encoder: + + + File + + + _Filename: + + + Original filename + + + Size name + + + Selected width + + + Selected height + + + Actual width (pixels) + + + Actual height (pixels) + + + The following parameters can be used. + + + _JPEG quality level: + + + _Use original date modified + + + _PNG interlacing: + + + Sizes + + + _TIFF compression: + + + Settings + + + All Files + + + Cancel + + + © 2019 Brice Lambson. All rights reserved. + + + Image Resizer for Windows + + + (auto) + + + _Select a size. + + + Custom + + + Ign_ore the orientation of pictures + + + Resize your pictures + + + R_esize the original pictures (don't create copies) + + + _Resize + + + Settings + + + _Make pictures smaller but not larger + + + Large + + + Medium + + + OK + + + Phone + + + All Picture Files + + + (Default) + + + Off + + + On + + + Resizing your pictures... + + + _Stop + + + About {0} hour, {1} minute remaining. + + + About {0} hour, {1} minutes remaining. + + + About {0} hours, {1} minute remaining. + + + About {0} hours, {1} minutes remaining. + + + About {1} minute, {2} second remaining. + + + About {1} minute, {2} seconds remaining. + + + About {1} minutes, {2} second remaining. + + + About {1} minutes, {2} seconds remaining. + + + About {2} second remaining. + + + About {2} seconds remaining. + + + Fill + + + fills + + + Fit + + + fits within + + + Stretch + + + stretches to + + + Centimeters + + + Inches + + + Percent + + + Pixels + + + Close + + + Can't resize the following pictures + + + Small + + + CCITT3 + + + CCITT4 + + + (Default) + + + LZW + + + None + + + RLE + + + Zip + + + Value must be between '{0}' and '{1}'. + + + Version + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.ru.resx b/src/modules/imageresizer/ui/Properties/Resources.ru.resx new file mode 100644 index 0000000000..78cbe618c5 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.ru.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + О программе + + + Создать новый размер + + + Удалить + + + Кодирование + + + Резервный кодировщик: + + + Файл + + + Имя файла: + + + Исходное имя файла: + + + Имя размера + + + Выбранная ширина + + + Выбранная высота + + + Текущая ширина (пикселей) + + + Текущая высота (пикселей) + + + Могут использоваться следующие параметры. + + + Качество JPEG: + + + Использовать исходную дату изменения + + + Интерлейсинг PNG: + + + Размеры + + + Сжатие TIFF: + + + Расширенные настройки + + + Все файлы + + + отменить + + + © 2019 Brice Lambson. Все права защищены. + + + Image Resizer для Windows + + + (авто) + + + _Выберите размер. + + + изготовленный на заказ + + + игнорировать ориентацию фото + + + Изменить размер ваших фотографий + + + Изменять оригинальные фотографии (не создавать копии) + + + Изменить + + + Расширенные настройки... + + + Делайте картинки меньше, но не больше + + + большой + + + Средняя + + + Хорошо + + + Телефон + + + Все файлы изображений + + + По умолчанию + + + Отключено + + + Включено + + + Изменение размеров изображений... + + + Остановить + + + Осталось около {0} часов, {1} минут. + + + Осталось около {0} часов, {1} минут. + + + Осталось около {0} часов, {1} минут. + + + Осталось около {0} часов, {1} минут. + + + Осталось около {1} минут, {2} секунд. + + + Осталось около {1} минут, {2} секунд. + + + Осталось около {1} минут, {2} секунд. + + + Осталось около {1} минут, {2} секунд. + + + Осталось около {2} секунд. + + + Осталось около {2} секунд. + + + заполнить + + + заливка + + + Поместиться + + + вписывается в + + + Растянуть + + + растянуто до + + + сантиметров + + + дюймов + + + Процентов + + + Пиксели + + + Закрыть + + + Не удается изменить размер следующих изображений + + + Маленький + + + CCITT3 + + + CCITT4 + + + По умолчанию + + + LZW + + + Нет + + + RLE + + + Zip + + + Значение должно быть от '{0}' до '{1}'. + + + Версия + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.sk.resx b/src/modules/imageresizer/ui/Properties/Resources.sk.resx new file mode 100644 index 0000000000..4949b771ae --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.sk.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + O programe + + + Vytvor novú veľkosť + + + Vymaž + + + Enkódovanie + + + Záložný encoder + + + Súbor + + + Meno súboru: + + + Pôvodné meno súboru + + + Názov veľkosti + + + Vybraná šírka + + + Vybraná výška + + + Skutočná šírka (pixely) + + + Skutočná výška (pixely) + + + Nasledujúce parametre sa môžu použiť + + + JPEG úroveň kvality + + + Použi pôvodný dátum úpravy + + + PNG interlacing + + + Veľkosti + + + TIFF kompresia: + + + Pokročilé Nastavenia + + + Všetky súbory + + + Zrušiť + + + © 2019 Brice Lambson. Všetky práva vyhradené. + + + Image Resizer for Windows + + + (auto) + + + Vyber veľkosť + + + Vlastné + + + Ignoruj otočenie obrázkov + + + Zmeň veľkosť obrázkov + + + Zmeň veľkosť originálov (nevytvoria sa kópie) + + + Zmeň Veľkosť + + + Pokročílé nastavenia... + + + Zmenši obrázky, nikdy nezväčšuj + + + Veľké + + + Stredné + + + OK + + + Telefón + + + Všetky obrázky + + + (Štandardné) + + + Vypnuté + + + Zapnuté + + + Zmeň veľkosť obrázkov... + + + _Stop + + + Ostáva približne {0} hod, {1} min + + + Ostáva približne {0} hod, {1} min + + + Ostáva približne {0} hod, {1} min + + + Ostáva približne {0} hod, {1} min + + + Ostáva približne {1} min, {2} sec + + + Ostáva približne {1} min, {2} sec + + + Ostáva približne {1} min, {2} sec + + + Ostáva približne {1} min, {2} sec + + + Ostáva približne {2} sec + + + Ostáva približne {2} sec + + + Vyplň + + + Vyplňuje + + + Prispôsob + + + Prispôsob do + + + Roztiahnuť + + + Roztiahnuť na + + + Centimetrov + + + Palcov + + + Percent + + + Pixely + + + Zatvor + + + Nemôžem spracovať nasledujúce obrázky + + + Malé + + + CCITT3 + + + CCITT4 + + + (Štandardné) + + + LZW + + + Žiadne + + + RLE + + + Zip + + + Hodnota musí byť medzi '{0}' a '{1}'. + + + Verzia + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.tr.resx b/src/modules/imageresizer/ui/Properties/Resources.tr.resx new file mode 100644 index 0000000000..4c7fb878c8 --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.tr.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Hakkında + + + Yeni bir boyut oluştur + + + Sil + + + Kodlama + + + Temel kodlayıcı: + + + Dosya + + + Dosya adı: + + + Orijinal dosya adı + + + Boyut adı + + + Seçili genişlik + + + Seçili yükseklik + + + Gerçek genişlik (piksel) + + + Gerçek yükseklik (piksel) + + + Aşağıdaki parametreler kullanılabilir. + + + JPEG kalite düzeyi: + + + Orijinal değiştirme tarihini kullan + + + PNG titreşimi: + + + Boyutlar + + + TIFF sıkıştırması: + + + Gelişmiş Seçenekler + + + Tüm Dosyalar + + + İptal + + + © 2019 Brice Lambson. Tüm hakları saklıdır. + + + Windows için Image Resizer + + + (otomatik) + + + Bir boyut seçin. + + + Özel + + + Resimlerin yönünü dikkate alma + + + Resimlerinizi yeniden boyutlandırın + + + Orijinal resimleri yeniden boyutlandır (kopya oluşturmaz) + + + Boyutlandır + + + Gelişmiş seçenekler... + + + Resimleri sadece küçültün, büyütmeyin + + + Büyük + + + Orta + + + Tamam + + + Telefon + + + Tüm Resim Dosyaları + + + (Varsayılan) + + + Kapalı + + + Açık + + + Resimleriniz yeniden boyutlandırılıyor... + + + Durdur + + + Yaklaşık {0} saat, {1} dakika kaldı. + + + Yaklaşık {0} saat, {1} dakika kaldı. + + + Yaklaşık {0} saat, {1} dakika kaldı. + + + Yaklaşık {0} saat, {1} dakika kaldı. + + + Yaklaşık {1} dakika, {2} saniye kaldı. + + + Yaklaşık {1} dakika, {2} saniye kaldı. + + + Yaklaşık {1} dakika, {2} saniye kaldı. + + + Yaklaşık {1} dakika, {2} saniye kaldı. + + + Yaklaşık {2} saniye kaldı. + + + Yaklaşık {2} saniye kaldı. + + + Doldur + + + doldurulacak değer + + + Sığdır + + + sığdırılacak değer + + + Uzat + + + uzatılacak değer + + + Santimetre + + + İnç + + + Yüzde + + + Piksel + + + Kapat + + + Aşağıdaki resimler yeniden boyutlandırılamıyor + + + Küçük + + + CCITT3 + + + CCITT4 + + + (Varsayılan) + + + LZW + + + Yok + + + RLE + + + Zip + + + Değer '{0}' ile '{1}' arasında olmalıdır. + + + Sürüm + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Resources.zh-Hans.resx b/src/modules/imageresizer/ui/Properties/Resources.zh-Hans.resx new file mode 100644 index 0000000000..45dd573fcd --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Resources.zh-Hans.resx @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 关于 + + + 新增尺寸 + + + 删除 + + + 编码中 + + + 退回编码器: + + + 文件 + + + 文件名 + + + 原文件名 + + + 尺寸名 + + + 选定的宽度 + + + 选定的高度 + + + 实际宽度(像素) + + + 实际高度(像素) + + + 以下参数可供使用。 + + + JPEG 图片质量等级: + + + 使用原来的文件修改日期 + + + PNG 格式隔行扫描 + + + 尺寸 + + + TIFF 压缩 + + + 高级选项 + + + 所有文件 + + + 取消 + + + © 2019 Brice Lambson. 保留所有权利。 + + + 重设图片大小(Windows 系统) + + + (自动) + + + 选择尺寸 + + + 定制 + + + 忽略图片方向 + + + 重设图片大小 + + + 缩放原图(不要创建备份) + + + 重设大小 + + + 高级选项 + + + 把图片缩小而不是放大 + + + 大尺寸 + + + 中尺寸 + + + 确定 + + + 手机 + + + 所有图片文件 + + + (默认) + + + 关闭 + + + 打开 + + + 正在重设图片大小…… + + + 停止 + + + 大约剩余 {0} 小时,{1} 分钟。 + + + 大约剩余 {0} 小时,{1} 分钟。 + + + 大约剩余 {0} 小时,{1} 分钟。 + + + 大约剩余 {0} 小时,{1} 分钟。 + + + 大约剩余 {0} 分钟,{1} 秒。 + + + 大约剩余 {0} 分钟,{1} 秒。 + + + 大约剩余 {0} 分钟,{1} 秒。 + + + 大约剩余 {0} 分钟,{1} 秒。 + + + 大约剩余 {0} 秒。 + + + 大约剩余 {0} 秒。 + + + 填充到 + + + 填充 + + + 适配到 + + + 适配于 + + + 拉伸 + + + 拉伸到 + + + 厘米 + + + 英尺 + + + 百分比 + + + 像素 + + + 关闭 + + + 无法重设以下图片大小: + + + 小尺寸 + + + CCITT3 + + + CCITT4 + + + (默认) + + + LZW + + + + + + RLE + + + Zip + + + 数值必须介于 '{0}' 和 '{1}' 之间. + + + 版本 + + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Settings.cs b/src/modules/imageresizer/ui/Properties/Settings.cs new file mode 100644 index 0000000000..6ea44c8c5d --- /dev/null +++ b/src/modules/imageresizer/ui/Properties/Settings.cs @@ -0,0 +1,435 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Threading; +using System.Windows.Media.Imaging; +using ImageResizer.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; + +namespace ImageResizer.Properties +{ + [JsonObject(MemberSerialization.OptIn)] + public partial class Settings : IDataErrorInfo, INotifyPropertyChanged + { + // Used to synchronize access to the settings.json file + private static Mutex _jsonMutex = new Mutex(); + private static string _settingsPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), "Microsoft", "PowerToys", "ImageResizer", "settings.json"); + private string _fileNameFormat; + private bool _shrinkOnly; + private int _selectedSizeIndex; + private bool _replace; + private bool _ignoreOrientation; + private int _jpegQualityLevel; + private PngInterlaceOption _pngInterlaceOption; + private TiffCompressOption _tiffCompressOption; + private string _fileName; + private ObservableCollection _sizes; + private bool _keepDateModified; + private System.Guid _fallbackEncoder; + private CustomSize _customSize; + + public Settings() + { + SelectedSizeIndex = 0; + ShrinkOnly = false; + Replace = false; + IgnoreOrientation = true; + JpegQualityLevel = 90; + PngInterlaceOption = System.Windows.Media.Imaging.PngInterlaceOption.Default; + TiffCompressOption = System.Windows.Media.Imaging.TiffCompressOption.Default; + FileName = "%1 (%2)"; + Sizes = new ObservableCollection + { + new ResizeSize("$small$", ResizeFit.Fit, 854, 480, ResizeUnit.Pixel), + new ResizeSize("$medium$", ResizeFit.Fit, 1366, 768, ResizeUnit.Pixel), + new ResizeSize("$large$", ResizeFit.Fit, 1920, 1080, ResizeUnit.Pixel), + new ResizeSize("$phone$", ResizeFit.Fit, 320, 568, ResizeUnit.Pixel), + }; + KeepDateModified = false; + FallbackEncoder = new System.Guid("19e4a5aa-5662-4fc5-a0c0-1758028e1057"); + CustomSize = new CustomSize(ResizeFit.Fit, 1024, 640, ResizeUnit.Pixel); + AllSizes = new AllSizesCollection(this); + } + + public IEnumerable AllSizes { get; set; } + + public string FileNameFormat + => _fileNameFormat + ?? (_fileNameFormat = FileName + .Replace("{", "{{") + .Replace("}", "}}") + .Replace("%1", "{0}") + .Replace("%2", "{1}") + .Replace("%3", "{2}") + .Replace("%4", "{3}") + .Replace("%5", "{4}") + .Replace("%6", "{5}")); + + public ResizeSize SelectedSize + { + get => SelectedSizeIndex >= 0 && SelectedSizeIndex < Sizes.Count + ? Sizes[SelectedSizeIndex] + : CustomSize; + set + { + var index = Sizes.IndexOf(value); + if (index == -1) + { + index = Sizes.Count; + } + + SelectedSizeIndex = index; + } + } + + string IDataErrorInfo.Error + => string.Empty; + + string IDataErrorInfo.this[string columnName] + { + get + { + if (columnName != nameof(JpegQualityLevel)) + { + return string.Empty; + } + + if (JpegQualityLevel < 1 || JpegQualityLevel > 100) + { + return string.Format(Resources.ValueMustBeBetween, 1, 100); + } + + return string.Empty; + } + } + + private class AllSizesCollection : IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged + { + private ObservableCollection _sizes; + private CustomSize _customSize; + + public AllSizesCollection(Settings settings) + { + _sizes = settings.Sizes; + _customSize = settings.CustomSize; + + _sizes.CollectionChanged += HandleCollectionChanged; + ((INotifyPropertyChanged)_sizes).PropertyChanged += HandlePropertyChanged; + + settings.PropertyChanged += (sender, e) => + { + if (e.PropertyName == nameof(Models.CustomSize)) + { + var oldCustomSize = _customSize; + _customSize = settings.CustomSize; + + OnCollectionChanged( + new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + _customSize, + oldCustomSize, + _sizes.Count)); + } + else if (e.PropertyName == nameof(Sizes)) + { + var oldSizes = _sizes; + + oldSizes.CollectionChanged -= HandleCollectionChanged; + ((INotifyPropertyChanged)oldSizes).PropertyChanged -= HandlePropertyChanged; + + _sizes = settings.Sizes; + + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + + _sizes.CollectionChanged += HandleCollectionChanged; + ((INotifyPropertyChanged)_sizes).PropertyChanged += HandlePropertyChanged; + } + }; + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public int Count + => _sizes.Count + 1; + + public ResizeSize this[int index] + => index == _sizes.Count + ? _customSize + : _sizes[index]; + + public IEnumerator GetEnumerator() + => new AllSizesEnumerator(this); + + private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + => OnCollectionChanged(e); + + private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + => PropertyChanged?.Invoke(this, e); + + private void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + => CollectionChanged?.Invoke(this, e); + + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + private class AllSizesEnumerator : IEnumerator + { + private readonly AllSizesCollection _list; + + private int _index = -1; + + public AllSizesEnumerator(AllSizesCollection list) + => _list = list; + + public ResizeSize Current + => _list[_index]; + + object IEnumerator.Current + => Current; + + public void Dispose() + { + } + + public bool MoveNext() + => ++_index < _list.Count; + + public void Reset() + => _index = -1; + } + } + + private static Settings defaultInstance = new Settings(); + + public static Settings Default + { + get + { + defaultInstance.Reload(); + return defaultInstance; + } + } + + [JsonProperty(PropertyName = "imageresizer_selectedSizeIndex")] + public int SelectedSizeIndex + { + get => _selectedSizeIndex; + set + { + _selectedSizeIndex = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_shrinkOnly")] + public bool ShrinkOnly + { + get => _shrinkOnly; + set + { + _shrinkOnly = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_replace")] + public bool Replace + { + get => _replace; + set + { + _replace = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_ignoreOrientation")] + public bool IgnoreOrientation + { + get => _ignoreOrientation; + set + { + _ignoreOrientation = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_jpegQualityLevel")] + public int JpegQualityLevel + { + get => _jpegQualityLevel; + set + { + _jpegQualityLevel = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_pngInterlaceOption")] + public PngInterlaceOption PngInterlaceOption + { + get => _pngInterlaceOption; + set + { + _pngInterlaceOption = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_tiffCompressOption")] + public TiffCompressOption TiffCompressOption + { + get => _tiffCompressOption; + set + { + _tiffCompressOption = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_fileName")] + public string FileName + { + get => _fileName; + set + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new System.ArgumentNullException(); + } + + _fileName = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_sizes")] + public ObservableCollection Sizes + { + get => _sizes; + set + { + _sizes = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_keepDateModified")] + public bool KeepDateModified + { + get => _keepDateModified; + set + { + _keepDateModified = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_fallbackEncoder")] + public System.Guid FallbackEncoder + { + get => _fallbackEncoder; + set + { + _fallbackEncoder = value; + NotifyPropertyChanged(); + } + } + + [JsonProperty(PropertyName = "imageresizer_customSize")] + public CustomSize CustomSize + { + get => _customSize; + set + { + _customSize = value; + NotifyPropertyChanged(); + } + } + + public static string SettingsPath { get => _settingsPath; set => _settingsPath = value; } + + public event PropertyChangedEventHandler PropertyChanged; + + private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public void Save() + { + _jsonMutex.WaitOne(); + string jsonData = "{\"version\":\"1.0\",\"name\":\"ImageResizer\",\"properties\":"; + string tempJsonData = JsonConvert.SerializeObject(this); + JObject tempSettings = JObject.Parse(tempJsonData); + + // Replace the of the property with { "value": } to be consistent with PowerToys + foreach (var property in tempSettings) + { + tempSettings[property.Key] = new JObject { { "value", property.Value } }; + } + + jsonData += tempSettings.ToString(Formatting.None); + jsonData += "}"; + + // Create directory if it doesn't exist + FileInfo file = new FileInfo(SettingsPath); + file.Directory.Create(); + + // write string to file + File.WriteAllText(SettingsPath, jsonData); + _jsonMutex.ReleaseMutex(); + } + + public void Reload() + { + _jsonMutex.WaitOne(); + if (!File.Exists(SettingsPath)) + { + _jsonMutex.ReleaseMutex(); + Save(); + return; + } + + string jsonData = File.ReadAllText(SettingsPath); + JObject imageResizerSettings = JObject.Parse(jsonData); + + // Replace the { "value": } with to match the Settings object format + foreach (var property in (JObject)imageResizerSettings["properties"]) + { + imageResizerSettings["properties"][property.Key] = property.Value["value"]; + } + + Settings jsonSettings = JsonConvert.DeserializeObject(imageResizerSettings["properties"].ToString(), new JsonSerializerSettings() { ObjectCreationHandling = ObjectCreationHandling.Replace }); + + // Needs to be called on the App UI thread as the properties are bound to the UI. + App.Current.Dispatcher.Invoke(() => + { + ShrinkOnly = jsonSettings.ShrinkOnly; + Replace = jsonSettings.Replace; + IgnoreOrientation = jsonSettings.IgnoreOrientation; + JpegQualityLevel = jsonSettings.JpegQualityLevel; + PngInterlaceOption = jsonSettings.PngInterlaceOption; + TiffCompressOption = jsonSettings.TiffCompressOption; + FileName = jsonSettings.FileName; + Sizes = jsonSettings.Sizes; + KeepDateModified = jsonSettings.KeepDateModified; + FallbackEncoder = jsonSettings.FallbackEncoder; + CustomSize = jsonSettings.CustomSize; + SelectedSizeIndex = jsonSettings.SelectedSizeIndex; + }); + _jsonMutex.ReleaseMutex(); + } + } +} diff --git a/src/modules/imageresizer/ui/Resources/ImageResizer.ico b/src/modules/imageresizer/ui/Resources/ImageResizer.ico new file mode 100644 index 0000000000..accd1ee6b4 Binary files /dev/null and b/src/modules/imageresizer/ui/Resources/ImageResizer.ico differ diff --git a/src/modules/imageresizer/ui/Resources/ImageResizer.png b/src/modules/imageresizer/ui/Resources/ImageResizer.png new file mode 100644 index 0000000000..f196da96a8 Binary files /dev/null and b/src/modules/imageresizer/ui/Resources/ImageResizer.png differ diff --git a/src/modules/imageresizer/ui/Utilities/MathHelpers.cs b/src/modules/imageresizer/ui/Utilities/MathHelpers.cs new file mode 100644 index 0000000000..7973b2cfc9 --- /dev/null +++ b/src/modules/imageresizer/ui/Utilities/MathHelpers.cs @@ -0,0 +1,14 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; + +namespace ImageResizer.Utilities +{ + internal static class MathHelpers + { + public static int Clamp(int value, int min, int max) + => Math.Min(Math.Max(value, min), max); + } +} diff --git a/src/modules/imageresizer/ui/Utilities/Win32Helpers.cs b/src/modules/imageresizer/ui/Utilities/Win32Helpers.cs new file mode 100644 index 0000000000..58e9c81273 --- /dev/null +++ b/src/modules/imageresizer/ui/Utilities/Win32Helpers.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1636:FileHeaderCopyrightTextMustMatch", Justification = "File created under PowerToys.")] + +namespace ImageResizer.Utilities +{ + // Win32 functions required for temporary workaround for issue #1273 + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Naming used in Win32 dll")] + internal class Win32Helpers + { + [DllImport("user32.dll")] + internal static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); + + [StructLayout(LayoutKind.Sequential)] + public struct INPUT + { + internal INPUTTYPE type; + internal InputUnion data; + + internal static int Size + { + get { return Marshal.SizeOf(typeof(INPUT)); } + } + } + + [StructLayout(LayoutKind.Explicit)] + internal struct InputUnion + { + [FieldOffset(0)] + internal MOUSEINPUT mi; + [FieldOffset(0)] + internal KEYBDINPUT ki; + [FieldOffset(0)] + internal HARDWAREINPUT hi; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MOUSEINPUT + { + internal int dx; + internal int dy; + internal int mouseData; + internal uint dwFlags; + internal uint time; + internal UIntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct KEYBDINPUT + { + internal short wVk; + internal short wScan; + internal uint dwFlags; + internal int time; + internal UIntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct HARDWAREINPUT + { + internal int uMsg; + internal short wParamL; + internal short wParamH; + } + + internal enum INPUTTYPE : uint + { + INPUT_MOUSE = 0, + INPUT_KEYBOARD = 1, + INPUT_HARDWARE = 2, + } + } +} diff --git a/src/modules/imageresizer/ui/ViewModels/AdvancedViewModel.cs b/src/modules/imageresizer/ui/ViewModels/AdvancedViewModel.cs new file mode 100644 index 0000000000..6f88d84151 --- /dev/null +++ b/src/modules/imageresizer/ui/ViewModels/AdvancedViewModel.cs @@ -0,0 +1,91 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Windows.Input; +using System.Windows.Media.Imaging; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using ImageResizer.Models; +using ImageResizer.Properties; + +namespace ImageResizer.ViewModels +{ + public class AdvancedViewModel : ViewModelBase + { + private static readonly IDictionary _encoderMap; + + static AdvancedViewModel() + { + var bmpCodec = new BmpBitmapEncoder().CodecInfo; + var gifCodec = new GifBitmapEncoder().CodecInfo; + var jpegCodec = new JpegBitmapEncoder().CodecInfo; + var pngCodec = new PngBitmapEncoder().CodecInfo; + var tiffCodec = new TiffBitmapEncoder().CodecInfo; + var wmpCodec = new WmpBitmapEncoder().CodecInfo; + + _encoderMap = new Dictionary + { + [bmpCodec.ContainerFormat] = bmpCodec.FriendlyName, + [gifCodec.ContainerFormat] = gifCodec.FriendlyName, + [jpegCodec.ContainerFormat] = jpegCodec.FriendlyName, + [pngCodec.ContainerFormat] = pngCodec.FriendlyName, + [tiffCodec.ContainerFormat] = tiffCodec.FriendlyName, + [wmpCodec.ContainerFormat] = wmpCodec.FriendlyName, + }; + } + + public AdvancedViewModel(Settings settings) + { + RemoveSizeCommand = new RelayCommand(RemoveSize); + AddSizeCommand = new RelayCommand(AddSize); + Settings = settings; + } + + public static IDictionary EncoderMap + => _encoderMap; + + public Settings Settings { get; } + + public string Version + => typeof(AdvancedViewModel).Assembly.GetCustomAttribute() + ?.InformationalVersion; + + public IEnumerable Encoders + => _encoderMap.Keys; + + public ICommand RemoveSizeCommand { get; } + + public ICommand AddSizeCommand { get; } + + public void RemoveSize(ResizeSize size) + => Settings.Sizes.Remove(size); + + public void AddSize() + => Settings.Sizes.Add(new ResizeSize()); + + public void Close(bool accepted) + { + if (accepted) + { + Settings.Save(); + + return; + } + + var selectedSizeIndex = Settings.SelectedSizeIndex; + var shrinkOnly = Settings.ShrinkOnly; + var replace = Settings.Replace; + var ignoreOrientation = Settings.IgnoreOrientation; + + Settings.Reload(); + Settings.SelectedSizeIndex = selectedSizeIndex; + Settings.ShrinkOnly = shrinkOnly; + Settings.Replace = replace; + Settings.IgnoreOrientation = ignoreOrientation; + } + } +} diff --git a/src/modules/imageresizer/ui/ViewModels/ITabViewModel.cs b/src/modules/imageresizer/ui/ViewModels/ITabViewModel.cs new file mode 100644 index 0000000000..4db87c0dcb --- /dev/null +++ b/src/modules/imageresizer/ui/ViewModels/ITabViewModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +namespace ImageResizer.ViewModels +{ + public interface ITabViewModel + { + string Header { get; } + } +} diff --git a/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs b/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs new file mode 100644 index 0000000000..5cf8522b81 --- /dev/null +++ b/src/modules/imageresizer/ui/ViewModels/InputViewModel.cs @@ -0,0 +1,58 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Windows.Input; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using ImageResizer.Models; +using ImageResizer.Properties; +using ImageResizer.Views; + +namespace ImageResizer.ViewModels +{ + public class InputViewModel : ViewModelBase + { + private readonly ResizeBatch _batch; + private readonly MainViewModel _mainViewModel; + private readonly IMainView _mainView; + + public InputViewModel( + Settings settings, + MainViewModel mainViewModel, + IMainView mainView, + ResizeBatch batch) + { + _batch = batch; + _mainViewModel = mainViewModel; + _mainView = mainView; + + Settings = settings; + settings.CustomSize.PropertyChanged += (sender, e) => settings.SelectedSize = (CustomSize)sender; + + ResizeCommand = new RelayCommand(Resize); + CancelCommand = new RelayCommand(Cancel); + ShowAdvancedCommand = new RelayCommand(ShowAdvanced); + } + + public Settings Settings { get; } + + public ICommand ResizeCommand { get; } + + public ICommand CancelCommand { get; } + + public ICommand ShowAdvancedCommand { get; } + + public void Resize() + { + Settings.Save(); + _mainViewModel.CurrentPage = new ProgressViewModel(_batch, _mainViewModel, _mainView); + } + + public void Cancel() + => _mainView.Close(); + + public void ShowAdvanced() + => _mainView.ShowAdvanced(new AdvancedViewModel(Settings)); + } +} diff --git a/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs b/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs new file mode 100644 index 0000000000..75c2224d5f --- /dev/null +++ b/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs @@ -0,0 +1,54 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Collections.Generic; +using System.Windows.Input; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using ImageResizer.Models; +using ImageResizer.Properties; +using ImageResizer.Views; + +namespace ImageResizer.ViewModels +{ + public class MainViewModel : ViewModelBase + { + private readonly Settings _settings; + private readonly ResizeBatch _batch; + + private object _currentPage; + private double _progress; + + public MainViewModel(ResizeBatch batch, Settings settings) + { + _batch = batch; + _settings = settings; + LoadCommand = new RelayCommand(Load); + } + + public ICommand LoadCommand { get; } + + public object CurrentPage + { + get => _currentPage; + set => Set(nameof(CurrentPage), ref _currentPage, value); + } + + public double Progress + { + get => _progress; + set => Set(nameof(Progress), ref _progress, value); + } + + public void Load(IMainView view) + { + if (_batch.Files.Count == 0) + { + _batch.Files.AddRange(view.OpenPictureFiles()); + } + + CurrentPage = new InputViewModel(_settings, this, view, _batch); + } + } +} diff --git a/src/modules/imageresizer/ui/ViewModels/ProgressViewModel.cs b/src/modules/imageresizer/ui/ViewModels/ProgressViewModel.cs new file mode 100644 index 0000000000..d55ef4a683 --- /dev/null +++ b/src/modules/imageresizer/ui/ViewModels/ProgressViewModel.cs @@ -0,0 +1,92 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Input; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using ImageResizer.Models; +using ImageResizer.Views; + +namespace ImageResizer.ViewModels +{ + public class ProgressViewModel : ViewModelBase + { + private readonly MainViewModel _mainViewModel; + private readonly ResizeBatch _batch; + private readonly IMainView _mainView; + private readonly Stopwatch _stopwatch = new Stopwatch(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + private double _progress; + private TimeSpan _timeRemaining; + + public ProgressViewModel( + ResizeBatch batch, + MainViewModel mainViewModel, + IMainView mainView) + { + _batch = batch; + _mainViewModel = mainViewModel; + _mainView = mainView; + + StartCommand = new RelayCommand(Start); + StopCommand = new RelayCommand(Stop); + } + + public double Progress + { + get => _progress; + set => Set(nameof(Progress), ref _progress, value); + } + + public TimeSpan TimeRemaining + { + get => _timeRemaining; + set => Set(nameof(TimeRemaining), ref _timeRemaining, value); + } + + public ICommand StartCommand { get; } + + public ICommand StopCommand { get; } + + public void Start() + => Task.Factory.StartNew( + () => + { + _stopwatch.Restart(); + var errors = _batch.Process( + _cancellationTokenSource.Token, + (completed, total) => + { + var progress = completed / total; + Progress = progress; + _mainViewModel.Progress = progress; + + TimeRemaining = _stopwatch.Elapsed.Multiply((total - completed) / completed); + }); + + if (errors.Any()) + { + _mainViewModel.Progress = 0; + _mainViewModel.CurrentPage = new ResultsViewModel(_mainView, errors); + } + else + { + _mainView.Close(); + } + }, + _cancellationTokenSource.Token); + + public void Stop() + { + _cancellationTokenSource.Cancel(); + _mainView.Close(); + } + } +} diff --git a/src/modules/imageresizer/ui/ViewModels/ResultsViewModel.cs b/src/modules/imageresizer/ui/ViewModels/ResultsViewModel.cs new file mode 100644 index 0000000000..a15e57747d --- /dev/null +++ b/src/modules/imageresizer/ui/ViewModels/ResultsViewModel.cs @@ -0,0 +1,31 @@ +// Copyright (c) Brice Lambson +// The Brice Lambson licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ + +using System.Collections.Generic; +using System.Windows.Input; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using ImageResizer.Models; +using ImageResizer.Views; + +namespace ImageResizer.ViewModels +{ + public class ResultsViewModel : ViewModelBase + { + private readonly IMainView _mainView; + + public ResultsViewModel(IMainView mainView, IEnumerable errors) + { + _mainView = mainView; + Errors = errors; + CloseCommand = new RelayCommand(Close); + } + + public IEnumerable Errors { get; } + + public ICommand CloseCommand { get; } + + public void Close() => _mainView.Close(); + } +} diff --git a/src/modules/imageresizer/ui/Views/AdvancedWindow.xaml b/src/modules/imageresizer/ui/Views/AdvancedWindow.xaml new file mode 100644 index 0000000000..a76f3b4d8c --- /dev/null +++ b/src/modules/imageresizer/ui/Views/AdvancedWindow.xaml @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + × + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %2 - + + + %3 - + + + %4 - + + + %5 - + + + %6 - + + + + + + + + + + + + + + +