none
How to "add" two images so that resulting is the difference of the two RRS feed

  • Question

  • I have tried using a bit by bit comparison of two bitmaps in order to find different pixels and draw the difference into yet another transparent bitmap, so that the resulting image is the difference of the 2 compared images. However, with large images the processing is very slow. I am sure there is a faster way of achieving the same. Could you please suggest? Thanks.

            Dim Bitmap1 As New Bitmap("c:\Bitmap1.bmp")
            Dim Bitmap2 As New Bitmap("c:\Bitmap2.bmp")
            Dim BitmapDelta As New Bitmap(Bitmap1.Width, Bitmap1.Height)
            Dim GraphDelta As Graphics = Graphics.FromImage(BitmapDelta)
            GraphDelta.Clear(Color.Transparent)
            For x = 0 To Bitmap1.Width - 1
                For y = 0 To Bitmap1.Height - 1
                    If Not Bitmap1.GetPixel(x, y).Equals(Bitmap2.GetPixel(x, y)) Then
                        GraphDelta.DrawRectangle(New Pen(Bitmap2.GetPixel(x, y), 1), x, y, 1, 1)
                    End If
                Next
            Next
            BitmapDelta.Save("c:\BitmapDelta.png", Imaging.ImageFormat.Png)

    Saturday, August 5, 2017 4:10 PM

