Announcement

Collapse
No announcement yet.

Another way to blur a picture

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Another way to blur a picture

    An earlier thread showed how to use the Windows API function AlphaBlend to blur a picture. Here is another way that also allows you disable bluring of certain colors or pixels.

    The code below blurs the picture in the obvious way, by scanning it and replacing each pixel with the average of it and the surrounding pixels.

    The method uses a crude Gaussian filter to compute a weighted average of the pixels. The mathematically interesting thing is that instead of doing this in two dimensions with a plane filter, you can do each dimension separately with a line filter, in either order, and the final result will be the same as with the plane filter. Despite the intermediate asymmetrical averaging the end result is symmetrical averaging.

    One pass with a plane filter looks at nine pixels per point. Two perpendicular passes looks at only three pixels per point, per pass – or six pixels together. Nine versus six, which is faster even accounting for the extra pass.

    Here are the factors I used for the line Gaussian filter:
    .25 and .50 and .25
    either horizontally or vertically. .50 is for the central pixel, .25 for its two neighbors. I told you it was crude. (A more refined filter would use half a dozen values, again with a peak in the middle, approximating a bell curve.)

    Don’t believe doing it twice, once vertically and once horizontally, could work? Here’s a simple example. Suppose your picture is 4x4 pixels, with black (zero) around the edges:
    Code:
    .   .   .    .
    .   60  20   .
    .   40  12   .
    .   .   .    .
    The total sum, representing the total luminance, is 132.

    Now hold the filter vertically
    1/4
    1/2
    1/4
    and run it over the picture matrix left to right, each row, summing the individual products, replacing the center pixel. You get:
    Code:
    .   15   5   .
    .   40  13   .
    .   35  11   .
    .   10   3   .
    The total sum is still 132.

    Now hold the filter horizontally
    1/4 1/2 1/4
    and run it over that matrix top to bottom, each column, summing the individual products, replacing the center pixel. You get:
    Code:
     3.75   8.75   6.25  1.25
    10.00  23.25  16.50  3.25
     8.75  20.25  14.25  2.75
     2.50   5.75   4.00   .75
    The total sum is still 132.

    And if you were a machine it would look blurred.

    To make the computation faster, instead of the .25 and .50 Gaussian factors use 1 and 2, then divide by 4 at the end.

    Averaging colors must be done color component-wise, and since we need to average three colors for every pixel in the picture, twice, I wrote an assembly routine for it.
    Code:
    %bluemask  = &h00FF0000       'nominally blue, might be red
    %greenmask = &h0000FF00
    %redmask   = &h000000FF       'nominally red, might be blue
    'We don't care which is which because we will replace each
    'component in its place.
    
    'Like  (c1 + c2*2 +c3)/4  only must do each color component separately.
    'Note: This alters c1, used to give out result.
    Macro Function GaussianAverage
    'eax                 'blue accumulator
    'ebx                 'green
    'ecx                 'red
    '--------------
    
    ! mov eax,c1                  'get blues and add-up in eax
    ! and eax,%bluemask
    ! shr eax,16
    
    ! mov edx,c2
    ! and edx,%bluemask
    ! shr edx,15                  'not 16, so double
    ! add eax,edx
    
    ! mov edx,c3
    ! and edx,%bluemask
    ! shr edx,16
    ! add eax,edx
                                  'divide by 4
    ! bt eax,1                    'check bit #1, set carry flag if 1
    ! jc RoundUpA
    ! shr eax,2
    ! jmp short SkipA
    RoundUpA:
    ! shr eax,2
    ! inc eax
    SkipA:                        'restore red position
    ! shl eax,16
    '--------------
    
    ! mov ebx,c1                  'greens
    ! and ebx,%greenmask
    ! shr ebx,8
    
    ! mov edx,c2
    ! and edx,%greenmask
    ! shr edx,7                   'not 8, so double
    ! add ebx,edx
    
    ! mov edx,c3
    ! and edx,%greenmask
    ! shr edx,8
    ! add ebx,edx
    
    ! bt ebx,1
    ! jc RoundUpB
    ! shr ebx,2
    ! jmp short SkipB
    RoundUpB:
    ! shr ebx,2
    ! inc ebx
    SkipB:
    ! shl ebx,8
    '--------------
    
    ! mov ecx,c1                  'reds
    ! and ecx,%redmask
    
    ! mov edx,c2
    ! and edx,%redmask
    ! shl edx,1                   'double
    ! add ecx,edx
    
    ! mov edx,c3
    ! and edx,%redmask
    ! add ecx,edx
    
    ! bt ecx,1
    ! jc RoundUpC
    ! shr ecx,2
    ! jmp short SkipC
    RoundUpC:
    ! shr ecx,2
    ! inc ecx
    SkipC:
    '--------------
    
    ! mov edx,eax                 'assemble color, edx = eax + ebx + ecx
    ! add edx,ebx
    ! add edx,ecx
    
    ! mov c1,edx                  'give out result
    End Macro = c1
    '-------------------------------------------------------------
    
    %dswin = 480
                                               
    Function PBMain
     Local winC As Dword                    'display window handle
     Local bmpC As Dword                    'bitmap handle
     Local A$                               'string to hold picture
     Local B$                               'string to hold blurred picture
     Local Aptr As Dword Ptr                'pointer into A$
     Local Bptr As Dword Ptr                'pointer into B$
     Local i As Long                        'index counter
     Local c1, c2, c3 As Long               'linear frame about pixel of interest
    
     Console Set Loc 50+%dswin,50           'move console window out of the way
    
     Graphic Window "", 50,50, %dswin,%dswin To winC   'window used for display
     Console Set Focus                                 'give focus back to console
    
     Graphic Attach winC,0, ReDraw          'draw some random lines
     Randomize Timer
     For i = 1 To 10
      Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Red
      Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Blue
      Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Green
      Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Black
     Next
     Sleep 1                                'this sleep is necessary for some reason,
     Graphic ReDraw                         'otherwise redraw only works at random on my
                                            'system and even then it occassionally misses.
     Print "Press any key to blur window at left."
     WaitKey$
    
     Graphic Bitmap New %dswin,%dswin To bmpC         'create bitmap
     Graphic Attach bmpC, 0 :Graphic Copy winC,0      'copy display window to bitmap
     Graphic Get Bits To A$                           'get bitmap into a string
     B$ = A$                                          'make a copy to hold blur
    
     'Assume picture is a uniform color for several pixels in from the edges.
     'In that case wrap-around averaging at left and right edges won't matter,
     'and we can ignore the top and bottom edges.
                                   
     'First pass - from left to right.        Skip top row
     Aptr = StrPtr(A$) + 8 + %dswin*4                 'pointer into original bitmap
     Bptr = StrPtr(B$) + 8 + %dswin*4                 'pointer into blurred bitmap
     For i = 1 To %dswin*%dswin - %dswin*2   'and skip bottom row
      c1 = @Aptr[-%dswin]        'above pixel
      c2 = @Aptr                 'at pixel
      c3 = @Aptr[ %dswin]        'below pixel
      GoSub GetGaussianAverage
      Incr Aptr
      Incr Bptr
     Next
     A$ = B$                      'update source string to horizontally  blurred picture
    
     'Second pass - from top to bottom.  Skip beginning left edge.
     Aptr = StrPtr(A$) + 8 + 4
     Bptr = StrPtr(B$) + 8 + 4
     For i = 1 To %dswin*%dswin - 2     'and skip ending right edge
      c1 = @Aptr[-1]             'to left of pixel
      c2 = @Aptr                 'at pixel
      c3 = @Aptr[ 1]             'to right of pixel
      GoSub GetGaussianAverage
      Incr Aptr
      Incr Bptr
     Next
    
     Graphic Attach winC,0
     Graphic Set Bits B$                              'update display window
     Graphic ReDraw
     Print "Press any key to exit."
     WaitKey$
     Exit Function
    '-------------
    GetGaussianAverage:           'macro in gosub so line labels not duplicated
     @Bptr = GaussianAverage
     Return
    End Function
    Last edited by Mark Hunter; 7 Dec 2013, 05:17 PM.
    Politically incorrect signatures about immigration patriots are forbidden. Googling “immigration patriots” is forbidden. Thinking about Googling ... well, don’t even think about it.

  • #2
    This post is in reply to Joe Cote’s comment on “An easy way to blur a picture” at
    http://www.powerbasic.com/support/pb...splay.php?f=11

    The above code (in this thread, not the other one) shows how to perform the equivalent of a weighted average within a circle of pixel diameter 3 in two passes that looks at only 3 pixels per pixel per pass.

    The method can be extended to a larger area. For example, for a circle of diameter 5 replace the appropriate sections with
    Code:
      'First pass - from left to right
      ...
      c1 = @Aptr[-%dswin*2]      'above that
      c2 = @Aptr[-%dswin]        'above pixel
      c3 = @Aptr                 'at pixel
      c4 = @Aptr[ %dswin]        'below pixel
      c5 = @Aptr[ %dswin*2]      'below that
      ...
    
      'Second pass - from top to bottom
      ...
      c1 = @Aptr[-2]             'to left of that
      c2 = @Aptr[-1]             'to left of pixel
      c3 = @Aptr                 'at pixel
      c4 = @Aptr[ 1]             'to right of pixel
      c5 = @Aptr[ 2]             'to right of that
      ...
    to access the required pixels. (You also need to start two rows from top and stop two rows from bottom, two pixels from left and two from right.) Then you would need to rewrite the assembly routine that does the linear weighted average so it handles five colors instead of three. This is easy except you need to change the weight factors from 1,2,1 divided by 4 to something like 2,3,5,3,2 divided by 15. (The idea is that you want an approximate bell curve and the sum if the weights must be one.)

    Another observation. If you work entirely within bitmap strings you can use having to make two passes to advantage and avoid coping the string. In what follows “video” is global:
    Code:
    Sub BlurBmp(ByVal n As Long) Static     'blur string video, pointed to by Vptr0
     Local B As String                      'string to hold blurred picture
     Local Aptr As Dword Ptr                'pointer into video
     Local Bptr As Dword Ptr                'pointer into B then pointer into video
     Local i As Long                        'index counter
     Local c1, c2, c3 As Long               'linear frame about pixel of interest
    
     B = video                                    'make a copy to hold blur
    
     'First pass - from left to right, A --> B
     Aptr = Vptr0 + 8 + %dswin*4                  'pointer into original bitmap
     Bptr = StrPtr(B) + 8 + %dswin*4              'pointer into blurred bitmap
     For i = 1 To n*(%dswin - 2)
      c1 = @Aptr[-%dswin]        'above pixel
      c2 = @Aptr                 'at pixel
      c3 = @Aptr[ %dswin]        'below pixel
      GoSub GetGaussianAverage
      Incr Aptr
      Incr Bptr
     Next
    
     'Second pass - from top to bottom, B --> A
     Bptr = Vptr0 + 8 + 4
     Aptr = StrPtr(B) + 8 + 4
     For i = 1 To n*%dswin - 2
      c1 = @Aptr[-1]             'to left of pixel
      c2 = @Aptr                 'at pixel
      c3 = @Aptr[ 1]             'to right of pixel
      GoSub GetGaussianAverage
      Incr Aptr
      Incr Bptr
     Next
     Exit Sub
    '-------------
    GetGaussianAverage:          'macro in gosub so line labels not duplicated
     @Bptr = GaussianAverage
     Return
    End Sub
    Last edited by Mark Hunter; 13 Dec 2013, 12:41 AM.
    Politically incorrect signatures about immigration patriots are forbidden. Googling “immigration patriots” is forbidden. Thinking about Googling ... well, don’t even think about it.

    Comment


    • #3
      It’s easy enough to make the blur code all assembly:
      Code:
      %bluemask  = &h00FF0000       'nominally blue, might be red
      %greenmask = &h0000FF00
      %redmask   = &h000000FF       'nominally red, might be blue
      
      %dswin = 480                  'side of square picture
      %dswin4 = %dswin*4            'one line, four bytes per pixel
      %offset1 = 8 + %dswin4        'skip two words and one line
      %offset2 = 8 + 4              'skip two words and one pixel
      %total1 = %dswin*(%dswin - 2)
      %total2 = %dswin*%dswin - 2
      
      ' We don't care if blue and red are in swapped positions
      ' because we will replace each component in its place.
      '
      ' Gaussian average is (p1 + p2*2 + p3)/4 and must do each color
      ' component separately.
      '
      ' Assume picture is a uniform color for several pixels in from the
      ' edges.  Then wrap-around averaging at left and right edges won't
      ' matter, and we can also ignore the top and bottom edges.
      
      Sub Blur(ByVal Aptr As Dword, ByVal Bptr As Dword)
      
      'first pass A --> B, gaussian average pixels above and below
      
      ! mov esi,Aptr
      ! mov edi,Bptr
      ! add esi,%offset1
      ! add edi,%offset1
      ! mov ecx,%total1
      
      ScanBmp:
      ! mov eax,[esi-%dswin4]        'get blues and add-up in eax
      ! and eax,%bluemask
      ! shr eax,16
      
      ! mov edx,[esi]
      ! and edx,%bluemask
      ! shr edx,15                  'not 16, so double
      ! add eax,edx
      
      ! mov edx,[esi+%dswin4]
      ! and edx,%bluemask
      ! shr edx,16
      ! add eax,edx
                                    'divide by 4
      ! bt eax,1                    'check bit #1, set carry flag if 1
      ! jc RoundUpA
      ! shr eax,2
      ! jmp short SkipA
      RoundUpA:
      ! shr eax,2
      ! inc eax
      SkipA:                        'restore red position
      ! shl eax,16
      ! push eax                    'store blues, as will use for reds later
      '--------------
      
      ! mov ebx,[esi-%dswin4]       'greens, use ebx
      ! and ebx,%greenmask
      ! shr ebx,8
      
      ! mov edx,[esi]
      ! and edx,%greenmask
      ! shr edx,7                   'not 8, so double
      ! add ebx,edx
      
      ! mov edx,[esi+%dswin4]
      ! and edx,%greenmask
      ! shr edx,8
      ! add ebx,edx
      
      ! bt ebx,1
      ! jc RoundUpB
      ! shr ebx,2
      ! jmp short SkipB
      RoundUpB:
      ! shr ebx,2
      ! inc ebx
      SkipB:
      ! shl ebx,8
      '--------------
      
      ! mov eax,[esi-%dswin4]       'reds, use eax again
      ! and eax,%redmask
      
      ! mov edx,[esi]
      ! and edx,%redmask
      ! shl edx,1                   'double
      ! add eax,edx
      
      ! mov edx,[esi+%dswin4]
      ! and edx,%redmask
      ! add eax,edx
      
      ! bt eax,1
      ! jc RoundUpC
      ! shr eax,2
      ! jmp short SkipC
      RoundUpC:
      ! shr eax,2
      ! inc eax
      SkipC:
      '--------------
      
      ! mov edx,eax                 'assemble color in edx, start with reds
      ! add edx,ebx                 'add in greens
      ! pop eax                     'get blues
      ! add edx,eax                 'add it in
      
      ! mov [edi],edx               'store result
      ! mov [edi],edx               'store result
      ! add esi,4
      ! add edi,4
      ! dec ecx                     'ScanBmp is further away than 128 bytes
      ! jnz ScanBmp   
      '-----------------------------------------------
      
      'second pass B --> A, gaussian average pixels left and right
      
      ! mov esi,Bptr
      ! mov edi,Aptr
      ! add esi,%offset2
      ! add edi,%offset2
      ! mov ecx,%total2
      
      ScanBmp2:
      ! mov eax,[esi-4]             'get blues and add-up in eax
      ! and eax,%bluemask
      ! shr eax,16
      
      ! mov edx,[esi]
      ! and edx,%bluemask
      ! shr edx,15                  'not 16, so double
      ! add eax,edx
      
      ! mov edx,[esi+4]
      ! and edx,%bluemask
      ! shr edx,16
      ! add eax,edx
                                    'divide by 4
      ! bt eax,1                    'check bit #1, set carry flag if 1
      ! jc RoundUpA2
      ! shr eax,2
      ! jmp short SkipA2
      RoundUpA2:
      ! shr eax,2
      ! inc eax
      SkipA2:                       'restore position
      ! shl eax,16
      ! push eax
      '--------------
      
      ! mov ebx,[esi-4]             'greens
      ! and ebx,%greenmask
      ! shr ebx,8
      
      ! mov edx,[esi]
      ! and edx,%greenmask
      ! shr edx,7                   'not 8, so double
      ! add ebx,edx
      
      ! mov edx,[esi+4]
      ! and edx,%greenmask
      ! shr edx,8
      ! add ebx,edx
      
      ! bt ebx,1
      ! jc RoundUpB2
      ! shr ebx,2
      ! jmp short SkipB2
      RoundUpB2:
      ! shr ebx,2
      ! inc ebx
      SkipB2:
      ! shl ebx,8
      '--------------
      
      ! mov eax,[esi-4]             'reds
      ! and eax,%redmask
      
      ! mov edx,[esi]
      ! and edx,%redmask
      ! shl edx,1                   'double
      ! add eax,edx
      
      ! mov edx,[esi+4]
      ! and edx,%redmask
      ! add eax,edx
      
      ! bt eax,1
      ! jc RoundUpC2
      ! shr eax,2
      ! jmp short SkipC2
      RoundUpC2:
      ! shr eax,2
      ! inc eax
      SkipC2:
      '--------------
      
      ! mov edx,eax                 'assemble color in edx
      ! add edx,ebx
      ! pop eax
      ! add edx,eax
      
      ! mov [edi],edx               'store result
      ! mov [edi],edx               'store result
      ! add esi,4
      ! add edi,4
      ! dec ecx                     'ScanBmp2 is further away than 128 bytes
      ! jnz ScanBmp2   
      End Sub
      '======================================================
      
      Function PBMain
       Local winC As Dword          'display window handle
       Local bmpC As Dword          'bitmap handle
       Local video As String        'bitmap string
       Local B As String            'string to hold partially blurred picture
       Local i As Long              'index counter
      
       '****** create example picture
       Console Set Loc 50+%dswin,50           'move console window out of the way
      
       Graphic Window "", 50,50, %dswin,%dswin To winC   'window used for display
       Console Set Focus                                 'give focus back to console
      
       Graphic Attach winC,0, ReDraw          'draw some random lines
       Randomize Timer
       For i = 1 To 10
        Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Red
        Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Blue
        Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Green
        Graphic Line (%dswin*Rnd,%dswin*Rnd)-(%dswin*Rnd,%dswin*Rnd), %Black
       Next
       Sleep 10                               'this sleep is necessary for some reason,
       Graphic ReDraw                         'otherwise redraw only works at random on my
                                              'system and even then it occassionally misses.
       Print "Press any key to blur window at left."
       WaitKey$
      
       Graphic Bitmap New %dswin,%dswin To bmpC    'create bitmap
       Graphic Attach bmpC, 0 :Graphic Copy winC,0 'copy display window to bitmap
       Graphic Get Bits To video                   'get bitmap into a string
       '----------------------------------------
      
       '****** blur the picture
       B = video                                   'copy of video to hold mid blur
       Blur StrPtr(video), StrPtr(B)
       '----------------------------------------
      
       '****** show blurred picture
       Graphic Attach winC,0
       Graphic Set Bits video                      'update display window
       Graphic ReDraw
       '----------------------------------------
      
       Print "Press any key to exit."
       WaitKey$
      End Function
      Last edited by Mark Hunter; 30 Jan 2014, 03:48 PM.
      Politically incorrect signatures about immigration patriots are forbidden. Googling “immigration patriots” is forbidden. Thinking about Googling ... well, don’t even think about it.

      Comment

      Working...
      X