如何在 Windows 10 中使用 WriteableBitmapEx 提高更改图像像素颜色的性能?

How to improve performance of Changing Image Pixel colour using WriteableBitmapEx in Windows 10?

我目前正在处理我想要下载一组图像并将其像素颜色更改为红色、黄色等的部分。我做了一些研究并遇到了第三方 API称为 WriteableBitmapEx。我使用了以下代码,它给出了完美的结果,但 10 张图像需要超过 4-5 分钟。

public class ChangeImageColor
{
    public async Task<WriteableBitmap> downloadImageandChangeColor(string image_url, string hex_color)
    {
        Uri uri = new Uri(image_url);
        var fileName = getname(image_url);
        var bitmapImage = new BitmapImage();
        var httpClient = new HttpClient();
        var httpResponse = await httpClient.GetAsync(uri);
        byte[] b = await httpResponse.Content.ReadAsByteArrayAsync();

        // create a new in memory stream and datawriter
        var stream = new InMemoryRandomAccessStream();

        DataWriter dw = new DataWriter(stream);

        // write the raw bytes and store
        dw.WriteBytes(b);
        await dw.StoreAsync();

        // set the image source
        stream.Seek(0);
        bitmapImage.SetSource(stream);



        // read from pictures library
        stream.Seek(0);
        WriteableBitmap bitMap = await GetFileFromStorageandChangeColor(fileName, stream,hex_color);

        //StorageFile file = await WriteableBitmapToStorageFile(bitMap, FileFormat.Png, fileName);

        return bitMap;
    }


    public async Task<WriteableBitmap> GetFileFromStorageandChangeColor(string fileName, InMemoryRandomAccessStream pictureStream,string hex_color)
    {
        //var pictureFile = await KnownFolders.PicturesLibrary.GetFileAsync(fileName);
        WriteableBitmap writeableBitmap = null;
        //using (var pictureStream = await pictureFile.OpenAsync(FileAccessMode.Read))
        //{
        BitmapImage bmp = new BitmapImage();
        bmp.SetSource(pictureStream);
        // Load the picture in a WriteableBitmap
        writeableBitmap = new WriteableBitmap(bmp.PixelWidth, bmp.PixelHeight);
        pictureStream.Seek(0);
        writeableBitmap.SetSource(pictureStream);

        // Now we have to extract the pixels from the writeablebitmap
        // Get all pixel colors from the buffer
        byte[] pixelColors = writeableBitmap.PixelBuffer.ToArray();

        // Execute the filter on the color array
        //ApplyFilter(pixelColors);
        writeableBitmap = ChangeColor(writeableBitmap, hex_color);

        // Tell the image it needs a redraw
        writeableBitmap.Invalidate();
        // }
        return writeableBitmap;
    }

    public WriteableBitmap ChangeColor(WriteableBitmap scrBitmap, string hex_value)
    {
        //You can change your new colour here.
        Color newColor = MCSExtensions.GetColorFromHex(hex_value).Color;
        Color actualColor;

        //WriteableBitmap newBitmap = BitmapFactory.New(scrBitmap.PixelWidth, scrBitmap.PixelHeight);
        //newBitmap.ForEach((x, y, srcColor) => srcColor.A > 150 ? newColor : srcColor);
        //newBitmap.Invalidate();
        //make an empty bitmap the same size as scrBitmap
        WriteableBitmap newBitmap = new WriteableBitmap(scrBitmap.PixelWidth, scrBitmap.PixelHeight);
        for (int i = 0; i < scrBitmap.PixelWidth; i++)
        {
            for (int j = 0; j < scrBitmap.PixelHeight; j++)
            {
                //get the pixel from the scrBitmap image
                actualColor = scrBitmap.GetPixel(i, j);
                // > 150 because.. Images edges can be of low pixel colr. if we set all pixel color to new then there will be no smoothness left.
                if (actualColor.A > 0)
                    newBitmap.SetPixel(i, j, (Color)newColor);
                else
                    newBitmap.SetPixel(i, j, actualColor);
            }
        }

        return newBitmap;

    }

