private static byte[] EncodePng(IcoFrame source, ParseContext context) { using (var stream = new MemoryStream()) { using (var masked = source.CookedData.Clone()) { for (var x = 0; x < masked.Width; x++) { for (var y = 0; y < masked.Height; y++) { if (source.Mask[x, y] && masked[x, y].A != 0) { var c = masked[x, y]; c.A = 0; masked[x, y] = c; } } } masked.SaveAsPng(stream, context.PngEncoder); } stream.Flush(); return(stream.GetBuffer()); } }
public static bool IsAlphaSignificant(IcoFrame source) { if (IsAnyPartialTransparency(source.CookedData)) { return(true); } return(IsAnyPixel(source.CookedData, (x, y, pixel) => IsCompletelyTransparent(pixel) != source.Mask[x, y])); }
private static IcoEncodingType GetOutputEncoding(IcoFrame frame, CommandLineOptions opts) { switch (opts.OutputFormat) { case OutputFileFormatPreference.AlwaysPng: return(IcoEncodingType.Png); case OutputFileFormatPreference.AlwaysBmp: return(IcoEncodingType.Bitmap); case OutputFileFormatPreference.KeepSource: return(frame.Encoding.Type); default: throw new ArgumentException(nameof(opts.OutputFormat)); } }
private static byte[] FinalizeBitmap(IcoFrame source, BitmapEncoding encoding, Dialect dialect, ByteWriter writer, uint offsetToData, uint offsetToImageSize) { var offsetToEndOfData = writer.SeekOffset; if (dialect == Dialect.Ico) { var padding = writer.Data.Count % 4; var inferMaskFromAlpha = (source.Encoding.PixelFormat == BitmapEncoding.Pixel_argb32 && encoding != BitmapEncoding.Pixel_argb32); for (var y = source.CookedData.Height - 1; y >= 0; y--) { var bits = new BitWriter(writer); for (var x = 0; x < source.CookedData.Width; x++) { var mask = inferMaskFromAlpha ? (source.CookedData[x, y].A == 0) : source.Mask[x, y]; bits.AddBit1((byte)(mask ? 1 : 0)); } while ((writer.Data.Count % 4) != padding) { writer.AddUint8(0); } } } if (dialect != Dialect.Ico) { writer.SeekOffset = 2; writer.AddUint32((uint)writer.Data.Count); writer.SeekOffset = 10; writer.AddUint32(offsetToData); } writer.SeekOffset = (int)offsetToImageSize; writer.AddUint32((uint)(offsetToEndOfData - offsetToData)); // biSizeImage return(writer.Data.ToArray()); }
private static void CheckDuplicateFrames(IcoFrame a, int aIndex, IcoFrame b, int bIndex, string fileName) { if (a.Encoding.ClaimedBitDepth != b.Encoding.ClaimedBitDepth || a.Encoding.ActualBitDepth != b.Encoding.ActualBitDepth) { return; } if (a.Encoding.ClaimedHeight != b.Encoding.ClaimedHeight || a.Encoding.ClaimedWidth != b.Encoding.ClaimedWidth) { return; } Reporter.WarnLine(IcoErrorCode.DuplicateFrameTypes, $"Icon file contains redundant frames. Frames #{aIndex} and #{bIndex} have the same height, width, and color depth " + $"({a.Encoding.ClaimedHeight}x{a.Encoding.ClaimedWidth}@{a.Encoding.ClaimedBitDepth}). Everyone will ignore the latter frame.", fileName); }
private static void EncodeBitmapHeader(IcoFrame source, Dialect dialect, BitmapEncoding encoding, Rgba32[] colorTable, ByteWriter writer, out uint offsetToImageSize) { if (dialect != Dialect.Ico) { writer.AddUint16(FileFormatConstants._bitmapFileMagic); writer.AddUint32(0); // Size will be filled in later writer.AddUint32(0); // Reserved writer.AddUint32(0); // Offset will be filled in later } writer.AddUint32(FileFormatConstants._bitmapInfoHeaderSize); writer.AddUint32((uint)source.CookedData.Width); writer.AddUint32((uint)source.CookedData.Height * ((dialect == Dialect.Ico) ? 2u : 1u)); writer.AddUint16(1); // biPlanes writer.AddUint16((ushort)BmpUtil.GetBitDepthForPixelFormat(encoding)); // biBitCount writer.AddUint32(FileFormatConstants.BI_RGB); // biCompression offsetToImageSize = (uint)writer.SeekOffset; writer.AddUint32(0); // biSizeImage writer.AddUint32((dialect == Dialect.Ico) ? 0u : FileFormatConstants._72dpiInPixelsPerMeter); // biXPelsPerMeter writer.AddUint32((dialect == Dialect.Ico) ? 0u : FileFormatConstants._72dpiInPixelsPerMeter); // biYPelsPerMeter writer.AddUint32((uint)(colorTable?.Length ?? 0)); // biClrUsed writer.AddUint32(0); // biClrImportant if (colorTable != null) { foreach (var color in colorTable) { writer.AddUint8(color.B); writer.AddUint8(color.G); writer.AddUint8(color.R); writer.AddUint8(0); } } if (dialect != Dialect.Ico) { while (writer.Data.Count % 4 != 0) { writer.AddUint8(0); } } }
private static void AddFrame(IcoFrame frame, ParseContext context, CommandLineOptions opts) { var outputEncoding = GetOutputEncoding(frame, opts); var extension = outputEncoding == IcoEncodingType.Bitmap ? "bmp" : "png"; var outputFilename = $"{context.FullPath}.{context.ImageDirectoryIndex:D2}.{frame.Encoding.ActualWidth}x{frame.Encoding.ActualHeight}x{frame.Encoding.ActualBitDepth}.{extension}"; if (File.Exists(outputFilename) && opts.ForceOverwrite == false) { Reporter.WarnLine(IcoErrorCode.FileExists, $"Not overwriting existing file {outputFilename}. Use --force-overwrite to change behavior."); return; } Reporter.VerboseLine($"Writing frame to {outputFilename}..."); switch (outputEncoding) { case IcoEncodingType.Bitmap: var bitmap = BmpEncoder.EncodeBitmap(context, frame.Encoding.PixelFormat, BmpEncoder.Dialect.Bmp, frame); File.WriteAllBytes(outputFilename, bitmap); break; case IcoEncodingType.Png: if (frame.Encoding.Type == IcoEncodingType.Png) { File.WriteAllBytes(outputFilename, frame.RawData); } else { using (var stream = new MemoryStream()) { frame.CookedData.SaveAsPng(stream, context.PngEncoder); stream.Flush(); var png = stream.GetBuffer(); File.WriteAllBytes(outputFilename, stream.GetBuffer()); } } break; } }
private static void AddFrame(IcoFrame frame, ParseContext context, CommandLineOptions opts, ref int frameNumber) { frameNumber += 1; System.Console.WriteLine($" Frame #{frameNumber}"); System.Console.WriteLine($" Encoding: {(frame.Encoding.Type == IcoEncodingType.Bitmap ? "Bitmap" : "PNG")}"); if (frame.Encoding.Type == IcoEncodingType.Bitmap) { System.Console.WriteLine($" Bitmap type: {StringFromPixelFormat(frame.Encoding.PixelFormat)}"); } if (frame.Encoding.PaletteSize != 0) { System.Console.WriteLine($" Palette size: {frame.Encoding.PaletteSize}"); } System.Console.WriteLine($" Bytes on disk: {frame.TotalDiskUsage}"); System.Console.WriteLine($" Actual Claimed in ICO header"); System.Console.WriteLine($" Width: {frame.Encoding.ActualWidth.ToString().PadLeft(4, ' ')} {frame.Encoding.ClaimedWidth.ToString().PadLeft(4, ' ')}"); System.Console.WriteLine($" Height: {frame.Encoding.ActualHeight.ToString().PadLeft(4, ' ')} {frame.Encoding.ClaimedHeight.ToString().PadLeft(4, ' ')}"); System.Console.WriteLine($" Bit depth: {frame.Encoding.ActualBitDepth.ToString().PadLeft(4, ' ')} {frame.Encoding.ClaimedBitDepth.ToString().PadLeft(4, ' ')}"); System.Console.WriteLine(); }
public static void DoPngEntry(ByteReader bitmapHeader, ParseContext context, IcoFrame source) { if (source.Encoding.ClaimedBitDepth != 32) { context.Reporter.WarnLine(IcoErrorCode.PngNot32Bit, $"PNG-encoded image with bit depth {source.Encoding.ClaimedBitDepth} (expected 32).", context.DisplayedPath, context.ImageDirectoryIndex.Value); } using (var stream = new MemoryStream(bitmapHeader.Data.ToArray())) { var decoder = new SixLabors.ImageSharp.Formats.Png.PngDecoder(); source.CookedData = decoder.Decode <Rgba32>(new Configuration(), stream); } source.Encoding.Type = IcoEncodingType.Png; source.Encoding.PixelFormat = BmpUtil.IsAnyPartialTransparency(source.CookedData) ? BitmapEncoding.Pixel_argb32 : BitmapEncoding.Pixel_0rgb32; source.Mask = GenerateMaskFromAlpha(source.CookedData); // Conservatively assume that the output wouldn't have used palette trimming, if it had been a bmp frame. if (source.Encoding.ClaimedBitDepth < 16) { source.Encoding.PaletteSize = 1u << (int)source.Encoding.ClaimedBitDepth; } var encoding = GetPngFileEncoding(bitmapHeader.Data); if (encoding.ColorType != PngColorType.RGBA) { context.Reporter.WarnLine(IcoErrorCode.PngNotRGBA32, $"ICO files require the embedded PNG image to be encoded in RGBA32 format; this is {encoding.ColorType}", context.DisplayedPath, context.ImageDirectoryIndex.Value); } else if (encoding.BitsPerChannel != 8) { context.Reporter.WarnLine(IcoErrorCode.PngNotRGBA32, $"ICO files require the embedded PNG image to be encoded in RGBA32 format; this is RGBA{encoding.BitsPerChannel * 4}", context.DisplayedPath, context.ImageDirectoryIndex.Value); } uint numChannels = 0; switch (encoding.ColorType) { case PngColorType.Grayscale: numChannels = 1; break; case PngColorType.RGB: numChannels = 3; break; case PngColorType.GrayscaleAlpha: numChannels = 2; break; case PngColorType.RGBA: numChannels = 4; break; case PngColorType.Palette: default: break; } source.Encoding.ActualHeight = encoding.Height; source.Encoding.ActualWidth = encoding.Width; source.Encoding.ActualBitDepth = encoding.BitsPerChannel * numChannels; }
private static void ReadBitmapMask(ByteReader reader, ParseContext context, int height, int width, IcoFrame source) { source.Mask = new bool[width, height]; var anyMask = false; var anyMaskedColors = false; var padding = reader.SeekOffset % 4; for (var y = height - 1; y >= 0; y--) { var bits = new BitReader(reader); for (var x = 0; x < width; x++) { var mask = bits.NextBit1(); if (mask == 0) { continue; } source.Mask[x, y] = true; anyMask = true; if (source.CookedData[x, y].R != 0 || source.CookedData[x, y].G != 0 || source.CookedData[x, y].B != 0) { anyMaskedColors = true; } //source.CookedData[x, y] = new Rgba32(0, 0, 0, 0); } while ((reader.SeekOffset % 4) != padding) { reader.SeekOffset += 1; } } if (!anyMask) { context.Reporter.WarnLine(IcoErrorCode.NoMaskedPixels, $"No bitmap mask.", context.DisplayedPath, context.ImageDirectoryIndex.Value); } if (anyMaskedColors) { context.Reporter.WarnLine(IcoErrorCode.MaskedPixelWithColor, $"Non-black image pixels masked out.", context.DisplayedPath, context.ImageDirectoryIndex.Value); } }
private static void EmitAllPossibleEncodings(ParseContext context, IcoFrame source) { byte[] png; var pathPrefix = $"{context.FullPath}.{context.ImageDirectoryIndex:D2}."; using (var stream = new MemoryStream()) { source.CookedData.SaveAsPng(stream, context.PngEncoder); stream.Flush(); png = stream.GetBuffer(); var pngPath = $"{pathPrefix}png"; File.WriteAllBytes(pngPath, stream.GetBuffer()); } var bitmap1 = BmpEncoder.EncodeBitmap(context, BitmapEncoding.Pixel_indexed1, BmpEncoder.Dialect.Bmp, source); if (bitmap1 != null) { var bmp1Path = $"{pathPrefix}01bpp.bmp"; File.WriteAllBytes(bmp1Path, bitmap1); } var bitmap4 = BmpEncoder.EncodeBitmap(context, BitmapEncoding.Pixel_indexed4, BmpEncoder.Dialect.Bmp, source); if (bitmap4 != null) { var bmp4Path = $"{pathPrefix}04bpp.bmp"; File.WriteAllBytes(bmp4Path, bitmap4); } var bitmap8 = BmpEncoder.EncodeBitmap(context, BitmapEncoding.Pixel_indexed8, BmpEncoder.Dialect.Bmp, source); if (bitmap8 != null) { var bmp8Path = $"{pathPrefix}08bpp.bmp"; File.WriteAllBytes(bmp8Path, bitmap8); } var bitmap16 = BmpEncoder.EncodeBitmap(context, BitmapEncoding.Pixel_rgb15, BmpEncoder.Dialect.Bmp, source); if (bitmap16 != null) { var bmp16Path = $"{pathPrefix}16bpp.bmp"; File.WriteAllBytes(bmp16Path, bitmap16); } var bitmap32 = BmpEncoder.EncodeBitmap(context, BitmapEncoding.Pixel_0rgb32, BmpEncoder.Dialect.Bmp, source); if (bitmap32 != null) { var bmp32Path = $"{pathPrefix}32bpp0.bmp"; File.WriteAllBytes(bmp32Path, bitmap32); } var bitmap32a = BmpEncoder.EncodeBitmap(context, BitmapEncoding.Pixel_argb32, BmpEncoder.Dialect.Bmp, source); if (bitmap32a != null) { var bmp32Path = $"{pathPrefix}32Abpp.bmp"; File.WriteAllBytes(bmp32Path, bitmap32a); } }
private static void DoLintFrame(IcoFrame frame, ParseContext context, CommandLineOptions opts) { context.GeneratedFrames.Add(frame); }
private static byte[] EncodeIndexedBitmap(ParseContext context, BitmapEncoding encoding, Dialect dialect, IcoFrame source) { var numBits = BmpUtil.GetBitDepthForPixelFormat(encoding); var colorTable = BuildColorTable(1u << numBits, context, source); if (colorTable == null) { context.LastEncodeError = IcoErrorCode.TooManyColorsForBitDepth; return(null); } var writer = new ByteWriter(ByteOrder.LittleEndian); EncodeBitmapHeader(source, dialect, encoding, colorTable, writer, out var offsetToImageSize); var reverseTable = new Dictionary <Rgba32, int>(); for (var i = 0; i < colorTable.Length; i++) { if (!reverseTable.ContainsKey(colorTable[i])) { reverseTable.Add(colorTable[i], i); } } var offsetToData = (uint)writer.Data.Count; var padding = writer.Data.Count % 4; for (var y = source.CookedData.Height - 1; y >= 0; y--) { var bits = new BitWriter(writer); for (var x = 0; x < source.CookedData.Width; x++) { var color = source.CookedData[x, y]; if (source.Mask[x, y]) { switch (context.MaskedImagePixelEmitOptions) { case StrictnessPolicy.Compliant: color = new Rgba32(0, 0, 0, 255); break; case StrictnessPolicy.PreserveSource: // Pass through whatever the original pixel was. break; case StrictnessPolicy.Loose: color = colorTable.First(); break; } } color.A = 255; var index = reverseTable[color]; bits.AddBits((uint)numBits, (byte)index); } while ((writer.Data.Count % 4) != padding) { writer.AddUint8(0); } } return(FinalizeBitmap(source, encoding, dialect, writer, offsetToData, offsetToImageSize)); }
private static byte[] EncodeRgbBitmap(IcoFrame source, ParseContext context, BitmapEncoding encoding, Dialect dialect) { var writer = new ByteWriter(ByteOrder.LittleEndian); EncodeBitmapHeader(source, dialect, encoding, null, writer, out var offsetToImageSize); var offsetToData = (uint)writer.Data.Count; var padding = writer.Data.Count % 4; for (var y = source.CookedData.Height - 1; y >= 0; y--) { var bits = new BitWriter(writer); for (var x = 0; x < source.CookedData.Width; x++) { var color = source.CookedData[x, y]; if (source.Mask[x, y]) { switch (context.MaskedImagePixelEmitOptions) { case StrictnessPolicy.Compliant: case StrictnessPolicy.Loose: color = new Rgba32(0, 0, 0, 0); break; case StrictnessPolicy.PreserveSource: // Pass through whatever the original pixel was. break; } } switch (encoding) { case BitmapEncoding.Pixel_rgb15: var value = X8To5(color.R) << 10 | X8To5(color.G) << 5 | X8To5(color.B); writer.AddUint16((ushort)value); break; case BitmapEncoding.Pixel_rgb24: writer.AddUint8(color.B); writer.AddUint8(color.G); writer.AddUint8(color.R); break; case BitmapEncoding.Pixel_0rgb32: writer.AddUint8(color.B); writer.AddUint8(color.G); writer.AddUint8(color.R); writer.AddUint8(0); break; case BitmapEncoding.Pixel_argb32: writer.AddUint8(color.B); writer.AddUint8(color.G); writer.AddUint8(color.R); writer.AddUint8(color.A); break; } } while ((writer.Data.Count % 4) != padding) { writer.AddUint8(0); } } return(FinalizeBitmap(source, encoding, dialect, writer, offsetToData, offsetToImageSize)); }
public static void DoBitmapEntry(ByteReader reader, ParseContext context, IcoFrame source) { var biSize = reader.NextUint32(); var biWidth = reader.NextInt32(); var biHeight = reader.NextInt32(); var biPlanes = reader.NextUint16(); var biBitCount = reader.NextUint16(); var biCompression = reader.NextUint32(); var biSizeImage = reader.NextUint32(); var biXPelsPerMeter = reader.NextInt32(); var biYPelsPerMeter = reader.NextInt32(); var biClrUsed = reader.NextUint32(); var biClrImportant = reader.NextUint32(); if (biSize != FileFormatConstants._bitmapInfoHeaderSize) { throw new InvalidIcoFileException(IcoErrorCode.InvalidBitapInfoHeader_ciSize, $"BITMAPINFOHEADER.ciSize should be {FileFormatConstants._bitmapInfoHeaderSize}, was {biSize}.", context); } if (biXPelsPerMeter != 0) { context.Reporter.WarnLine(IcoErrorCode.InvalidBitapInfoHeader_biXPelsPerMeter, $"BITMAPINFOHEADER.biXPelsPerMeter should be 0, was {biXPelsPerMeter}.", context.DisplayedPath, context.ImageDirectoryIndex.Value); } if (biYPelsPerMeter != 0) { context.Reporter.WarnLine(IcoErrorCode.InvalidBitapInfoHeader_biYPelsPerMeter, $"BITMAPINFOHEADER.biYPelsPerMeter should be 0, was {biYPelsPerMeter}.", context.DisplayedPath, context.ImageDirectoryIndex.Value); } if (biCompression == FileFormatConstants.BI_BITFIELDS) { throw new InvalidIcoFileException(IcoErrorCode.BitfieldCompressionNotSupported, $"This tool does not implement icon bitmaps that use BI_BITFIELDS compression. (The .ICO file may be okay, although it is certainly unusual.)", context); } if (biCompression != FileFormatConstants.BI_RGB) { throw new InvalidIcoFileException(IcoErrorCode.BitmapCompressionNotSupported, $"BITMAPINFOHEADER.biCompression is unknown value ({biCompression}).", context); } if (biHeight != source.Encoding.ClaimedHeight * 2) { context.Reporter.WarnLine(IcoErrorCode.MismatchedHeight, $"BITMAPINFOHEADER.biHeight is not exactly double ICONDIRECTORY.bHeight ({biHeight} != 2 * {source.Encoding.ClaimedHeight}).", context.DisplayedPath, context.ImageDirectoryIndex.Value); } if (biWidth != source.Encoding.ClaimedWidth) { context.Reporter.WarnLine(IcoErrorCode.MismatchedWidth, $"BITMAPINFOHEADER.biWidth is not exactly equal to ICONDIRECTORY.bWidth ({biWidth} != 2 * {source.Encoding.ClaimedWidth}).", context.DisplayedPath, context.ImageDirectoryIndex.Value); } var height = biHeight / 2; var width = biWidth; source.Encoding.ActualHeight = (uint)height; source.Encoding.ActualWidth = (uint)width; source.Encoding.ActualBitDepth = biBitCount; source.Encoding.Type = IcoEncodingType.Bitmap; source.CookedData = new Image <Rgba32>(width, height); switch (biBitCount) { case 1: case 2: case 4: case 8: ReadIndexedBitmap(reader, context, biBitCount, biClrUsed, height, width, source); break; case 16: ReadBitmap16(reader, context, height, width, source); break; case 24: ReadBitmap24(reader, context, biClrUsed, height, width, source); break; case 32: ReadBitmap32(reader, context, height, width, source); break; default: throw new InvalidIcoFileException(IcoErrorCode.InvalidBitapInfoHeader_biBitCount, $"BITMAPINFOHEADER.biBitCount is unknown value ({biBitCount}); expected 1, 4, 8, 16, or 32 bit depth.", context); } }
private static Rgba32[] BuildColorTable(uint maxColorTableSize, ParseContext context, IcoFrame source) { var colorTable = new Dictionary <Rgba32, uint>(); for (var y = source.CookedData.Height - 1; y >= 0; y--) { for (var x = 0; x < source.CookedData.Width; x++) { var color = source.CookedData[x, y]; if (source.Mask[x, y]) { switch (context.MaskedImagePixelEmitOptions) { case StrictnessPolicy.Compliant: // Ensure an entry is added for black. color = new Rgba32(0, 0, 0, 0); break; case StrictnessPolicy.PreserveSource: // Pass through whatever the original pixel was. break; case StrictnessPolicy.Loose: // Don't create a palette entry for this pixel. continue; } } color.A = 255; if (colorTable.ContainsKey(color)) { colorTable[color] += 1; } else if (colorTable.Count == maxColorTableSize) { return(null); } else { colorTable.Add(color, 1); } } } if (colorTable.Count == 0) { colorTable.Add(new Rgba32(0, 0, 0, 255), 1); } var table = (from c in colorTable orderby c.Value descending select c.Key).ToList(); var targetPaletteSize = 0u; switch (context.AllowPaletteTruncation) { case StrictnessPolicy.Compliant: targetPaletteSize = maxColorTableSize; break; case StrictnessPolicy.PreserveSource: targetPaletteSize = source.Encoding.PaletteSize; break; case StrictnessPolicy.Loose: targetPaletteSize = (uint)table.Count; break; } while (table.Count < targetPaletteSize) { table.Add(new Rgba32(0, 0, 0, 255)); } return(table.ToArray()); }
private static void WritePngFrame(CommandLineOptions opts, ParseContext context, IcoFrame frame) { using (var output = new MemoryStream()) { frame.CookedData.SaveAsPng(output, context.PngEncoder); output.Flush(); frame.RawData = output.GetBuffer(); frame.Encoding.ClaimedBitDepth = 32; frame.Encoding.ActualBitDepth = 32; } }
private static IcoFrame GetBestEncodingForFrame(ParseContext context, CommandLineOptions opts, IcoFrame source) { var result = new IcoFrameEncoding { ClaimedBitDepth = source.Encoding.ClaimedBitDepth, ClaimedHeight = source.Encoding.ClaimedHeight, ClaimedWidth = source.Encoding.ClaimedWidth, ActualHeight = (uint)source.CookedData.Height, ActualWidth = (uint)source.CookedData.Width, }; byte[] png = null; byte[] bitmap = null; var policy = opts.BestFormatPolicy; if (policy == BestFormatPolicy.Inherited) { policy = BestFormatPolicy.PreserveSource; } if (source.Encoding.ClaimedHeight == 16 && source.Encoding.ClaimedWidth == 16 && source.Encoding.ClaimedBitDepth == 32 && opts.BestFormatPolicy16x16x32 != BestFormatPolicy.Inherited) { policy = opts.BestFormatPolicy16x16x32; } if (source.Encoding.ClaimedHeight == 32 && source.Encoding.ClaimedWidth == 32 && source.Encoding.ClaimedBitDepth == 32 && opts.BestFormatPolicy32x32x32 != BestFormatPolicy.Inherited) { policy = opts.BestFormatPolicy32x32x32; } if (policy != BestFormatPolicy.AlwaysBmp && (source.Encoding.ClaimedBitDepth == 32 || opts.BestFormatPolicy == BestFormatPolicy.AlwaysPng)) { png = EncodePng(source, context); if (opts.PngToolPath != null) { png = ReprocessPngFile(png, context, opts); } } if (opts.BestFormatPolicy != BestFormatPolicy.AlwaysPng) { foreach (var encoding in _allEncodings) { if (encoding == BitmapEncoding.Pixel_indexed2) { switch (opts.Emit2BitBitmaps) { case StrictnessPolicy.Compliant: continue; case StrictnessPolicy.PreserveSource: if (source.Encoding.PixelFormat != BitmapEncoding.Pixel_indexed2) { continue; } break; case StrictnessPolicy.Loose: break; } } if (encoding == BitmapEncoding.Pixel_rgb24) { switch (opts.Emit24BitBitmaps) { case StrictnessPolicy.Compliant: continue; case StrictnessPolicy.PreserveSource: if (source.Encoding.PixelFormat != BitmapEncoding.Pixel_rgb24) { continue; } break; case StrictnessPolicy.Loose: break; } } if (source.Encoding.PixelFormat != encoding) { if (!opts.AllowDownsample) { continue; } // Going to rgb15 can lose some color data, due to rounding. Avoid unless the source // was already in rgb15. if (encoding == BitmapEncoding.Pixel_rgb15) { continue; } // Don't lose the per-pixel alpha channel, if one was in the source. if (source.Encoding.PixelFormat == BitmapEncoding.Pixel_argb32 && BmpUtil.IsAlphaSignificant(source)) { continue; } } bitmap = BmpEncoder.EncodeBitmap(context, encoding, BmpEncoder.Dialect.Ico, source); if (bitmap != null) { result.PixelFormat = encoding; break; } } } switch (opts.BestFormatPolicy) { case BestFormatPolicy.PreserveSource: result.Type = source.Encoding.Type; break; case BestFormatPolicy.MinimizeStorage: result.Type = (bitmap.Length < ((png?.Length ?? 0) + opts.PngSizePenalty)) ? IcoEncodingType.Bitmap : IcoEncodingType.Png; break; case BestFormatPolicy.PngLargeImages: result.Type = (source.CookedData.Width >= opts.LargeImagePixelThreshold) ? IcoEncodingType.Png : IcoEncodingType.Bitmap; break; case BestFormatPolicy.AlwaysPng: result.Type = IcoEncodingType.Png; break; case BestFormatPolicy.AlwaysBmp: result.Type = IcoEncodingType.Bitmap; break; default: break; } if (png == null && result.Type == IcoEncodingType.Png) { result.Type = IcoEncodingType.Bitmap; } var finalData = (result.Type == IcoEncodingType.Bitmap) ? bitmap : png; return(new IcoFrame { Encoding = result, RawData = finalData }); }
private static void WriteBmpFrame(CommandLineOptions opts, ParseContext context, IcoFrame frame) { var encoding = opts.BitmapEncodingOverride ?? BmpUtil.GetIdealBitmapEncoding(frame.CookedData, hasIcoMask: true); if (opts.MaskImagePath != null) { var maskSource = File.ReadAllBytes(opts.MaskImagePath); using (var stream = new MemoryStream(maskSource)) { var decoder = new SixLabors.ImageSharp.Formats.Bmp.BmpDecoder(); var mask = decoder.Decode <Rgba32>(new Configuration(), stream); if (mask.Width != frame.CookedData.Width || mask.Height != frame.CookedData.Height) { Reporter.ErrorLine( IcoErrorCode.BitmapMaskWrongDimensions, $"The mask's dimentions {mask.Height}x{mask.Width} don't match " + $"the frame dimensions {frame.CookedData.Height}x{frame.CookedData.Width}.", opts.MaskImagePath); throw new ArgumentException(); } for (var x = 0; x < mask.Width; x++) { for (var y = 0; y < mask.Height; y++) { if (mask[x, y].PackedValue != 0xffffffff && mask[x, y].PackedValue != 0x000000ff) { Reporter.ErrorLine( IcoErrorCode.BitampMaskWrongColors, $"The mask must be comprised entirely of black and white pixels (where black means transparent).", opts.MaskImagePath); throw new ArgumentException(); } } } frame.Mask = BmpUtil.CreateMaskFromImage(mask, blackIsTransparent: true); } } else { frame.Mask = BmpUtil.CreateMaskFromImage(frame.CookedData, blackIsTransparent: false); } frame.RawData = BmpEncoder.EncodeBitmap(context, encoding, BmpEncoder.Dialect.Ico, frame); if (frame.RawData == null) { Reporter.ErrorLine(context.LastEncodeError, $"Cannot encode the source image as a bitmap of type: {encoding}. Try reducing the number of colors, or changing the bitmap encoding.", opts.SourceImagePath); throw new ArgumentException(); } frame.Encoding.ClaimedBitDepth = (uint)BmpUtil.GetBitDepthForPixelFormat(encoding); frame.Encoding.ActualBitDepth = frame.Encoding.ClaimedBitDepth; }
private static IcoFrame GenerateFrame(CommandLineOptions opts, ParseContext context) { var frame = new IcoFrame { Encoding = new IcoFrameEncoding(), }; var sourceFile = File.ReadAllBytes(opts.SourceImagePath); var reader = new ByteReader(sourceFile, ByteOrder.LittleEndian); var signature = reader.NextUint64(); var sourceEncoding = PngDecoder.IsProbablyPngFile(ByteOrderConverter.To(ByteOrder.NetworkEndian, signature)) ? IcoEncodingType.Png : IcoEncodingType.Bitmap; bool canSourceBePreserved = false; switch (sourceEncoding) { case IcoEncodingType.Bitmap: ReadBmpFile(opts, context, frame, sourceFile); break; case IcoEncodingType.Png: ReadPngFile(opts, context, frame, sourceFile, out canSourceBePreserved); break; } frame.Encoding.ActualHeight = (uint)frame.CookedData.Height; frame.Encoding.ClaimedHeight = (uint)frame.CookedData.Height; frame.Encoding.ActualWidth = (uint)frame.CookedData.Width; frame.Encoding.ClaimedWidth = (uint)frame.CookedData.Width; var outputEncoding = opts.EncodingOverride ?? sourceEncoding; if (outputEncoding != IcoEncodingType.Bitmap && opts.BitmapEncodingOverride.HasValue) { Reporter.WarnLine(IcoErrorCode.OnlySupportedOnBitmaps, "Ignoring bitmap encoding configuration because we're not emitting a bitmap frame"); } if (outputEncoding != sourceEncoding) { canSourceBePreserved = false; } if (canSourceBePreserved || opts.KeepRawFrameData) { frame.RawData = sourceFile; } else { switch (outputEncoding) { case IcoEncodingType.Bitmap: WriteBmpFrame(opts, context, frame); break; case IcoEncodingType.Png: WritePngFrame(opts, context, frame); break; } } if (opts.BitDepthOverride.HasValue) { frame.Encoding.ClaimedBitDepth = (uint)opts.BitDepthOverride.Value; } return(frame); }
private static void ReadBitmap32(ByteReader reader, ParseContext context, int height, int width, IcoFrame source) { for (var y = height - 1; y >= 0; y--) { for (var x = 0; x < width; x++) { var colorValue = new Bgra32 { PackedValue = reader.NextUint32() }; source.CookedData[x, y] = colorValue.ToRgba32(); } } source.Encoding.PixelFormat = BmpUtil.IsAnyAlphaChannel(source.CookedData) ? BitmapEncoding.Pixel_argb32 : BitmapEncoding.Pixel_0rgb32; ReadBitmapMask(reader, context, height, width, source); }
private static void ReadBitmap24(ByteReader reader, ParseContext context, uint colorTableSize, int height, int width, IcoFrame source) { reader.SeekOffset += (int)colorTableSize * 4; for (var y = height - 1; y >= 0; y--) { for (var x = 0; x < width; x++) { var b = reader.NextUint8(); var g = reader.NextUint8(); var r = reader.NextUint8(); source.CookedData[x, y] = new Rgba32(r, g, b, 255); } } source.Encoding.PixelFormat = BitmapEncoding.Pixel_rgb24; ReadBitmapMask(reader, context, height, width, source); }
private static void ReadBitmap16(ByteReader reader, ParseContext context, int height, int width, IcoFrame source) { for (var y = height - 1; y >= 0; y--) { for (var x = 0; x < width; x++) { var colorValue = reader.NextUint16(); source.CookedData[x, y] = new Rgba32( _5To8[colorValue >> 10], _5To8[(colorValue >> 5) & 0x1f], _5To8[colorValue & 0x1f], 255); } } source.Encoding.PixelFormat = BitmapEncoding.Pixel_rgb15; ReadBitmapMask(reader, context, height, width, source); }
private static void ReadBmpFile(CommandLineOptions opts, ParseContext context, IcoFrame frame, byte[] sourceFile) { using (var stream = new MemoryStream(sourceFile)) { var decoder = new SixLabors.ImageSharp.Formats.Bmp.BmpDecoder(); frame.CookedData = decoder.Decode <Rgba32>(new Configuration(), stream); } frame.Encoding.Type = IcoEncodingType.Bitmap; }
private static void ReadIndexedBitmap(ByteReader reader, ParseContext context, uint bitDepth, uint colorTableSize, int height, int width, IcoFrame source) { var anyReservedChannel = false; var anyIndexOutOfBounds = false; if (colorTableSize == 0) { colorTableSize = 1u << (int)bitDepth; } source.Encoding.PaletteSize = colorTableSize; if (colorTableSize > 1u << (int)bitDepth) { throw new InvalidIcoFileException(IcoErrorCode.InvalidBitapInfoHeader_biClrUsed, $"BITMAPINFOHEADER.biClrUsed is greater than 2^biBitCount (biClrUsed == {colorTableSize}, biBitCount = {bitDepth}).", context); } else if (colorTableSize < 1u << (int)bitDepth) { context.Reporter.WarnLine(IcoErrorCode.UndersizedColorTable, $"This bitmap uses a color table that is smaller than the bit depth ({colorTableSize} < 2^{bitDepth})", context.DisplayedPath, context.ImageDirectoryIndex.Value); } var colorTable = new Rgba32[colorTableSize]; for (var i = 0; i < colorTableSize; i++) { var c = new Bgra32 { PackedValue = reader.NextUint32() }; if (c.A != 0) { anyReservedChannel = true; } c.A = 255; colorTable[i] = c.ToRgba32(); } var padding = reader.SeekOffset % 4; for (var y = height - 1; y >= 0; y--) { var bits = new BitReader(reader); for (var x = 0; x < width; x++) { var colorIndex = bits.NextBit(bitDepth); if (colorIndex >= colorTableSize) { anyIndexOutOfBounds = true; source.CookedData[x, y] = Rgba32.Black; } else { source.CookedData[x, y] = colorTable[colorIndex]; } } while ((reader.SeekOffset % 4) != padding) { reader.SeekOffset += 1; } } switch (bitDepth) { case 1: source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed1; break; case 2: source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed2; break; case 4: source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed4; break; case 8: source.Encoding.PixelFormat = BitmapEncoding.Pixel_indexed8; break; } ReadBitmapMask(reader, context, height, width, source); if (anyReservedChannel) { context.Reporter.WarnLine(IcoErrorCode.NonzeroAlpha, $"Reserved Alpha channel used in color table.", context.DisplayedPath, context.ImageDirectoryIndex.Value); } if (anyIndexOutOfBounds) { context.Reporter.WarnLine(IcoErrorCode.IndexedColorOutOfBounds, $"Bitmap uses color at illegal index; pixel filled with Black color.", context.DisplayedPath, context.ImageDirectoryIndex.Value); } }
private static void ReadPngFile(CommandLineOptions opts, ParseContext context, IcoFrame frame, byte[] sourceFile, out bool canSourceBePreserved) { using (var stream = new MemoryStream(sourceFile)) { var decoder = new SixLabors.ImageSharp.Formats.Png.PngDecoder(); frame.CookedData = decoder.Decode <Rgba32>(new Configuration(), stream); } var encoding = PngDecoder.GetPngFileEncoding(new Memory <byte>(sourceFile)); frame.Encoding.Type = IcoEncodingType.Png; frame.Encoding.ClaimedBitDepth = 32; frame.Encoding.ActualBitDepth = 32; canSourceBePreserved = encoding.ColorType == PngColorType.RGBA && encoding.BitsPerChannel == 8; }
public static byte[] EncodeBitmap(ParseContext context, BitmapEncoding encoding, Dialect dialect, IcoFrame source) { context.LastEncodeError = IcoErrorCode.NoError; return((BmpUtil.GetBitDepthForPixelFormat(encoding) < 16) ? EncodeIndexedBitmap(context, encoding, dialect, source) : EncodeRgbBitmap(source, context, encoding, dialect)); }
private static IcoFrame ProcessIcoFrame(ByteReader reader, ParseContext context) { var bWidth = reader.NextUint8(); var bHeight = reader.NextUint8(); var bColorCount = reader.NextUint8(); var bReserved = reader.NextUint8(); var wPlanes = reader.NextUint16(); var wBitCount = reader.NextUint16(); var dwBytesInRes = reader.NextUint32(); var dwImageOffset = reader.NextUint32(); if (bWidth != bHeight) { context.Reporter.WarnLine(IcoErrorCode.NotSquare, $"Icon is not square ({bWidth}x{bHeight}).", context.DisplayedPath, context.ImageDirectoryIndex.Value); } if (bReserved != FileFormatConstants._iconEntryReserved) { throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_bReserved, $"ICONDIRECTORY.bReserved should be {FileFormatConstants._iconEntryReserved}, was {bReserved}.", context); } if (wPlanes > 1) { throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_wPlanes, $"ICONDIRECTORY.wPlanes is {wPlanes}. Only single-plane bitmaps are supported.", context); } if (dwBytesInRes > int.MaxValue) { throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_dwBytesInRes, $"ICONDIRECTORY.dwBytesInRes == {dwBytesInRes}, which is unreasonably large.", context); } if (dwImageOffset > int.MaxValue) { throw new InvalidIcoFileException(IcoErrorCode.InvalidFrameHeader_dwImageOffset, $"ICONDIRECTORY.dwImageOffset == {dwImageOffset}, which is unreasonably large.", context); } var source = new IcoFrame { TotalDiskUsage = dwBytesInRes + /* sizeof(ICONDIRENTRY) */ 16, Encoding = new IcoFrameEncoding { ClaimedBitDepth = wBitCount, ClaimedHeight = bHeight > 0 ? bHeight : 256u, ClaimedWidth = bWidth > 0 ? bWidth : 256u, }, }; source.RawData = reader.Data.Slice((int)dwImageOffset, (int)dwBytesInRes).ToArray(); var bitmapHeader = new ByteReader(source.RawData, ByteOrder.LittleEndian); var signature = bitmapHeader.NextUint64(); bitmapHeader.SeekOffset = 0; if (PngDecoder.IsProbablyPngFile(ByteOrderConverter.To(ByteOrder.NetworkEndian, signature))) { PngDecoder.DoPngEntry(bitmapHeader, context, source); } else { BmpDecoder.DoBitmapEntry(bitmapHeader, context, source); } return(source); }
private static void ProcessFrame(ParseContext context, CommandLineOptions opts, IcoFrame source) { source.Encoding.ActualHeight = (uint)source.CookedData.Height; source.Encoding.ActualWidth = (uint)source.CookedData.Width; var frame = GetBestEncodingForFrame(context, opts, source); context.GeneratedFrames.Add(frame); if (opts.EmitBmpFilesForAllEncodings) { EmitAllPossibleEncodings(context, source); } }