public void AssignBestMatchColors()
        {
            Image <Bgr, byte> result = ColorQuantization.AssignBestMatchColors(image, colorTable);

            Assert.AreEqual(new Bgr(0, 0, 0), result[0, 0]);
            Assert.AreEqual(new Bgr(0, 0, 0), result[0, 1]);
            Assert.AreEqual(new Bgr(100, 100, 100), result[1, 0]);
            Assert.AreEqual(new Bgr(0, 0, 0), result[1, 1]);
        }
        public void BestMatch()
        {
            Assert.AreEqual(new Bgr(0, 0, 0), ColorQuantization.BestMatch(new Bgr(0, 0, 0), colorTable));
            Assert.AreEqual(new Bgr(0, 0, 0), ColorQuantization.BestMatch(new Bgr(0, 100, 0), colorTable));
            Assert.AreEqual(new Bgr(0, 0, 0), ColorQuantization.BestMatch(new Bgr(101, 0, 50), colorTable));
            Assert.AreEqual(new Bgr(0, 0, 0), ColorQuantization.BestMatch(new Bgr(50, 50, 50), colorTable));

            Assert.AreEqual(new Bgr(100, 100, 100), ColorQuantization.BestMatch(new Bgr(100, 100, 100), colorTable));
            Assert.AreEqual(new Bgr(100, 100, 100), ColorQuantization.BestMatch(new Bgr(100, 1, 50), colorTable));
        }
Beispiel #3
0
        static Texture CreateDecalTexture(string name, Image <Rgba32>[] images, TextureSettings textureSettings)
        {
            // Create any missing mipmaps (this does not affect the palette, so it can be done up-front):
            for (int i = 1; i < images.Length; i++)
            {
                if (images[i] == null)
                {
                    images[i] = images[0].Clone(context => context.Resize(images[0].Width >> i, images[0].Height >> i));
                }
            }

            // The last palette color determines the color of the decal. All other colors are irrelevant - palette indexes are treated as alpha values instead.
            var decalColor = textureSettings.DecalColor ?? ColorQuantization.GetAverageColor(ColorQuantization.GetColorHistogram(images, color => color.A == 0));
            var palette    = Enumerable.Range(0, 255)
                             .Select(i => new Rgba32((byte)i, (byte)i, (byte)i))
                             .Append(decalColor)
                             .ToArray();

            var textureData = images
                              .Select(CreateDecalTextureData)
                              .ToArray();

            return(Texture.CreateMipmapTexture(
                       name: name,
                       width: images[0].Width,
                       height: images[0].Height,
                       imageData: textureData[0],
                       palette: palette,
                       mipmap1Data: textureData[1],
                       mipmap2Data: textureData[2],
                       mipmap3Data: textureData[3]));


            byte[] CreateDecalTextureData(Image <Rgba32> image)
            {
                var mode            = textureSettings.DecalTransparencySource ?? DecalTransparencySource.AlphaChannel;
                var getPaletteIndex = (mode == DecalTransparencySource.AlphaChannel) ? (Func <Rgba32, byte>)(color => color.A) :
                                      (Func <Rgba32, byte>)(color => (byte)((color.R + color.G + color.B) / 3));

                var data = new byte[image.Width * image.Height];

                for (int y = 0; y < image.Height; y++)
                {
                    var rowSpan = image.GetPixelRowSpan(y);
                    for (int x = 0; x < image.Width; x++)
                    {
                        var color = rowSpan[x];
                        data[y * image.Width + x] = getPaletteIndex(color);
                    }
                }
                return(data);
            }
        }
Beispiel #4
0
        // TODO: Disable dithering for animated textures again? It doesn't actually remove all flickering, because different frames can still have different palettes...!
        static byte[] CreateTextureData(
            Image <Rgba32> image,
            Rgba32[] palette,
            IDictionary <Rgba32, int> colorIndexMappingCache,
            TextureSettings textureSettings,
            Func <Rgba32, bool> isTransparent,
            bool disableDithering)
        {
            var getColorIndex = ColorQuantization.CreateColorIndexLookup(palette, colorIndexMappingCache, isTransparent);

            var ditheringAlgorithm = textureSettings.DitheringAlgorithm ?? (disableDithering ? DitheringAlgorithm.None : DitheringAlgorithm.FloydSteinberg);

            switch (ditheringAlgorithm)
            {
            default:
            case DitheringAlgorithm.None:
                return(ApplyPaletteWithoutDithering());

            case DitheringAlgorithm.FloydSteinberg:
                return(Dithering.FloydSteinberg(image, palette, getColorIndex, textureSettings.DitherScale ?? 0.75f, isTransparent));
            }


            byte[] ApplyPaletteWithoutDithering()
            {
                var textureData = new byte[image.Width * image.Height];

                for (int y = 0; y < image.Height; y++)
                {
                    var rowSpan = image.GetPixelRowSpan(y);
                    for (int x = 0; x < image.Width; x++)
                    {
                        var color = rowSpan[x];
                        textureData[y * image.Width + x] = (byte)getColorIndex(color);
                    }
                }
                return(textureData);
            }
        }
