Skip to content

Instantly share code, notes, and snippets.

@apsun
Created October 19, 2015 22:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apsun/884b780ab18ff92f9064 to your computer and use it in GitHub Desktop.
Save apsun/884b780ab18ff92f9064 to your computer and use it in GitHub Desktop.
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