All replies

  • Imaxus, 

    Probably I've to wait on Tommy. But for me it looks impossible. 

    You have 2 bitmaps and somewhere they are unequal. 

    But what is unequal. If there is a one pixel shift to the left then what is different, the part at the left or the part at the right side?

    Maybe can you show a sample with some commercial software what you try to achieve. 


    Success
    Cor

    Saturday, August 5, 2017 5:38 PM
  • If they have same dimensions, you can use BitBlt() (PInvoke) with ROP like SRCINVERT

    • Edited by Castorix31 Saturday, August 5, 2017 6:06 PM
    Saturday, August 5, 2017 6:05 PM
  • Hi,

    copy all Bitmap-bits with Marshal.Copy to three byte-Arrays, get the difference of the first two and set these in the third array, copy the third array back to the tranparent bitmap and show this.

    I'll do an example, if not someone else is faster....

    Regards,

      Thorsten

    Saturday, August 5, 2017 9:11 PM
  • Hi,

    example (for same sized images, if sizes differ, you need to get the overlap-rectangle first)

    Option Strict On
    Imports System.Runtime.InteropServices
    
    Partial Public Class Form1
        Inherits Form
        Private _rnd As New Random()
    
        Public Sub New()
            InitializeComponent()
            Init()
        End Sub
    
        Private Sub Form1_Load(sender As Object, e As EventArgs)
            Dim bmp As New Bitmap(Me.pictureBox1.ClientSize.Width, Me.pictureBox1.ClientSize.Height)
            Using g As Graphics = Graphics.FromImage(bmp)
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias
                g.Clear(Color.Green)
                For i As Integer = 0 To 3
                    Using sb As New SolidBrush(Color.FromArgb(_rnd.[Next](256), _rnd.[Next](256), _rnd.[Next](256), _rnd.[Next](256)))
                        g.FillEllipse(sb, New RectangleF(_rnd.[Next](bmp.Width \ 2), _rnd.[Next](bmp.Width \ 2), _rnd.[Next](bmp.Width \ 2), _rnd.[Next](bmp.Width \ 2)))
                    End Using
                Next
            End Using
    
            Dim bmp2 As Bitmap = DirectCast(bmp.Clone(), Bitmap)
            Using g As Graphics = Graphics.FromImage(bmp2)
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias
    
                Using sb As New SolidBrush(Color.FromArgb(_rnd.[Next](256), _rnd.[Next](256), _rnd.[Next](256), _rnd.[Next](256)))
                    g.FillEllipse(sb, New RectangleF(_rnd.[Next](bmp2.Width \ 2), _rnd.[Next](bmp2.Width \ 2), _rnd.[Next](bmp2.Width), _rnd.[Next](bmp2.Width)))
                End Using
            End Using
    
            Me.pictureBox1.Image = bmp
            Me.pictureBox2.Image = bmp2
    
        End Sub
    
        Private Sub button1_Click(sender As Object, e As EventArgs)
            Me.pictureBox3.Image = CompareBitmaps(DirectCast(Me.pictureBox1.Image, Bitmap), DirectCast(Me.pictureBox2.Image, Bitmap))
            Me.pictureBox3.Refresh()
        End Sub
    
        Private Function CompareBitmaps(image1 As Bitmap, image2 As Bitmap) As Image
            If image1.Width <> image2.Width OrElse image1.Height <> image2.Height Then
                Throw New Exception("Bitmaps must be of same size.")
            End If
    
            Dim bmData As System.Drawing.Imaging.BitmapData = Nothing
            Dim bmData2 As System.Drawing.Imaging.BitmapData = Nothing
    
            Dim bmpOUt As New Bitmap(image1.Width, image1.Height)
            Dim bmDataOut As System.Drawing.Imaging.BitmapData = Nothing
    
            Try
                bmData = image1.LockBits(New Rectangle(0, 0, image1.Width, image1.Height), System.Drawing.Imaging.ImageLockMode.[ReadOnly], System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
                bmData2 = image2.LockBits(New Rectangle(0, 0, image2.Width, image2.Height), System.Drawing.Imaging.ImageLockMode.[ReadOnly], System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
                bmDataOut = bmpOUt.LockBits(New Rectangle(0, 0, bmpOUt.Width, bmpOUt.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
    
                Dim stride As Integer = bmData.Stride
                Dim nW As Integer = image1.Width
                Dim nH As Integer = image1.Height
    
                Dim p(stride * nH - 1) As Byte
                Dim p2(stride * nH - 1) As Byte
                Dim pOut(stride * nH - 1) As Byte
    
                Marshal.Copy(bmData.Scan0, p, 0, p.Length)
                Marshal.Copy(bmData2.Scan0, p2, 0, p2.Length)
                Marshal.Copy(bmDataOut.Scan0, pOut, 0, pOut.Length)
    
                Parallel.[For](0, nH, Sub(y)
                                          Dim pos As Integer = y * stride
                                          Dim pos2 As Integer = y * stride
                                          Dim posOut As Integer = y * stride
    
                                          For x As Integer = 0 To nW - 1
                                              If p(pos) <> p2(pos2) OrElse p(pos + 1) <> p2(pos2 + 1) OrElse p(pos + 2) <> p2(pos2 + 2) OrElse p(pos + 3) <> p2(pos2 + 3) Then
                                                  For i As Integer = 0 To 2
                                                      'pOut(posOut + i) = CByte(Math.Abs(CInt(p(pos + i)) - CInt(p2(pos2 + i)))) 'real difference
                                                      pOut(posOut + i) = p2(pos2 + i)
                                                  Next
    
                                                  'set opaque
                                                  pOut(posOut + 3) = CByte(255)
                                              End If
    
                                              pos += 4
                                              pos2 += 4
                                              posOut += 4
                                          Next
    
                                      End Sub)
    
                Marshal.Copy(pOut, 0, bmDataOut.Scan0, pOut.Length)
    
                image1.UnlockBits(bmData)
                image2.UnlockBits(bmData2)
                bmpOUt.UnlockBits(bmDataOut)
    
                p = Nothing
                p2 = Nothing
                pOut = Nothing
            Catch
                Try
                    image1.UnlockBits(bmData)
    
                Catch
                End Try
                Try
                    image2.UnlockBits(bmData2)
    
                Catch
                End Try
                Try
                    bmpOUt.UnlockBits(bmDataOut)
    
                Catch
                End Try
            End Try
    
            Return bmpOUt
        End Function
    
        'usually done with the Designer
        Private button1 As System.Windows.Forms.Button
        Private pictureBox1 As System.Windows.Forms.PictureBox
        Private pictureBox2 As System.Windows.Forms.PictureBox
        Private pictureBox3 As System.Windows.Forms.PictureBox
    
        Private Sub Init()
            Me.button1 = New System.Windows.Forms.Button()
            Me.pictureBox1 = New System.Windows.Forms.PictureBox()
            Me.pictureBox2 = New System.Windows.Forms.PictureBox()
            Me.pictureBox3 = New System.Windows.Forms.PictureBox()
            DirectCast(Me.pictureBox1, System.ComponentModel.ISupportInitialize).BeginInit()
            DirectCast(Me.pictureBox2, System.ComponentModel.ISupportInitialize).BeginInit()
            DirectCast(Me.pictureBox3, System.ComponentModel.ISupportInitialize).BeginInit()
            Me.SuspendLayout()
            ' 
            ' button1
            ' 
            Me.button1.Location = New System.Drawing.Point(597, 290)
            Me.button1.Name = "button1"
            Me.button1.Size = New System.Drawing.Size(75, 23)
            Me.button1.TabIndex = 0
            Me.button1.Text = "button1"
            Me.button1.UseVisualStyleBackColor = True
            AddHandler Me.button1.Click, AddressOf Me.button1_Click
            '
            ' pictureBox1
            ' 
            Me.pictureBox1.Location = New System.Drawing.Point(13, 13)
            Me.pictureBox1.Name = "pictureBox1"
            Me.pictureBox1.Size = New System.Drawing.Size(276, 260)
            Me.pictureBox1.TabIndex = 1
            Me.pictureBox1.TabStop = False
            ' 
            ' pictureBox2
            ' 
            Me.pictureBox2.Location = New System.Drawing.Point(309, 13)
            Me.pictureBox2.Name = "pictureBox2"
            Me.pictureBox2.Size = New System.Drawing.Size(276, 260)
            Me.pictureBox2.TabIndex = 1
            Me.pictureBox2.TabStop = False
            ' 
            ' pictureBox3
            ' 
            Me.pictureBox3.Location = New System.Drawing.Point(597, 13)
            Me.pictureBox3.Name = "pictureBox3"
            Me.pictureBox3.Size = New System.Drawing.Size(276, 260)
            Me.pictureBox3.TabIndex = 1
            Me.pictureBox3.TabStop = False
            ' 
            ' Form1
            ' 
            Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0F, 13.0F)
            Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
            Me.ClientSize = New System.Drawing.Size(890, 322)
            Me.Controls.Add(Me.pictureBox3)
            Me.Controls.Add(Me.pictureBox2)
            Me.Controls.Add(Me.pictureBox1)
            Me.Controls.Add(Me.button1)
            Me.Name = "Form1"
            Me.Text = "Form1"
            AddHandler Me.FormClosing, AddressOf Form1_FormClosing
            AddHandler Me.Load, AddressOf Me.Form1_Load
            DirectCast(Me.pictureBox1, System.ComponentModel.ISupportInitialize).EndInit()
            DirectCast(Me.pictureBox2, System.ComponentModel.ISupportInitialize).EndInit()
            DirectCast(Me.pictureBox3, System.ComponentModel.ISupportInitialize).EndInit()
            Me.ResumeLayout(False)
        End Sub
    
        Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs)
            If Me.pictureBox1.Image IsNot Nothing Then
                Me.pictureBox1.Image.Dispose()
            End If
            If Me.pictureBox2.Image IsNot Nothing Then
                Me.pictureBox2.Image.Dispose()
            End If
            If Me.pictureBox3.Image IsNot Nothing Then
                Me.pictureBox3.Image.Dispose()
            End If
        End Sub
    End Class


    Edit: Added a (commented-out) line that gets the real "difference" (like the difference operator in image editing applications). Remove the comment and comment out the next line, if you like to get it that way.

    Regards,

      Thorsten




    Saturday, August 5, 2017 9:26 PM
  • If both are 24bit bitmap, you can change one of them into a 32bitmap (I think that happens anyway when you instantiate into memory). Then alter the alpha channel to 127, then use GDI to overlay that onto the first bitmap.

    Pride is the most destructive force in the universe

    Saturday, August 5, 2017 11:23 PM
  •  I am guessing that you want to create a new image that only shows the pixels of the 2nd image which have changed from the first (original) image.  If so,  here is another example which uses the Bitmap.LockBits Method as Thorsten has shown too.  Using the LockBits method is quite a bit faster for processing/manipulating larger images.

     This example can be tested in a new form project with 1 Button and 3 PictureBoxes added to it.  I set the SizeMode of all 3 PictureBoxes to (Zoom) in the designer properties.  You will also need to set the paths to the images to ones on your computer.

    Imports System.Drawing.Imaging
    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private sw As New Stopwatch
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim BmImg1 As New Bitmap("C:\TestFolder\RollingInCatnip.bmp") 'Snoopy_Original.png | QuarryShowing.jpg
            Dim BmImg2 As New Bitmap("C:\TestFolder\RollingInCatnip_Changed.bmp") 'Snoopy_Changed.png | QuarryShowing3q99.jpg
            PictureBox1.Image = BmImg1
            PictureBox2.Image = BmImg2
    
            sw.Restart()
            PictureBox3.Image = FindDifference(BmImg1, BmImg2)
            sw.Stop()
            Me.Text = sw.ElapsedMilliseconds.ToString & " ms  |  Image Size: " & BmImg1.Size.ToString
        End Sub
    
        Private Function FindDifference(bm1 As Bitmap, bm2 As Bitmap) As Bitmap
            Dim bm As New Bitmap(bm1.Width, bm1.Height)
            Dim bts1() As Byte = GetImageArgbBytes(bm1)
            Dim bts2() As Byte = GetImageArgbBytes(bm2)
            If bts1.Length = bts2.Length Then
                For i As Integer = 0 To bts1.Length - 5 Step 4
                    If bts1(i + 3) = bts2(i + 3) AndAlso bts1(i + 2) = bts2(i + 2) AndAlso bts1(i + 1) = bts2(i + 1) AndAlso bts1(i) = bts2(i) Then
                        bts2(i + 3) = 0
                    End If
                Next
                Dim bmd As BitmapData = bm.LockBits(New Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb)
                Marshal.Copy(bts2, 0, bmd.Scan0, bts2.Length)
                bm.UnlockBits(bmd)
            Else
                MessageBox.Show("Images must be the same size.", "Error...")
            End If
            Return bm
        End Function
    
        Private Function GetImageArgbBytes(b As Bitmap) As Byte()
            Dim bmd As BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
            Dim bts((bmd.Stride * bmd.Height) - 1) As Byte
            Marshal.Copy(bmd.Scan0, bts, 0, bts.Length)
            b.UnlockBits(bmd)
            Return bts
        End Function
    End Class
     

     Here you can see two examples,  the first using a 32bpp Png image that is 1024x768.  It took an average of 51ms for this one.  The second was with a 24bpp Bmp image that is 2064x1161 and took an average of 144ms.  However,  the speed will very depending on the ratio of same/different pixel colors.

     

      You should also be aware that Jpg type images use an encoder which will loose image quality.  This can cause the same jpg image saved two different times to have some slightly different colored pixels.  This might give you results that you are not expecting,  for example....

     


    If you say it can`t be done then i`ll try it

    • Edited by IronRazerz Sunday, August 6, 2017 1:34 AM
    • Proposed as answer by David_Day Thursday, August 10, 2017 3:44 PM
    Sunday, August 6, 2017 1:11 AM
  • If both are 24bit bitmap, you can change one of them into a 32bitmap (I think that happens anyway when you instantiate into memory). Then alter the alpha channel to 127, then use GDI to overlay that onto the first bitmap.

    Pride is the most destructive force in the universe

    Sorry Imaxus - - - ignore my post - - it was way off.

    I did not read your original post carefully enough.


    Pride is the most destructive force in the universe

    Wednesday, August 9, 2017 9:18 AM
  • I managed to achieve this using my company’s product (LEADTOOLS imaging SDK) based on the following forum post:
    https://www.leadtools.com/support/forum/posts/t12177-

    I modified the code (and converted it to VB.NET), and it looks like this:

    Dim codecs As RasterCodecs = New RasterCodecs()
    ' load the 2 images
    Dim bitmap1 As RasterImage = codecs.Load(imagesPath + "Image1.png")
    Dim bitmap2 As RasterImage = codecs.Load(imagesPath + "Image2.png")
    Dim temp As RasterImage = bitmap2.Clone()
    ' pefrom XOR combining to convert similar pixels to black in image2. All non-similar pixels become non-black
    Dim combineRect As LeadRect = New LeadRect(0, 0, bitmap1.Width, bitmap1.Height)
    Dim combineCmd As CombineFastCommand = New CombineFastCommand(temp, combineRect, New LeadPoint(0, 0), CombineFastCommandFlags.OperationXor)
    combineCmd.Run(bitmap1)
    ' create a region in image2 with all the black pixels. Those pixels were the similar ones.
    temp.AddColorToRegion(RasterColor.Black, RasterRegionCombineMode.Set)
    ' fill the region with White
    Dim fillWhite As FillCommand = New FillCommand(RasterColor.FromKnownColor(RasterKnownColor.White))
    fillWhite.Run(temp)
    ' perfom copy combining to copy the white pixels to the first image
    combineCmd.DestinationImage = bitmap2
    combineCmd.Flags = CombineFastCommandFlags.SourceCopy
    combineCmd.Run(temp)
    ' Save the result
    codecs.Save(bitmap2, imagesPath + "Result.png", RasterImageFormat.Png, 24)
    
    If you’d like to try it, the SDK has a free evaluation edition.

    Thursday, August 10, 2017 4:30 PM
  • Hi imaxus22,

    It seems that IronRazerz's reply resolve your issue, if so, please mark it as answer, it will be beneficial to other communities who have the same issue.

    Thanks for your understanding.

    Best Regards,

    Cherry


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, August 17, 2017 10:12 AM
    Moderator