    private async Task<StorageFile> WriteableBitmapToStorageFile(WriteableBitmap WB, FileFormat fileFormat, string fileName)
    {
        string FileName = fileName.Replace(".png", "") + ".";
        Guid BitmapEncoderGuid = BitmapEncoder.JpegEncoderId;
        switch (fileFormat)
        {
            case FileFormat.Jpeg:
                FileName += "jpeg";
                BitmapEncoderGuid = BitmapEncoder.JpegEncoderId;
                break;
            case FileFormat.Png:
                FileName += "png";
                BitmapEncoderGuid = BitmapEncoder.PngEncoderId;
                break;
            case FileFormat.Bmp:
                FileName += "bmp";
                BitmapEncoderGuid = BitmapEncoder.BmpEncoderId;
                break;
            case FileFormat.Tiff:
                FileName += "tiff";
                BitmapEncoderGuid = BitmapEncoder.TiffEncoderId;
                break;
            case FileFormat.Gif:
                FileName += "gif";
                BitmapEncoderGuid = BitmapEncoder.GifEncoderId;
                break;
        }
        var file = await KnownFolders.PicturesLibrary.CreateFileAsync(
                    FileName,
                    CreationCollisionOption.ReplaceExisting);
        using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
        {
            BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoderGuid, stream);
            Stream pixelStream = WB.PixelBuffer.AsStream();
            byte[] pixels = new byte[pixelStream.Length];
            await pixelStream.ReadAsync(pixels, 0, pixels.Length);
            encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight,
                      (uint)WB.PixelWidth,
                      (uint)WB.PixelHeight,
                      96.0,
                      96.0,
                      pixels);
            await encoder.FlushAsync();
        }
        return file;
    }
    private enum FileFormat
    {
        Jpeg,
        Png,
        Bmp,
        Tiff,
        Gif
    }

    public static string getname(string name)
    {
        string image_name = string.Empty;
        image_name = (name).Substring(Math.Max(0, (name).Length - 20)).Replace(@"/", "_");
        return image_name;
    }

有人可以建议,如何改进优化我的代码以提供良好的性能并减少转换图像像素颜色的时间?

来自

The GetPixel and SetPixel extension methods are very expensive for multiple iterative changes, since they extract the BitmapContext (the WriteableBitmap's PixelBuffer), make the change, and then writes back the updated PixelBuffer when the call is done with the BitmapContext.

要改进这一点,请使用 WriteableBitmapEx 的 BitmapContext 对象来提取 PixelBuffer,然后根据需要在位图上下文中调用 Get 和 SetPixel。完成 setPixel 后,处置 BitmapContext。

因此,更新您的 ChangeColor 方法以添加 using(){},如下所示:

   using (newBitmap.GetBitmapContext())
   {
       for (int i = 0; i < scrBitmap.PixelWidth; i++)
       {
           for (int j = 0; j < scrBitmap.PixelHeight; j++)
           {

               actualColor = scrBitmap.GetPixel(i, j);
               // > 150 because.. Images edges can be of low pixel col
               if (actualColor.A > 0)
                   newBitmap.SetPixel(i, j, (Color)newColor);
               else
                   newBitmap.SetPixel(i, j, actualColor);

               //get the pixel from the scrBitmap image             
           }
       }
   }

我测试了你的代码,图片大小为 238px*220px。我之前花了大约40秒设置所有像素,更新后减少到大约20秒。

试错了这么多方法,终于找到了Windows Phone 8.1/Windows 10可用的BitmapIcon控件,有前景属性可用于更改图标颜色。我将它用于以下类型的图标,性能非常好,不需要太多代码。最好的部分是不需要第三方 API。

BitmapIcon 参考 BitmapIcon