.NET Matters
Sepia Tone, StringLogicalComparer, and More
Stephen Toub
Code download available at:NETMatters0501.exe(195 KB)
Q My digital camera allows me to take pictures using a "sepia" filter which results in photos that look like they were taken in the late 1800s. While this is cool, I'd rather take the pictures normally and then apply this change later on. I can probably do something like this in Digital Image Pro 10, but I'm working on my own photo utility library in .NET and was hoping to add this functionality. What's the best way to change the coloring of a bitmap?
Q My digital camera allows me to take pictures using a "sepia" filter which results in photos that look like they were taken in the late 1800s. While this is cool, I'd rather take the pictures normally and then apply this change later on. I can probably do something like this in Digital Image Pro 10, but I'm working on my own photo utility library in .NET and was hoping to add this functionality. What's the best way to change the coloring of a bitmap?
A There are a few ways to change the colors that make up an image. One approach is to iterate over all of the pixels in the image, retrieving each pixel's color using Bitmap.GetPixel, modifying the color to your liking, and then setting the color back into the image using Bitmap.SetPixel (note that a System.Drawing.Color structure is immutable once created, so you'll actually need to create a new value based on the old one). This is a relatively slow process, however. The conversion can be sped up using unsafe code to access the image data, but that still requires you to manually touch each pixel.
A There are a few ways to change the colors that make up an image. One approach is to iterate over all of the pixels in the image, retrieving each pixel's color using Bitmap.GetPixel, modifying the color to your liking, and then setting the color back into the image using Bitmap.SetPixel (note that a System.Drawing.Color structure is immutable once created, so you'll actually need to create a new value based on the old one). This is a relatively slow process, however. The conversion can be sped up using unsafe code to access the image data, but that still requires you to manually touch each pixel.
An easier way is to use the ColorMatrix class, available in the System.Drawing.Imaging namespace. ColorMatrix defines a 5¥5 matrix that contains the coordinates for the RGBA color space (the A stands for alpha, a measure of the transparency of the color) and can be used in coordination with the ImageAttributes class to control how images are drawn onto a Graphics surface. When applied, the ColorMatrix is multiplied by the color vector for each pixel in the image to come up with the new color pixel (for more on this operation, see ColorMatrix Basics - Simple Image Color Adjustment). In addition to being easier, this method is also much faster than manually manipulating each pixel. My informal tests show it to be an order of magnitude faster than the GetPixel/SetPixel approach.
So, in order to create a sepia-colored image from your photos, you can create a ColorMatrix that represents the necessary linear transformation for the color space. Sepia is a tone where the colors of the image appear in shades of brown rather than in shades of gray (or grayscale). With black and white photographic prints, metallic compounds, bleach, and other chemicals are used to turn the black areas to brown, a technique popular a century ago (possibly because skin tones can look more lifelike in sepia tone than in grayscale). Of course, chemicals aren't necessary when dealing with digital images; one plausible matrix can be created as follows:
private ColorMatrix CreateSepiaMatrix() { return new ColorMatrix(new float[][]{ new float[] {0.393f, 0.349f, 0.272f, 0, 0}, new float[] {0.769f, 0.686f, 0.534f, 0, 0}, new float[] {0.189f, 0.168f, 0.131f, 0, 0}, new float[] { 0, 0, 0, 1, 0}, new float[] { 0, 0, 0, 0, 1} }); }
This corresponds to the following color transformation:
New Red = R*.393 + G*.769 + B*.189 New Green = R*.349 + G*.686 + B*.168 New Blue = R*.272 + G*.534 + B*.131
Note that these numbers aren't necessarily the "right" answer. This matrix is just one possible solution, and you can play with the numbers in this matrix to better suit your eye and needs. Of course, other shifts are possible by changing the numbers accordingly, such as creating the green and blue hues prevalent throughout the Matrix trilogy (no pun intended).
Once I have the color matrix I want to use, I can set it on an ImageAttributes instance and then use that instance in a call to DrawImage, as shown here:
Image img = GetImage(); ImageAttributes imageAttrs = new ImageAttributes(); imageAttrs.SetColorMatrix(CreateSepiaMatrix()); using(Graphics g = Graphics.FromImage(img)) { g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imageAttrs); }
Figure 1 shows my original image and then a transformed image that resulted from running this code on the original.
Figure 1** A Duck and Peacock in Original and Sepia Tones **
Q I'm working with a list of files and I want to sort this list before displaying it to the user. Many of the files have numbers in the names at multiple locations, for example with a pattern similar to Class#_Student#.txt, and I'd like the sorting algorithm to consider those numbers in their entirety during the sort, rather than sorting based on the character values of the individual digits. So, if I have a list of files named "Class1_Student1.txt", "Class10_Student1.txt", and "Class2_Student1.txt", they should be sorted as "Class1_Student1.txt", "Class2_Student1.txt", and "Class10_Student1.txt". Is there anything in the .NET Framework that will allow me to easily perform this form of sort?
Q I'm working with a list of files and I want to sort this list before displaying it to the user. Many of the files have numbers in the names at multiple locations, for example with a pattern similar to Class#_Student#.txt, and I'd like the sorting algorithm to consider those numbers in their entirety during the sort, rather than sorting based on the character values of the individual digits. So, if I have a list of files named "Class1_Student1.txt", "Class10_Student1.txt", and "Class2_Student1.txt", they should be sorted as "Class1_Student1.txt", "Class2_Student1.txt", and "Class10_Student1.txt". Is there anything in the .NET Framework that will allow me to easily perform this form of sort?
A As far as I know, the base class libraries do not provide a string comparison option that would allow for this kind of comparison. However, given that you're asking about doing this with file names, you probably already know that the Windows® shell is capable of sorting files by name in this fashion. In fact, the Microsoft® Shell Lightweight Utility Library, shlwapi.dll, exposes this comparison routine as StrCmpLogicalW. You can easily consume this function from your managed code using P/Invoke:
[DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)] private static extern int StrCmpLogicalW(string strA, string strB);
It's then very straightforward to incorporate this method into an IComparer implementation that you can use with Array.Sort.
public class StringLogicalComparer : IComparer { public int Compare(object x, object y) { string text1 = x as string; if (text1 != null) { string text2 = y as string; if (text2 != null) return StrCmpLogicalW(text1, text2); } return Comparer.Default.Compare(x, y); } ••• }
This IComparer implementation could obviously be extended to support FileInfo, DirectoryInfo, or any other file-related classes you might want to sort based on the needs of your particular application.
A As far as I know, the base class libraries do not provide a string comparison option that would allow for this kind of comparison. However, given that you're asking about doing this with file names, you probably already know that the Windows® shell is capable of sorting files by name in this fashion. In fact, the Microsoft® Shell Lightweight Utility Library, shlwapi.dll, exposes this comparison routine as StrCmpLogicalW. You can easily consume this function from your managed code using P/Invoke:
[DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)] private static extern int StrCmpLogicalW(string strA, string strB);
It's then very straightforward to incorporate this method into an IComparer implementation that you can use with Array.Sort.
public class StringLogicalComparer : IComparer { public int Compare(object x, object y) { string text1 = x as string; if (text1 != null) { string text2 = y as string; if (text2 != null) return StrCmpLogicalW(text1, text2); } return Comparer.Default.Compare(x, y); } ••• }
This IComparer implementation could obviously be extended to support FileInfo, DirectoryInfo, or any other file-related classes you might want to sort based on the needs of your particular application.
Q I'm using DirectoryInfo.GetFiles in a console application to enumerate all of the files on various disks in my computer. When I attempt to do so for my CD-ROM drive when no disc is in the drive, I get a dialog box as shown in Figure 2. But as this is a console application, users aren't expecting to receive dialog boxes, and sometimes there aren't any physical users at the machine. Can you tell me what various options are available to me?
Q I'm using DirectoryInfo.GetFiles in a console application to enumerate all of the files on various disks in my computer. When I attempt to do so for my CD-ROM drive when no disc is in the drive, I get a dialog box as shown in Figure 2. But as this is a console application, users aren't expecting to receive dialog boxes, and sometimes there aren't any physical users at the machine. Can you tell me what various options are available to me?
Figure 2** Error Dialog from DirectoryInfo.GetFiles **
A The System.IO namespace uses the file management functions FindFirstFile/FindNextFile from the Win32® API to enumerate all of the files in a specified directory. When a serious system error is encountered, such as a drive that isn't ready, the default system behavior is to handle the error and display an error dialog to the user. You'll notice that after you get this dialog and continue out of it, you'll receive the expected System.IO.IOException that states "The device is not ready."
A The System.IO namespace uses the file management functions FindFirstFile/FindNextFile from the Win32® API to enumerate all of the files in a specified directory. When a serious system error is encountered, such as a drive that isn't ready, the default system behavior is to handle the error and display an error dialog to the user. You'll notice that after you get this dialog and continue out of it, you'll receive the expected System.IO.IOException that states "The device is not ready."
Kernel32.dll provides the SetErrorMode method that allows for this behavior to be changed, allowing the caller to specify whether the system will handle these sorts of errors or whether it will expect the process to handle them instead. For the .NET Framework 2.0, Directory.InternalGetFileDirectoryNames (on which DirectoryInfo.GetFiles relies) has been modified to use this method through P/Invoke in order to change the error mode so that this dialog is not displayed. To achieve this same functionality with the .NET Framework 1.x, you can do the same thing and manually P/Invoke to SetErrorMode.
Little code is required to make working with this very straightforward, as is demonstrated in Figure 3. The ChangeErrorMode struct implements IDisposable; in its constructor it changes the error mode to the specified value, and in its IDisposable.Dispose method it changes the mode back to its original value. This pattern makes it easy to incorporate this functionality into your application via the C# using statement:
using(new ChangeErrorMode(ErrorModes.FailCriticalErrors)) { string [] files = Directory.GetFiles(@"D:\"); }
Figure 3 ChangeErrorMode
[Flags] public enum ErrorModes { Default = 0x0, FailCriticalErrors = 0x1, NoGpFaultErrorBox = 0x2, NoAlignmentFaultExcept = 0x4, NoOpenFileErrorBox = 0x8000 } public struct ChangeErrorMode : IDisposable { private int _oldMode; public ChangeErrorMode(ErrorModes mode) { _oldMode = SetErrorMode((int)mode); } void IDisposable.Dispose() { SetErrorMode(_oldMode); } [DllImport("kernel32.dll")] private static extern int SetErrorMode(int newMode); }
When used with SetErrorMode, SEM_FAILCRITICALERRORS, defined as 0x1 in WinBase.h, causes the system to not display the critical-error-handler message box when a serious error is encountered. Instead, the system sends the error to the calling process, which is exactly the functionality you desire.
Q I'd like to interact with the Encrypting File System (EFS) from my .NET application, specifically determining what files have been encrypted and then encrypting and decrypting as appropriate. Is any of this functionality exposed to me?
Q I'd like to interact with the Encrypting File System (EFS) from my .NET application, specifically determining what files have been encrypted and then encrypting and decrypting as appropriate. Is any of this functionality exposed to me?
A In the .NET Framework 2.0 Beta 1, the ability to encrypt and decrypt files is exposed through the File and FileInfo classes. Unfortunately, in the .NET Framework 1.x and later there is no exposed support for EFS, with the exception that the FileAttributes value returned from File.GetFileAttributes will tell you whether a file is encrypted. But as with most of the Win32 API, the magic of P/Invoke allows you to access the rest of EFS's functionality with a minimal amount of work.
A In the .NET Framework 2.0 Beta 1, the ability to encrypt and decrypt files is exposed through the File and FileInfo classes. Unfortunately, in the .NET Framework 1.x and later there is no exposed support for EFS, with the exception that the FileAttributes value returned from File.GetFileAttributes will tell you whether a file is encrypted. But as with most of the Win32 API, the magic of P/Invoke allows you to access the rest of EFS's functionality with a minimal amount of work.
In order to encrypt a file, decrypt a file, or determine the status of a file's encryption, there are three functions I can access through platform invoke: EncryptFile, DecryptFile, and FileEncryptionStatus, all of which are exposed from advapi32.dll. My P/Invoke declarations for them are shown here:
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool EncryptFile(string lpFilename); [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool DecryptFile(string lpFilename, int dwReserved); [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool FileEncryptionStatus( string lpFilename, out int lpStatus);
If all I'm concerned about is accessing this functionality, I can simply call these methods through these P/Invoke declarations. However, I almost always prefer to pass around FileInfo objects rather than string paths when working with files, and as such desire methods that can accept FileInfo instances instead of strings. In addition, these methods don't play as nicely with Code Access Security (CAS) as I'd like. Methods exposed through DllImport require the caller to have the UnmanagedCode security permission. Ideally, however, calling these methods would require nothing more and nothing less than the appropriate FilePermission for reading and writing the file in question.
Taking all of this into account, I've created the class shown in Figure 4. The Encrypt and Decrypt static methods accept a FileInfo as an argument and delegate to the unmanaged EncryptFile and DecryptFile methods, passing in the FileInfo's FullName. If the unmanaged methods return false, meaning that an error occurred, an exception is thrown. I've also included an enumeration, EncryptionStatus, which represents the possible lpStatus output values from FileEncryptionStatus. Note that the information provided by FileEncryptionStatus is much more informative that what is accessible through File.GetFileAttributes. GetFileAttributes can only tell you whether the file is encrypted or not, whereas if the file is not encrypted, FileEncryptionStatus can also tell you whether it can be, and if it can't, FileEncryptionStatus can tell you why.
Figure 4 Using EFS Functionality in .NET
public class Efs { public static void Encrypt(FileInfo file) { Validate(file); if (!EncryptFile(file.FullName)) throw new Win32Exception(); } public static void Decrypt(FileInfo file) { Validate(file); if (!DecryptFile(file.FullName, 0)) throw new Win32Exception(); } private static void Validate(FileInfo file) { if (file == null) throw new ArgumentNullException("file"); if (Environment.OSVersion.Platform != PlatformID.Win32NT) throw new PlatformNotSupportedException(); new FileIOPermission( FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, file.FullName).Demand(); } public static EncryptionStatus GetEncryptionStatus(FileInfo file) { Validate(file); int status; if (!FileEncryptionStatus(file.FullName, out status)) throw new Win32Exception(); return (EncryptionStatus)status; } [SuppressUnmanagedCodeSecurity] [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool EncryptFile(string lpFilename); [SuppressUnmanagedCodeSecurity] [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool DecryptFile( string lpFilename, int dwReserved); [SuppressUnmanagedCodeSecurity] [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] private static extern bool FileEncryptionStatus( string lpFilename, out int lpStatus); public enum EncryptionStatus { CanBeEncrypted = 0, IsEncrypted = 1, IsSystemFile = 2, IsRootDirectory = 3, IsSystemDirectory = 4, Unknown = 5, NotSupportedByFileSystem = 6, UserDisallowed = 7, IsReadOnly = 8, DirectoryDisallowed = 9 } }
Prior to calling the unmanaged methods, the call is validated. This validation ensures that the code is operating on a Windows NT®-based platform (since EFS is only supported on NTFS) and demands read and write file permissions to the specified file. As a result, I've also attributed the P/Invoke declarations with the SuppressUnmanagedCodeSecurityAttribute so that calls to these methods don't trigger a stack walk, allowing classes that use the Efs class to do so without the UnmanagedCode security permission.
Note that EncryptFile, DecryptFile, and FileEncryptionStatus have all been declared with the SetLastError field set to true, meaning that the runtime will call the SetLastError Win32 API function before returning from the method. As a result, I can use Marshal.GetLastWin32Error, which retrieves the last error code set by a call to the Win32 SetLastError API method. However, since I want to return this information to the caller in the form of an exception, I can take advantage of the Win32Exception's default constructor which looks something like
public Win32Exception() : this(Marshal.GetLastWin32Error()) {}
This constructor delegates the result of GetLastWin32Error to the constructor that accepts the native error number as a parameter, which means I don't have to call it explicitly.
Send your questions and comments to netqa@microsoft.com.
Stephen Toub is the Technical Editor for MSDN Magazine.