使用Xamarin更改UIImage颜色

sqyvllje  于 2022-12-07  发布在  其他
关注(0)|答案(2)|浏览(155)

我正在使用Xamarin Forms,我有一个UIImage(在一个自定义渲染器中),我从一个文件中加载它,并将它用作一个模式。该图像是一个4x16像素的图像,其中有两个4x4像素的黑色区域和一个4x8像素的透明区域:

我需要动态地改变黑色区域的颜色。
这是我尝试过的,没有任何成功:

UIImage image = UIImage.FromFile("line_pattern.png");
image = image.ApplyTintColor(UIColor.Orange,UIImageRenderingMode.AlwaysTemplate);

图像加载正确,但颜色没有改变。我该怎么做?

xvw2m8pv

xvw2m8pv1#

试着将tintColor设置为imageView:

UIImageView imageView = new UIImageView();
    UIImage image = UIImage.FromFile("line_pattern.png");
    image = image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
    image = image.ApplyTintColor(UIColor.Orange, UIImageRenderingMode.AlwaysTemplate);

    imageView.Image = image;
    imageView.TintColor = UIColor.Orange;
wf82jlnq

wf82jlnq2#

设置色调颜色适用于具有单一颜色的PNG。
然而,我们希望能够改变从JPG或PNG加载的UIImage对象中的单个颜色,这些对象可能有多种颜色。我们的目标图像是图标或类似的图像,它们的不同颜色数量相对有限,将一种颜色替换为另一种颜色似乎应该很简单,但我们在网上找到的解决方案都没有提供我们所需要的。
因此,为了替换颜色,我们创建了以下从CIColorCube派生的滤镜类。我们将其用于颜色数量相对有限的图像,但也可以用于更复杂的图像。如果没有得到想要的结果,请尝试使用CubeDimensionIndexPlusMinus属性,看看它们对图像的影响。
这段代码大致基于Apple Developer Chroma Key Filter Recipe示例。其中用于设置颜色Map表的逻辑可能更适用于照片等复杂图像。
最后,对于从Xamarin更新到MAUI的任何人,只需将一个nfloat引用更改为NFloat,此代码就可以在MAUI上工作。

UPDATEsushihangover/ColorReplaceFilter.cs也可以用来替换颜色。事实上,我们最初尝试使用sushihangover代码,但遇到了问题,所以我们创建了颜色立方体过滤器。最终,我们使用sushihangover过滤器遇到的问题是我们自己造成的;一旦我们弄清楚了这一点,它就能很好地工作。彩色立方体滤镜和Sushihangover滤镜的性能似乎差不多,所以任何一种滤镜都能完成这项工作。

public class iOSReplaceColorFilter : CIColorCube
{
    /// <summary>
    /// The color to replace with NewColor. If not set, the color of all non-transparent 
    /// pixels in the image will be changed to NewColor.
    /// </summary>
    public CIColor OldColor { get; set; }

    /// <summary>
    /// The new color used to replace the old color.
    /// </summary>
    public CIColor NewColor { get; set; }

    /// <summary>
    /// An offset that is applied when calculating which color cube index values match
    /// the old color. Set this to 1 to avoid interpolation of the new color when the 
    /// RGB color indices of the old color do not exactly resolve to integer values.
    /// </summary>
    public int IndexPlusMinus { get; set; } = 1;

    /// <summary>
    /// Default constructor. To use this, you must explictly set OldColor, NewColor, and 
    /// the CubeDimension, then call InitCube before getting the OutputImage.
    /// </summary>
    public iOSReplaceColorFilter()
    {
    }

    /// <summary>
    /// Create a replacement filter that uses the specified cube dimension for the color 
    /// cube map. The default dimension is 16, but cube dimensions as small as 2 can be 
    /// used. When replacing a color in an image with 2 or more distinct colors, the cube
    /// dimension determines how close two colors' RGB values can be without introducing 
    /// interpolation effects.
    /// </summary>
    public iOSReplaceColorFilter(CIColor oldColor, CIColor newColor, int cubeDimension = 16)
    {
        OldColor = oldColor;
        NewColor = newColor;
        CubeDimension = cubeDimension;
        InitCube();
    }

    /// <summary>
    /// Create a replacement filter for replacing all non-transparent pixels in the image
    /// with the specified color.
    /// </summary>
    public iOSReplaceColorFilter(CIColor newColor)
    {
        NewColor = newColor;
        CubeDimension = 2;
        InitCube();
    }

