Created
October 19, 2015 22:34
-
-
Save apsun/884b780ab18ff92f9064 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Drawing; | |
using System.Drawing.Drawing2D; | |
using System.Drawing.Imaging; | |
using System.IO; | |
using System.Windows.Forms; | |
namespace WallpaperFixer | |
{ | |
public class Program | |
{ | |
public enum OverwriteMode | |
{ | |
Skip, | |
Overwrite, | |
Ask | |
} | |
public enum CopyImageMode | |
{ | |
IfOptimized, | |
Always, | |
Ask, | |
} | |
public static bool TryGetNextIntegerArg(string[] args, ref int currIndex, out int value) | |
{ | |
int origIndex = currIndex; | |
value = 0; | |
string stringVal; | |
bool success = TryGetNextArg(args, ref currIndex, out stringVal); | |
success = success && int.TryParse(stringVal, out value); | |
if (!success) currIndex = origIndex; | |
return success; | |
} | |
public static bool TryGetNextIntegerPairArg(string[] args, ref int currIndex, out int val1, out int val2) | |
{ | |
int origIndex = currIndex; | |
val2 = 0; //In case first one fails, val2 will be uninitialized, val1 will be 0. | |
bool success = TryGetNextIntegerArg(args, ref currIndex, out val1); | |
success = success && TryGetNextIntegerArg(args, ref currIndex, out val2); | |
if (!success) currIndex = origIndex; | |
return success; | |
} | |
public static bool TryGetNextArg(string[] args, ref int currIndex, out string value) | |
{ | |
if (currIndex + 1 < args.Length) | |
{ | |
value = args[++currIndex]; | |
return true; | |
} | |
value = null; | |
return false; | |
} | |
public static bool TryGetNextArgUpper(string[] args, ref int currIndex, out string value) | |
{ | |
bool success = TryGetNextArg(args, ref currIndex, out value); | |
if (success) value = value.ToUpperInvariant(); | |
return success; | |
} | |
public static char PromptCharUpper(string question) | |
{ | |
Console.Write(question); | |
char input; | |
do | |
{ | |
input = Console.ReadKey(true).KeyChar; | |
} while (char.IsControl(input)); | |
char c = char.ToUpperInvariant(input); | |
Console.Write(input); | |
Console.WriteLine(); | |
return c; | |
} | |
public static string PromptString(string question) | |
{ | |
Console.Write(question); | |
return Console.ReadLine(); | |
} | |
public static bool PromptYesNo(string question, bool? defaultChoice = null) | |
{ | |
while (true) | |
{ | |
char c = PromptCharUpper(question + " (y/n): "); | |
if (c == 'Y') return true; | |
if (c == 'N') return false; | |
if (defaultChoice.HasValue) return defaultChoice.Value; | |
Console.WriteLine("Invalid input!"); | |
} | |
} | |
public static T PromptShortEnum<T>(string question, T? defaultChoice = null) where T : struct | |
{ | |
Type enumType = typeof(T); | |
if (!enumType.IsEnum) throw new ArgumentException(); | |
Console.WriteLine(question); | |
string[] enumNames = Enum.GetNames(enumType); | |
if (enumNames.Length > 10) throw new ArgumentException(); | |
for (int i = 0; i < enumNames.Length; ++i) | |
{ | |
Console.WriteLine(((i + 1) % 10) + ". " + enumNames[i]); | |
} | |
while (true) | |
{ | |
char c = PromptCharUpper("Select an option: "); | |
int numC = PositiveMod((c - '0') - 1, 10); | |
if (numC >= 0 && numC < enumNames.Length) | |
{ | |
//The first one here breaks if the enum is not | |
//sequential starting from 0, e.g. { 0, 2, 3 } | |
//return (T)(object)numC; | |
return (T)Enum.Parse(enumType, enumNames[numC]); | |
} | |
if (defaultChoice.HasValue) return defaultChoice.Value; | |
Console.WriteLine("Invalid input!"); | |
} | |
} | |
public static int PromptPositiveInteger(string question) | |
{ | |
while (true) | |
{ | |
string input = PromptString(question); | |
int value; | |
bool success = TryParsePositiveInteger(input, out value); | |
if (success) return value; | |
Console.WriteLine("Invalid input!"); | |
} | |
} | |
public static void WriteSeparator() | |
{ | |
Console.WriteLine("-----------------------------------------------"); | |
} | |
public static void WriteSeparator(ref bool alreadyWritten) | |
{ | |
if (alreadyWritten) return; | |
WriteSeparator(); | |
alreadyWritten = true; | |
} | |
public static void Pause() | |
{ | |
Console.Write("Press any key to continue . . . "); | |
Console.ReadKey(true); | |
Console.WriteLine(); | |
} | |
public static EncoderParameters GetQualityEncoderParams(long quality) | |
{ | |
if (quality < 0 || quality > 100) throw new ArgumentOutOfRangeException("quality"); | |
var ep = new EncoderParameters(); | |
ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); | |
return ep; | |
} | |
public static ImageCodecInfo GetEncoder(ImageFormat format) | |
{ | |
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders(); | |
foreach (var codec in codecs) | |
{ | |
if (codec.FormatID == format.Guid) return codec; | |
} | |
return null; | |
} | |
public static void SaveAsJpeg(Image img, string path) | |
{ | |
var encoder = GetEncoder(ImageFormat.Jpeg); | |
var encoderParams = GetQualityEncoderParams(100L); | |
img.Save(path, encoder, encoderParams); | |
} | |
public static void CreateDirIfNotExist(string dirPath) | |
{ | |
if (Directory.Exists(dirPath)) return; | |
Directory.CreateDirectory(dirPath); | |
} | |
public static int PositiveMod(int a, int b) | |
{ | |
if (b < 0) | |
{ | |
a = -a; | |
b = -b; | |
} | |
int ret = a % b; | |
if (ret < 0) ret += b; | |
return ret; | |
} | |
public static bool TryLoadImage(string path, out Image img) | |
{ | |
try | |
{ | |
img = Image.FromFile(path); | |
return true; | |
} | |
catch | |
{ | |
img = null; | |
return false; | |
} | |
} | |
public static bool TryParsePath(string path, out string formattedPath) | |
{ | |
try | |
{ | |
formattedPath = Path.GetFullPath(path); | |
return true; | |
} | |
catch | |
{ | |
formattedPath = null; | |
return false; | |
} | |
} | |
public static bool TryParsePositiveInteger(string text, out int value) | |
{ | |
return int.TryParse(text, out value) && value > 0; | |
} | |
public static bool DoStuff(string[] args) | |
{ | |
Rectangle screen = Screen.PrimaryScreen.Bounds; | |
bool sizeParams = false; | |
int screenWidth = screen.Width; | |
int screenHeight = screen.Height; | |
bool overwriteParams = false; | |
var overwrite = OverwriteMode.Ask; | |
bool copyImageParams = false; | |
var copyImage = CopyImageMode.IfOptimized; | |
bool autoexit = false; | |
bool quiet = false; | |
bool srcParams = false; | |
string srcDir = @".\"; | |
bool destParams = false; | |
string destDir = @".\Optimized\"; | |
#region Args parsing | |
for (int i = 0; i < args.Length; i++) | |
{ | |
string arg = args[i]; | |
switch (arg.ToUpperInvariant()) | |
{ | |
#region AutoExit | |
case "/AUTOEXIT": | |
autoexit = true; | |
break; | |
#endregion | |
#region Quiet | |
case "/QUIET": | |
quiet = true; | |
break; | |
#endregion | |
#region Size | |
case "/SIZE": | |
if (sizeParams) | |
{ | |
Console.WriteLine("Size parameter set multiple times!"); | |
return false; | |
} | |
int w, h; | |
if (TryGetNextIntegerPairArg(args, ref i, out w, out h) && w > 0 && h > 0) | |
{ | |
sizeParams = true; | |
screenWidth = w; | |
screenHeight = h; | |
break; | |
} | |
string autoSize; | |
if (TryGetNextArgUpper(args, ref i, out autoSize) && autoSize == "AUTO") | |
{ | |
sizeParams = true; | |
break; | |
} | |
Console.WriteLine("Invalid size parameter! " + | |
"Format is \"/size 1024 768\" or \"/size auto\""); | |
return false; | |
#endregion | |
#region Overwrite | |
case "/OVERWRITE": | |
if (overwriteParams) | |
{ | |
Console.WriteLine("Overwrite parameter set multiple times!"); | |
return false; | |
} | |
int overwriteVal; | |
if (TryGetNextIntegerArg(args, ref i, out overwriteVal)) | |
{ | |
overwriteParams = true; | |
switch (overwriteVal) | |
{ | |
case 0: | |
overwrite = OverwriteMode.Skip; | |
break; | |
case 1: | |
overwrite = OverwriteMode.Overwrite; | |
break; | |
case 2: | |
overwrite = OverwriteMode.Ask; | |
break; | |
default: | |
overwriteParams = false; | |
break; | |
} | |
} | |
if (!overwriteParams) | |
{ | |
Console.WriteLine("Invalid overwrite parameter! " + | |
"Format is /overwrite [0/1/2]"); | |
return false; | |
} | |
break; | |
#endregion | |
#region CopyImage | |
case "/COPYIMAGE": | |
if (copyImageParams) | |
{ | |
Console.WriteLine("Image copy parameter set multiple times!"); | |
return false; | |
} | |
int copyVal; | |
if (TryGetNextIntegerArg(args, ref i, out copyVal)) | |
{ | |
copyImageParams = true; | |
switch (copyVal) | |
{ | |
case 0: | |
copyImage = CopyImageMode.IfOptimized; | |
break; | |
case 1: | |
copyImage = CopyImageMode.Always; | |
break; | |
case 2: | |
copyImage = CopyImageMode.Ask; | |
break; | |
default: | |
copyImageParams = false; | |
break; | |
} | |
} | |
if (!copyImageParams) | |
{ | |
Console.WriteLine("Invalid image copy parameter! " + | |
"Format is /imagecopy [0/1/2]"); | |
return false; | |
} | |
break; | |
#endregion | |
#region Source | |
case "/SOURCE": | |
if (srcParams) | |
{ | |
Console.WriteLine("Source parameter set multiple times!"); | |
return false; | |
} | |
string srcDirVal; | |
if (!TryGetNextArg(args, ref i, out srcDirVal)) | |
{ | |
Console.WriteLine("Invalid source parameter! " + | |
@"Format is /source ""C:\Src\"""); | |
return false; | |
} | |
if (!TryParsePath(srcDirVal, out srcDirVal)) | |
{ | |
Console.WriteLine("Source directory is not a valid path!"); | |
return false; | |
} | |
if (!Directory.Exists(srcDirVal)) | |
{ | |
Console.WriteLine("Source directory does not exist!"); | |
return false; | |
} | |
srcParams = true; | |
srcDir = srcDirVal; | |
break; | |
#endregion | |
#region Dest | |
case "/DEST": | |
if (destParams) | |
{ | |
Console.WriteLine("Destination parameter set multiple times!"); | |
return false; | |
} | |
string destDirVal; | |
if (!TryGetNextArg(args, ref i, out destDirVal)) | |
{ | |
Console.WriteLine("Invalid source parameter! " + | |
@"Format is /dest ""C:\Dest\"""); | |
return false; | |
} | |
if (!TryParsePath(destDirVal, out destDirVal)) | |
{ | |
Console.WriteLine("Destination directory is not a valid path!"); | |
return false; | |
} | |
destParams = true; | |
destDir = destDirVal; | |
break; | |
#endregion | |
#region Invalid parameter | |
default: | |
Console.WriteLine("Invalid parameter: " + arg); | |
return false; | |
#endregion | |
} | |
} | |
#endregion | |
if (!srcParams) srcDir = Path.GetFullPath(srcDir); | |
if (!destParams) destDir = Path.GetFullPath(destDir); | |
if (!sizeParams) | |
{ | |
string dimensionQ = string.Format("Is {0}x{1} your desired screen dimension?", | |
screenWidth, screenHeight); | |
bool dimensionA = PromptYesNo(dimensionQ); | |
if (!dimensionA) | |
{ | |
screenWidth = PromptPositiveInteger("Enter your screen width: "); | |
screenHeight = PromptPositiveInteger("Enter your screen height: "); | |
} | |
} | |
if (!overwriteParams) | |
{ | |
overwrite = PromptShortEnum<OverwriteMode>("Do you want to overwrite existing images?"); | |
} | |
if (!copyImageParams) | |
{ | |
copyImage = PromptShortEnum<CopyImageMode>("Do you want to copy all images, " + | |
"even if they cannot be optimized?"); | |
} | |
WriteSeparator(); | |
Console.WriteLine("Dimensions: {0}x{1}\n" + | |
"Overwrite: {2}\n" + | |
"CopyImage: {3}\n" + | |
"AutoExit: {4}\n" + | |
"Quiet: {5}\n" + | |
"Source: {6}\n" + | |
"Dest: {7}", | |
screenWidth, screenHeight, overwrite, copyImage, | |
autoexit, quiet, | |
srcDir, destDir); | |
int count = 0; | |
CreateDirIfNotExist(destDir); | |
foreach (string srcPath in Directory.GetFiles(srcDir)) | |
{ | |
bool wroteSeparator = false; | |
if (!quiet) WriteSeparator(ref wroteSeparator); | |
string srcName = Path.GetFileName(srcPath); | |
Debug.Assert(srcName != null); | |
string destName = Path.ChangeExtension(srcName, ".jpg"); | |
string destPath = Path.Combine(destDir, destName); | |
if (File.Exists(destPath)) | |
{ | |
if (overwrite == OverwriteMode.Ask) | |
{ | |
if (quiet) WriteSeparator(ref wroteSeparator); | |
bool replace = PromptYesNo("File already exists: " + destName + | |
"\nDo you want to replace it?"); | |
if (!replace) continue; | |
} | |
else if (overwrite == OverwriteMode.Skip) | |
{ | |
if (!quiet) Console.WriteLine("File already exists: " + destName); | |
continue; | |
} | |
} | |
Image img; | |
if (!TryLoadImage(srcPath, out img)) | |
{ | |
if (!quiet) Console.WriteLine("Could not load file: " + srcName); | |
continue; | |
} | |
int srcWidth = img.Width; | |
int srcHeight = img.Height; | |
if (srcWidth <= screenWidth || srcHeight <= screenHeight) | |
{ | |
if (img.RawFormat.Guid == ImageFormat.Jpeg.Guid) | |
{ | |
if (copyImage == CopyImageMode.Ask) | |
{ | |
if (quiet) WriteSeparator(ref wroteSeparator); | |
bool copy = PromptYesNo("Cannot optimize: " + srcName + | |
"\nDo you still wish to copy this image?"); | |
if (!copy) continue; | |
} | |
else if (!quiet) | |
{ | |
Console.WriteLine("Cannot optimize: " + srcName); | |
} | |
if (copyImage == CopyImageMode.IfOptimized) | |
{ | |
continue; | |
} | |
} | |
else | |
{ | |
if (!quiet) Console.WriteLine("Image too small: " + srcName); | |
} | |
} | |
else | |
{ | |
var srcRect = new Rectangle(0, 0, srcWidth, srcHeight); | |
float hRatio = (float)screenWidth / srcWidth; | |
float vRatio = (float)screenHeight / srcHeight; | |
float scaleFactor = Math.Max(hRatio, vRatio); | |
var destWidth = (int)Math.Round(srcWidth * scaleFactor); | |
var destHeight = (int)Math.Round(srcHeight * scaleFactor); | |
var destRect = new Rectangle(0, 0, destWidth, destHeight); | |
Debug.Assert(destWidth == screenWidth || destHeight == screenHeight); | |
if (!quiet) | |
{ | |
Console.WriteLine("Resizing: " + srcName); | |
Console.WriteLine("Old size: {0}x{1}", srcWidth, srcHeight); | |
Console.WriteLine("New size: {0}x{1}", destWidth, destHeight); | |
} | |
var resized = new Bitmap(destWidth, destHeight); | |
Graphics g = Graphics.FromImage(resized); | |
g.InterpolationMode = InterpolationMode.HighQualityBicubic; | |
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel); | |
g.Dispose(); | |
img.Dispose(); | |
img = resized; | |
} | |
SaveAsJpeg(img, destPath); | |
if (!quiet) Console.WriteLine("Saved as: " + destName); | |
img.Dispose(); | |
++count; | |
} | |
WriteSeparator(); | |
Console.WriteLine("Complete! Optimized " + count + " image(s)."); | |
return autoexit; | |
} | |
public static void Main(string[] args) | |
{ | |
if (!DoStuff(args)) Pause(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment