0

I am currently working on the part where I want to download a set of images and change their pixel colour for example to red, yellow, etc. I did some research and came across a third party API called WriteableBitmapEx. I used the following code and it gives the perfect result but it takes more than 4-5 minutes for 10 images.

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;
    }

Can someone suggest, how to improve optimize my code so it gives good performance and time to convert the pixel color of images reduces?

Kinjan Bhavsar
  • 1,439
  • 28
  • 58
  • You can change pixelcolors using the BackBuffer directly: Here's some more info: http://stackoverflow.com/questions/20181132/edit-raw-pixel-data-of-writeablebitmap – Jeroen van Langen May 09 '16 at 07:25
  • ok but according to you at which line I need to do change? – Kinjan Bhavsar May 09 '16 at 07:27
  • The problem is that you call `newBitmap.SetPixel(i, j, (Color)newColor);` each pixel. Every time you modify a pixel, you're calling a function twice and also the position in memory (x + (y*stride)) is calculated twice. which is very expensive. So the trick is, to get a pointer to the backbuffer create some loops and assign the pixel values to it. increase the pointer position and loop again. Just study the code on the link. – Jeroen van Langen May 09 '16 at 07:35
  • if I use `foreach` then? – Kinjan Bhavsar May 09 '16 at 10:00
  • Use a `foreach` to iterate a collection/enumerable, which is not the case here. You should understand why the use of `SetPixel` is slow, try read more. There are plenty of examples out there. – Jeroen van Langen May 09 '16 at 10:05
  • I checked the backbuffer part but didn't understood which I can change pixel color using it? – Kinjan Bhavsar May 09 '16 at 11:27

2 Answers2

1

After doing so much trial and error methods, I found BitmapIcon control which is available for Windows Phone 8.1/Windows 10. It has a foreground property which can be used to change icon color. I used it for following types of icons and the performance was really good and not much code needed. The best part is no third party API required.

Icon

BitmapIcon Reference BitmapIcon

Kinjan Bhavsar
  • 1,439
  • 28
  • 58
0

From this thread,

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.

To improve this, use WriteableBitmapEx's BitmapContext object to extract the PixelBuffer then call Get and SetPixel as often as needed on the bitmap context. When done with setPixel, dispose the BitmapContext.

So, update your ChangeColor method to add using(){} as follows:

   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             
           }
       }
   }

I have tested your code with a image's size is 238px*220px. I spent about 40 seconds to set all the pixels before, after updating it decrease to about 20 seconds.

Community
  • 1
  • 1
Sunteen Wu
  • 10,509
  • 1
  • 10
  • 21
  • Thanks will try this. Just I need to add `using(newBitmap.GetBitmapContext()){}` that's it right? Any other change is required? – Kinjan Bhavsar May 19 '16 at 09:21
  • No, just add this, you will find at least increase half the performance. If it helped, remember to back to mark as answer. – Sunteen Wu May 19 '16 at 09:23
  • `var begintime = System.DateTime.Now; System.Diagnostics.Debug.WriteLine("BeginTime" + begintime);` Add this before the above code block. Add the same code in the end of the code block. – Sunteen Wu May 19 '16 at 09:39
  • Thanks. It is showing good performance now. Can we improve little more performance as I need to download the image and then change colour? Check my entire code and let me know if you have any suggestions? – Kinjan Bhavsar May 19 '16 at 09:42
  • @KinjanBhavsar I think it will be difficult. Since the download speed limited by your image size and you net speed. Also the set image pixels very limited by the image size. I'm using a 40px*40px, it is very fast. You are using the third party package which already have the better performance then the API self. The only way I can think of is to use multiply threads. But you have already use the asynchronous method. – Sunteen Wu May 19 '16 at 09:53
  • Ok Thanks. I can notice the difference in desktop app. I will test it in mobile app and will mark as answer. – Kinjan Bhavsar May 19 '16 at 09:54
  • it taking more time on the mobile device. And can you suggest where I need to dispose code for BitmapContext and how to dispose of it? – Kinjan Bhavsar May 19 '16 at 11:32
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112456/discussion-between-sunteen-and-kinjan-bhavsar). – Sunteen Wu May 20 '16 at 02:09