Beispiel #5
0
 private bool QuantizeImage(Image <Bgr, byte> i)
 {
     quantizedImage = ColorQuantization.AssignBestMatchColors(i, testTable);
     return(true);
 }
Beispiel #6
0
        static Texture CreateTextureFromImage(string path, string textureName, TextureSettings textureSettings, bool isDecalsWad)
        {
            // Load the main texture image, and any available mipmap images:
            using (var images = new DisposableList <Image <Rgba32> >(GetMipmapFilePaths(path).Prepend(path)
                                                                     .Select(imagePath => File.Exists(imagePath) ? ImageReading.ReadImage(imagePath) : null)))
            {
                // Verify image sizes:
                if (images[0].Width % 16 != 0 || images[0].Height % 16 != 0)
                {
                    throw new InvalidDataException($"Texture '{path}' width or height is not a multiple of 16.");
                }

                for (int i = 1; i < images.Count; i++)
                {
                    if (images[i] != null && (images[i].Width != images[0].Width >> i || images[i].Height != images[0].Height >> i))
                    {
                        throw new InvalidDataException($"Mipmap {i} for texture '{path}' width or height does not match texture size.");
                    }
                }

                if (isDecalsWad)
                {
                    return(CreateDecalTexture(textureName, images.ToArray(), textureSettings));
                }


                var filename             = Path.GetFileName(path);
                var isTransparentTexture = filename.StartsWith("{");
                var isAnimatedTexture    = AnimatedTextureNameRegex.IsMatch(filename);
                var isWaterTexture       = filename.StartsWith("!");

                // Create a suitable palette, taking special texture types into account:
                var transparencyThreshold = isTransparentTexture ? Clamp(textureSettings.TransparencyThreshold ?? 128, 0, 255) : 0;
                Func <Rgba32, bool> isTransparentPredicate = null;
                if (textureSettings.TransparencyColor != null)
                {
                    var transparencyColor = textureSettings.TransparencyColor.Value;
                    isTransparentPredicate = color => color.A < transparencyThreshold || (color.R == transparencyColor.R && color.G == transparencyColor.G && color.B == transparencyColor.B);
                }
                else
                {
                    isTransparentPredicate = color => color.A < transparencyThreshold;
                }

                var colorHistogram = ColorQuantization.GetColorHistogram(images.Where(image => image != null), isTransparentPredicate);
                var maxColors      = 256 - (isTransparentTexture ? 1 : 0) - (isWaterTexture ? 2 : 0);
                var colorClusters  = ColorQuantization.GetColorClusters(colorHistogram, maxColors);

                // Always make sure we've got a 256-color palette (some tools can't handle smaller palettes):
                if (colorClusters.Length < maxColors)
                {
                    colorClusters = colorClusters
                                    .Concat(Enumerable
                                            .Range(0, maxColors - colorClusters.Length)
                                            .Select(i => (new Rgba32(), new[] { new Rgba32() })))
                                    .ToArray();
                }

                // Make palette adjustments for special textures:
                if (isWaterTexture)
                {
                    var fogColor     = textureSettings.WaterFogColor ?? ColorQuantization.GetAverageColor(colorHistogram);
                    var fogIntensity = new Rgba32((byte)Clamp(textureSettings.WaterFogColor?.A ?? (int)((1f - GetBrightness(fogColor)) * 255), 0, 255), 0, 0);

                    colorClusters = colorClusters.Take(3)
                                    .Append((fogColor, new[] { fogColor }))         // Slot 3: water fog color
                                    .Append((fogIntensity, new[] { fogIntensity })) // Slot 4: fog intensity (stored in red channel)
                                    .Concat(colorClusters.Skip(3))
                                    .ToArray();
                }

                if (isTransparentTexture)
                {
                    var colorKey = new Rgba32(0, 0, 255);
                    colorClusters = colorClusters
                                    .Append((colorKey, new[] { colorKey })) // Slot 255: used for transparent pixels
                                    .ToArray();
                }

                // Create the actual palette, and a color index lookup cache:
                var palette = colorClusters
                              .Select(cluster => cluster.Item1)
                              .ToArray();
                var colorIndexMappingCache = new Dictionary <Rgba32, int>();
                for (int i = 0; i < colorClusters.Length; i++)
                {
                    (_, var colors) = colorClusters[i];
                    foreach (var color in colors)
                    {
                        colorIndexMappingCache[color] = i;
                    }
                }

                // Create any missing mipmaps:
                for (int i = 1; i < images.Count; i++)
                {
                    if (images[i] == null)
                    {
                        images[i] = images[0].Clone(context => context.Resize(images[0].Width >> i, images[0].Height >> i));
                    }
                }

                // Create texture data:
                var textureData = images
                                  .Select(image => CreateTextureData(image, palette, colorIndexMappingCache, textureSettings, isTransparentPredicate, disableDithering: isAnimatedTexture))
                                  .ToArray();

                return(Texture.CreateMipmapTexture(
                           name: textureName,
                           width: images[0].Width,
                           height: images[0].Height,
                           imageData: textureData[0],
                           palette: palette,
                           mipmap1Data: textureData[1],
                           mipmap2Data: textureData[2],
                           mipmap3Data: textureData[3]));
            }
        }