|
4 | 4 | // Contact and Information: http://www.hardcodet.net |
5 | 5 |
|
6 | 6 | using System; |
| 7 | +using System.Collections.Generic; |
7 | 8 | using System.ComponentModel; |
8 | 9 | using System.Drawing; |
| 10 | +using System.IO; |
| 11 | +using System.Linq; |
9 | 12 | using System.Windows; |
10 | 13 | using System.Windows.Input; |
11 | 14 | using System.Windows.Media; |
@@ -161,7 +164,135 @@ public static Icon ToIcon(this ImageSource imageSource) |
161 | 164 | } |
162 | 165 |
|
163 | 166 | Interop.Size iconSize = SystemInfo.SmallIconSize; |
164 | | - return new Icon(streamInfo.Stream, new System.Drawing.Size(iconSize.Width, iconSize.Height)); |
| 167 | + |
| 168 | + using var stream = streamInfo.Stream; |
| 169 | + var bestIcon = GetBestFitIcon(stream, new System.Drawing.Size(iconSize.Width, iconSize.Height)); |
| 170 | + return bestIcon; |
| 171 | + } |
| 172 | + |
| 173 | + /// <summary> |
| 174 | + /// Finds the best fitting icon from a stream based on the desired size. |
| 175 | + /// </summary> |
| 176 | + /// <param name="iconStream">The stream containing the icon data.</param> |
| 177 | + /// <param name="desiredSize">The desired size of the icon.</param> |
| 178 | + /// <returns>The best fitting icon as an <see cref="Icon"/> object.</returns> |
| 179 | + /// <exception cref="InvalidDataException">Thrown if the ICO file header is invalid or contains no images.</exception> |
| 180 | + /// <exception cref="EndOfStreamException">Thrown if the complete icon image data could not be read.</exception> |
| 181 | + private static Icon GetBestFitIcon(Stream iconStream, System.Drawing.Size desiredSize) |
| 182 | + { |
| 183 | + // Read the icon entries |
| 184 | + iconStream.Seek(0, SeekOrigin.Begin); |
| 185 | + using var reader = new BinaryReader(iconStream); |
| 186 | + |
| 187 | + // Read and validate the ICONDIR header |
| 188 | + var idReserved = reader.ReadUInt16(); // Reserved (must be 0) |
| 189 | + var idType = reader.ReadUInt16(); // Resource Type (1 for icons) |
| 190 | + var idCount = reader.ReadUInt16(); // Number of images |
| 191 | + |
| 192 | + if (idReserved != 0 || idType != 1) |
| 193 | + throw new InvalidDataException("Invalid ICO file header."); |
| 194 | + |
| 195 | + if (idCount == 0) |
| 196 | + throw new InvalidDataException("The ICO file contains no images."); |
| 197 | + |
| 198 | + // Read ICONDIRENTRYs |
| 199 | + var iconEntries = new List<IconEntry>(); |
| 200 | + for (var i = 0; i < idCount; i++) |
| 201 | + { |
| 202 | + var entry = new IconEntry |
| 203 | + { |
| 204 | + Width = reader.ReadByte(), |
| 205 | + Height = reader.ReadByte(), |
| 206 | + ColorCount = reader.ReadByte(), |
| 207 | + Reserved = reader.ReadByte(), |
| 208 | + Planes = reader.ReadUInt16(), |
| 209 | + BitCount = reader.ReadUInt16(), |
| 210 | + BytesInRes = reader.ReadUInt32(), |
| 211 | + ImageOffset = reader.ReadUInt32() |
| 212 | + }; |
| 213 | + |
| 214 | + // Adjust for 256x256 icons, which are stored with width and height as 0 |
| 215 | + if (entry.Width == 0) entry.Width = 256; |
| 216 | + if (entry.Height == 0) entry.Height = 256; |
| 217 | + |
| 218 | + iconEntries.Add(entry); |
| 219 | + } |
| 220 | + |
| 221 | + // Find icons greater than or equal to the desired size |
| 222 | + IconEntry bestEntry; |
| 223 | + var largerOrEqualIcons = iconEntries |
| 224 | + .Where(entry => entry.Width >= desiredSize.Width && entry.Height >= desiredSize.Height) |
| 225 | + .OrderBy(entry => entry.Width * entry.Height) |
| 226 | + .ThenBy(entry => entry.Width) |
| 227 | + .ThenBy(entry => entry.Height) |
| 228 | + .ToList(); |
| 229 | + |
| 230 | + if (largerOrEqualIcons.Any()) |
| 231 | + { |
| 232 | + // Select the smallest icon among those larger or equal to the desired size |
| 233 | + bestEntry = largerOrEqualIcons.First(); |
| 234 | + } |
| 235 | + else |
| 236 | + { |
| 237 | + // No larger icons; select the largest icon smaller than the desired size |
| 238 | + var smallerIcons = iconEntries |
| 239 | + .Where(entry => entry.Width < desiredSize.Width && entry.Height < desiredSize.Height) |
| 240 | + .OrderByDescending(entry => entry.Width * entry.Height) |
| 241 | + .ThenByDescending(entry => entry.Width) |
| 242 | + .ThenByDescending(entry => entry.Height) |
| 243 | + .ToList(); |
| 244 | + |
| 245 | + // If no icons are smaller or larger, select any available icon (unlikely case) |
| 246 | + bestEntry = smallerIcons.Any() ? smallerIcons.First() : iconEntries.FirstOrDefault(); |
| 247 | + } |
| 248 | + |
| 249 | + if (bestEntry == null) |
| 250 | + return null; |
| 251 | + |
| 252 | + // Read the image data of the selected icon |
| 253 | + var iconImageData = new byte[bestEntry.BytesInRes]; |
| 254 | + iconStream.Seek(bestEntry.ImageOffset, SeekOrigin.Begin); |
| 255 | + var bytesRead = iconStream.Read(iconImageData, 0, (int)bestEntry.BytesInRes); |
| 256 | + if (bytesRead != bestEntry.BytesInRes) |
| 257 | + throw new EndOfStreamException("Could not read the complete icon image data."); |
| 258 | + |
| 259 | + // Create a new .ico file with the single best-matching image |
| 260 | + using var destStream = new MemoryStream(); |
| 261 | + using var writer = new BinaryWriter(destStream); |
| 262 | + |
| 263 | + writer.Write((ushort)0); // idReserved |
| 264 | + writer.Write((ushort)1); // idType |
| 265 | + writer.Write((ushort)1); // idCount |
| 266 | + |
| 267 | + writer.Write(bestEntry.Width == 256 ? (byte)0 : (byte)bestEntry.Width); |
| 268 | + writer.Write(bestEntry.Height == 256 ? (byte)0 : (byte)bestEntry.Height); |
| 269 | + writer.Write(bestEntry.ColorCount); |
| 270 | + writer.Write(bestEntry.Reserved); |
| 271 | + writer.Write(bestEntry.Planes); |
| 272 | + writer.Write(bestEntry.BitCount); |
| 273 | + writer.Write(bestEntry.BytesInRes); |
| 274 | + writer.Write((uint)(6 + 16)); // Image data offset |
| 275 | + |
| 276 | + // Write the image data |
| 277 | + writer.Write(iconImageData); |
| 278 | + |
| 279 | + destStream.Seek(0, SeekOrigin.Begin); |
| 280 | + return new Icon(destStream); |
| 281 | + } |
| 282 | + |
| 283 | + /// <summary> |
| 284 | + /// Represents an entry in the icon directory. |
| 285 | + /// </summary> |
| 286 | + private class IconEntry |
| 287 | + { |
| 288 | + public int Width; |
| 289 | + public int Height; |
| 290 | + public byte ColorCount; |
| 291 | + public byte Reserved; |
| 292 | + public ushort Planes; |
| 293 | + public ushort BitCount; |
| 294 | + public uint BytesInRes; |
| 295 | + public uint ImageOffset; |
165 | 296 | } |
166 | 297 |
|
167 | 298 | #endregion |
|
0 commit comments