示例#1
0
        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());
            }
        }
示例#2
0
        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]));
        }
示例#3
0
        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));
            }
        }
示例#4
0
        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());
        }
示例#5
0
        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);
        }
示例#6
0
        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);
                }
            }
        }
示例#7
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;
            }
        }
示例#8
0
        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();
        }
示例#9
0
        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;
        }
示例#10
0
        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);
            }
        }
示例#11
0
        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);
            }
        }
示例#12
0
 private static void DoLintFrame(IcoFrame frame, ParseContext context, CommandLineOptions opts)
 {
     context.GeneratedFrames.Add(frame);
 }
示例#13
0
        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));
        }
示例#14
0
        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));
        }
示例#15
0
        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);
            }
        }
示例#16
0
        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());
        }
示例#17
0
        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;
            }
        }
示例#18
0
        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
            });
        }
示例#19
0
        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;
        }
示例#20
0
        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);
        }
示例#21
0
        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);
        }
示例#22
0
        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);
        }
示例#23
0
        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);
        }
示例#24
0
        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;
        }
示例#25
0
        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);
            }
        }
示例#26
0
        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;
        }
示例#27
0
        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));
        }
示例#28
0
        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);
        }
示例#29
0
        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);
            }
        }