    /// <summary>
    /// Build the color cube. This must be called before using OutputImage to get 
    /// the converted image.
    /// </summary>
    public virtual iOSReplaceColorFilter InitCube()
    {
        var dim = (int)CubeDimension;
        var dimFactor = (float)(dim - 1);

        var rangeChecker = new IndexRangeChecker(OldColor, dimFactor, IndexPlusMinus);
        var offset = 0;

        var cubeData = new float[dim * dim * dim * 4];
        for (var b = 0; b < dim; ++b)
        {
            var blue = b / dimFactor;
            for (var g = 0; g < dim; ++g)
            {
                var green = g / dimFactor;
                for (var r = 0; r < dim; ++r)
                {
                    var red = r / dimFactor;
                    if (NewColor != null && rangeChecker.Matches(r, g, b))
                    {
                        cubeData[offset++] = (float)NewColor.Red;
                        cubeData[offset++] = (float)NewColor.Green;
                        cubeData[offset++] = (float)NewColor.Blue;
                    }
                    else
                    {
                        cubeData[offset++] = red;
                        cubeData[offset++] = green;
                        cubeData[offset++] = blue;
                    }
                    cubeData[offset++] = 1.0f;
                }
            }
        }

        var byteArray = new byte[cubeData.Length * 4];
        Buffer.BlockCopy(cubeData, 0, byteArray, 0, byteArray.Length);
        var data = NSData.FromArray(byteArray);

        CubeData = data;

        return this;
    }

    /// <summary>
    /// Checks to see if an r,g,b index set falls within the range of cube indices 
    /// that should be considered a "hit" on the target color. If the checker is 
    /// created with a null color, then all indices match.
    /// </summary>
    private class IndexRangeChecker
    {
        private IndexRange rRange;
        private IndexRange gRange;
        private IndexRange bRange;

        public IndexRangeChecker(CIColor color, float dimFactor, int indexPlusMinus)
        {
            if (color != null)
            {
                rRange = new IndexRange(color.Red, dimFactor, indexPlusMinus);
                gRange = new IndexRange(color.Green, dimFactor, indexPlusMinus);
                bRange = new IndexRange(color.Blue, dimFactor, indexPlusMinus);
            }
        }

        public bool Matches(int r, int g, int b)
        {
            if (rRange != null)
                return rRange.Matches(r) && gRange.Matches(g) && bRange.Matches(b);
            else
                return true;
        }

        private class IndexRange
        {
            private int start;
            private int end;

            public IndexRange(nfloat colorValue, float dimFactor, int offset)
            {
                var indx = (int)Math.Round(dimFactor * colorValue);
                start = indx - offset;
                end = indx + offset;
            }

            public bool Matches(int indx) => start <= indx && indx <= end;
        }
    }

    /// <summary>
    /// Convenience function that creates and applies a iOSReplaceColorFilter to 
    /// an image and returns the resulting image.
    /// </summary>
    public static UIImage ReplaceColor(UIImage inputImage, CIColor oldColor, CIColor newColor)
    {
        var filter = new iOSReplaceColorFilter(oldColor, newColor);
        filter.InputImage = new CIImage(inputImage);
        var outputImage = iOSColorCubeUtil.RenderFilteredImage(filter.OutputImage, inputImage);
        return outputImage;
    }
}

public class iOSColorCubeUtil
{
    /// <summary>
    /// Render an image after all filters have been applied. If you have more than one
    /// filter, the output of each should be passed to the next filter, and only the final
    /// filter result should be passed to this function. 
    /// 
    /// For example, to apply a sepia tone filter and then an invert filter, do this:
    /// 
    ///     var sepiaFilter  = new CISepiaTone { Intensity = 0.8f, InputImage = myImage };
    ///     var invertFilter = new CIColorInvert { InputImage = sepiaFilter.OutputImage };
    ///     var finalImage = RenderFilteredImage(invertFilter.OutputImage);
    ///     
    /// The originalImage is needed as a argument in order to render the image using the 
    /// same scale factor and orientation as the original image.
    /// </summary>
    public static UIImage RenderFilteredImage(CIImage filteredImage, UIImage originalImage)
    {
        // The ColorSpace MUST be set in order for the CIColorCube mapping to work. If 
        // the color space isn't set, CIColorCube-based color swapping only worked for
        // target colors whose RGB color values were all either 0x00 or 0xFF (e.g. #000000,
        // #FFFFFF, #FF0000, #FF00FF, etc.)

        var rgbColorSpace = CGColorSpace.CreateDeviceRGB();
        var options = new CIContextOptions
        {
            WorkingColorSpace = rgbColorSpace,
            OutputColorSpace = rgbColorSpace
        };

        var context = CIContext.FromOptions(options);
        var cgImage = context.CreateCGImage(filteredImage, filteredImage.Extent);
        var fixedImage = UIImage.FromImage(cgImage, originalImage.CurrentScale, originalImage.Orientation);
        return fixedImage;
    }
}

相关问题