cmksoftdev / AnimatedPngCreator

Creates animated PNG out ouf a sequence of Images.
GNU General Public License v3.0
13 stars 3 forks source link

Slow image change analysis with solution #16

Open ez2X8pk61DHkdztgFT6O opened 2 years ago

ez2X8pk61DHkdztgFT6O commented 2 years ago

Thanks for your code! I noticed that checking for image changes was the slowest part due to the use of GetPixel and SetPixel. The code below passes your tests and is much faster due to the use of 'LockBits' and direct access to the data. Requires the build properties to be set to 'allow unsafe code', though.

using System.Drawing;

namespace CMK { internal class ImageChangeAnalyser { private Bitmap oldImage = null;

    public unsafe Image BlackoutImage(Image newImage, out bool equal)
    {
        equal = false;
        if (newImage == null)
        {
            return null;
        }
        // Make sure we have a bitmap
        var newImageBmp = new Bitmap(newImage);
        // Copy of new image
        var copy_new_image = new Bitmap(newImageBmp);
        // Dimensions
        var x = newImage.Width;            
        var y = newImage.Height;
        var x4 = 4 * x; // argb format

        // Something to do?
        if (oldImage != null)
        {
            // Lock bits of old and new image
            var o_img = oldImage.LockBits(new Rectangle(0, 0, x, y), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            var n_img = newImageBmp.LockBits(new Rectangle(0, 0, x, y), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            // To track equality
            bool is_equal = true;

            for (int j = 0; j < y; j++)
            {
                byte* o_row = ((byte *) o_img.Scan0) + o_img.Stride * j;
                byte* n_row = ((byte * )n_img.Scan0) + n_img.Stride * j;
                for (int i = 0; i < x4; i+=4)
                {                    
                    // Compare b, g, and r values (+0, +1, +2)
                    if (o_row[i+0] == n_row[i+0] && o_row[i+1] == n_row[i+1] && o_row[i+2] == n_row[i+2])
                    {   
                        // For compatibility with tests
                        n_row[i + 0] = 255;
                        n_row[i + 1] = 255;
                        n_row[i + 2] = 255;
                        // Set 'A' value(+3) of new image to 0: fully transparent (whatever the other colors!)
                        n_row[i + 3] = 0;
                    }
                    else
                    {
                        is_equal = false;
                    }
                }
            }
            // Unlock bits
            oldImage.UnlockBits(o_img);
            newImageBmp.UnlockBits(n_img);
            // Update equality
            equal = is_equal;
        }
        // old image is current new image (but without our transparancy changes, so use copy0
        oldImage = copy_new_image;            
        // Return bitmap with transparance changes
        return newImageBmp;
    }

}

}

ez2X8pk61DHkdztgFT6O commented 2 years ago

As an afterthought: although the above code always 'runs', the transparency is only changed when the original image had an alpha channel to start with (not sure how your code handles this, maybe GDI+ automagically adds alpha?). Otherwise the alpha channel changes will do nothing as the 'unlockbits' will write back all data except the alpha channel values. To make things full-proof one would need to check whether the original image has an alpha channel. And if not, do something like this:

    /// <summary>
    /// Convert image to 32bpp argb. Disposes of the existing images!
    /// </summary>
    /// <param name="a"></param>
    /// <returns></returns>
    private Image ToARGB(Image a)
    {
        Bitmap clone = new Bitmap(a.Width, a.Height, PixelFormat.Format32bppPArgb);
        using (Graphics gr = Graphics.FromImage(clone))
        {
            gr.DrawImage(a, new Rectangle(0, 0, clone.Width, clone.Height));
        }
        a.Dispose();
        return clone;
    }
cmk1988 commented 2 years ago

Thank you! I will review it on weekend. Do you want to create a pull request for the changes?

ez2X8pk61DHkdztgFT6O commented 2 years ago
Hello, No, not really. Just want to help make it a bit faster. If you decide to implement you likely want to make sure that the bitmap you will be writing in (transparency) really has a transparency channel, for example by replacing “var newImageBmp = new Bitmap(newImage);” by a variation of the ‘ToARGB’ function. Possibly checking first whether it is needed at all.      From: cmk1988Sent: donderdag 6 januari 2022 16:44To: cmksoftdev/AnimatedPngCreatorCc: ez2X8pk61DHkdztgFT6O; AuthorSubject: Re: [cmksoftdev/AnimatedPngCreator] Slow image change analysis with solution (Issue #16) Thank you! I will review it on weekend. Do you want to create a pull request for the changes?—Reply to this email directly, view it on GitHub, or unsubscribe.Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you authored the thread.Message ID: ***@***.***>