我在前面幾章裡面提到過ColorMatrix,可以將圖像的色彩進行仿射變換。但是如果要對圖 像的色彩進行非線性變換的話,那就必須用到更強悍的API了。在Windows早期,有一套標准 的色彩管理的API,叫做ICM 2.0 (Image Color Management 2.0)。在Windows Vista 以後, 這套API升級成了WCS 1.0 (Windows Color System 1.0)。 這套API實現了www.color.org 上說的色彩管理的算法,具體的內容在http://msdn.microsoft.com/en- us/library/dd372446(VS.85).aspx。其中包括了顯示,設備以及Gamut影射的算法。
剛才順手抄了一個使用ICM 2.0轉換圖像的算法,用C#把ICM的幾個API 封裝了一下,這樣 就可以使用ICC來轉換不同的圖像了。
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Runtime.InteropServices;
5 using System.Drawing;
6 using System.Drawing.Imaging;
7 using System.IO;
8
9 namespace ICCConverter
10 {
11 public class ICM
12 {
13 #region Consts
14
15 const uint PROFILE_FILENAME = 1; // profile data is NULL terminated filename
16 const uint PROFILE_READ = 1; // opened for read access
17 const uint FILE_SHARE_READ = 0x00000001;
18 const uint OPEN_EXISTING = 3;
19 const uint PROOF_MODE = 0x00000001;
20 const uint NORMAL_MODE = 0x00000002;
21 const uint BEST_MODE = 0x00000003;
22 const uint ENABLE_GAMUT_CHECKING = 0x00010000;
23 const uint USE_RELATIVE_COLORIMETRIC = 0x00020000;
24 const uint FAST_TRANSLATE = 0x00040000;
25 const int LCS_SIGNATURE = 0x50534F43; /* PSOC */
26
27 #endregion
28
29 #region Types
30
31 public enum BMFORMAT
32 {
33 //
34 // 16bpp - 5 bits per channel. The most significant bit is ignored.
35 //
36
37 BM_x555RGB = 0x0000,
38 BM_x555XYZ = 0x0101,
39 BM_x555Yxy,
40 BM_x555Lab,
41 BM_x555G3CH,
42
43 //
44 // Packed 8 bits per channel => 8bpp for GRAY and
45 // 24bpp for the 3 channel colors, more for hifi channels
46 //
47
48 BM_RGBTRIPLETS = 0x0002,
49 BM_BGRTRIPLETS = 0x0004,
50 BM_XYZTRIPLETS = 0x0201,
51 BM_YxyTRIPLETS,
52 BM_LabTRIPLETS,
53 BM_G3CHTRIPLETS,
54 BM_5CHANNEL,
55 BM_6CHANNEL,
56 BM_7CHANNEL,
57 BM_8CHANNEL,
58 BM_GRAY,
59
60 //
61 // 32bpp - 8 bits per channel. The most significant byte is ignored
62 // for the 3 channel colors.
63 //
64
65 BM_xRGBQUADS = 0x0008,
66 BM_xBGRQUADS = 0x0010,
67 BM_xG3CHQUADS = 0x0304,
68 BM_KYMCQUADS,
69 BM_CMYKQUADS = 0x0020,
70
71 //
72 // 32bpp - 10 bits per channel. The 2 most significant bits are ignored.
73 //
74
75 BM_10b_RGB = 0x0009,
76 BM_10b_XYZ = 0x0401,
77 BM_10b_Yxy,
78 BM_10b_Lab,
79 BM_10b_G3CH,
80
81 //
82 // 32bpp - named color indices (1-based)
83 //
84
85 BM_NAMED_INDEX,
86
87 //
88 // Packed 16 bits per channel => 16bpp for GRAY and
89 // 48bpp for the 3 channel colors.
90 //
91
92 BM_16b_RGB = 0x000A,
93 BM_16b_XYZ = 0x0501,
94 BM_16b_Yxy,
95 BM_16b_Lab,
96 BM_16b_G3CH,
97 BM_16b_GRAY,
98
99 //
100 // 16 bpp - 5 bits for Red & Blue, 6 bits for Green
101 //
102
103 BM_565RGB = 0x0001,
104
105 //#if NTDDI_VERSION >= NTDDI_LONGHORN
106 //
107 // scRGB - 32 bits per channel floating point
108 // 16 bits per channel floating point
109 //
110
111 BM_32b_scRGB = 0x0601,
112 BM_32b_scARGB = 0x0602,
113 BM_S2DOT13FIXED_scRGB = 0x0603,
114 BM_S2DOT13FIXED_scARGB = 0x0604
115 //#endif // NTDDI_VERSION >= NTDDI_LONGHORN
116
117 }
118
119 [StructLayout(LayoutKind.Sequential)]
120 public struct CIEXYZ
121 {
122 public int ciexyzX, ciexyzY, ciexyzZ;
123 }
124
125 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
126 public struct tagPROFILE
127 {
128 public uint dwType;
129 public string pProfileData;
130 public uint cbDataSize;
131 }
132
133 [StructLayout(LayoutKind.Sequential)]
134 public struct CIEXYZTRIPLE
135 {
136 public CIEXYZ ciexyzRed, ciexyzGreen, ciexyBlue;
137 }
138
139 [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Unicode)]
140 struct LOGCOLORSPACE
141 {
142 public uint Signature, Version, Size;
143 public int CSType, Intent, GammaRed, GammaGreen, GammaBlue;
144 public CIEXYZTRIPLE Endpoints;
145
146 [MarshalAs (UnmanagedType.ByValTStr, SizeConst = 260)]
147 public string Filename;
148 }
149
150 public enum GamutMappingIntent
151 {
152 LCS_GM_ABS_COLORIMETRIC = 0x00000008,
153 LCS_GM_BUSINESS = 0x00000001,
154 LCS_GM_GRAPHICS = 0x00000002,
155 LCS_GM_IMAGES = 0x00000004
156 }
157
158 public enum LogicalColorSpace
159 {
160 LCS_CALIBRATED_RGB = 0x00000000,
161 LCS_sRGB = 0x73524742,
162 LCS_WINDOWS_COLOR_SPACE = 0x57696E20
163 }
164
165
166
167 #endregion
168
169 public delegate bool ICMProgressProcCallback(uint ulMax, uint ulCurrent, int ulCallbackData);
170
171 [DllImport("mscms.dll", SetLastError = true)]
172 static extern IntPtr OpenColorProfile(ref tagPROFILE pProfile, uint AccessMode, uint ShareMode, uint CreateMode);
173
174
175
176 [DllImport("mscms.dll", SetLastError = true)]
177 static extern bool TranslateBitmapBits(IntPtr pTransform, IntPtr inBuffer, BMFORMAT inFormat, uint width, uint height, uint stride, IntPtr outBuffer, BMFORMAT outFormat, uint outStride, ICMProgressProcCallback pfCallback, int CallBackParam);
178
179 [DllImport("mscms.dll", SetLastError = true)]
180 static extern bool CloseColorProfile(IntPtr profile);
181
182 [DllImport("mscms.dll", SetLastError = true)]
183 static extern bool DeleteColorTransform(IntPtr transform);
184
185 [DllImport("mscms.dll", SetLastError = true)]
186 static extern IntPtr CreateColorTransform(ref LOGCOLORSPACE pLogColorSpace, IntPtr hDestProfile, IntPtr hTargetProfile, uint dwFlags);
187
188 public void Convert(string profilePath, string imageFilePath, string outputPath)
189 {
190
191 LOGCOLORSPACE logColorSpace = new LOGCOLORSPACE();
192
193 logColorSpace.Signature = LCS_SIGNATURE; /* LCS_SIGNATURE */
194 logColorSpace.Intent = (int) GamutMappingIntent.LCS_GM_IMAGES; /* LCS_GM_IMAGES */
195 logColorSpace.Version = 0x0400;
196 logColorSpace.Size = (uint)Marshal.SizeOf(logColorSpace);
197 logColorSpace.CSType = (int)LogicalColorSpace.LCS_sRGB; /* LCS_sRGB */
198 IntPtr Destprofile;
199
200 tagPROFILE profile = new tagPROFILE();
201 profile.dwType = PROFILE_FILENAME;
202 profile.pProfileData = profilePath;
203 profile.cbDataSize = (uint)profile.pProfileData.Length + 1;
204 Destprofile = OpenColorProfile(ref profile, PROFILE_READ, FILE_SHARE_READ, OPEN_EXISTING);
205 IntPtr pTransforms = CreateColorTransform(ref logColorSpace, Destprofile, IntPtr.Zero, BEST_MODE);
206
207 if (pTransforms != IntPtr.Zero)
208 {
209 FileStream fs = new FileStream(imageFilePath, FileMode.Open, FileAccess.Read);
210 Bitmap bmpTemp = (Bitmap)Image.FromStream(fs, false, false);
211 Bitmap bmp = new Bitmap(bmpTemp);
212 fs.Close();
213 bmpTemp.Dispose ();
214
215 BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
216 bool success = TranslateBitmapBits(
217 pTransforms,
218 bmData.Scan0,
219 BMFORMAT.BM_RGBTRIPLETS,
220 (uint)bmData.Width,
221 (uint)bmData.Height,
222 (uint)bmData.Stride,
223 bmData.Scan0,
224 BMFORMAT.BM_RGBTRIPLETS,
225 (uint) bmData.Stride, null, 0);
226
227 bmp.UnlockBits(bmData);
228 bmp.Save(outputPath, ImageFormat.Jpeg);
229 CloseColorProfile(Destprofile);
230 DeleteColorTransform(Destprofile);
231 }
232 else
233 {
234 int errorCode = Marshal.GetLastWin32Error();
235 throw new COMException("Error", errorCode);
236 }
237 }
238 }
239 }
240
241
這一章其實跟GDI+並沒有什麼太大的關系,不知道什麼時候這些代碼會直接放在.NET Framework Code裡面,這樣用起來就方便了。