Chase Rocker
Chase Rocker

Reputation: 1908

Need faster method to replace colors in an image based on another color

I'm looking for a faster method of replacing colors in an image based on another color. For example, I have an image that is mostly black with shades of gray and I want to recolor it based on red. Right now I pass it into this function with black as my base color and red as my new color. Then I go pixel by pixel to determine its RGB and recalculate that to the new red shade. So pure black becomes pure red and grays become a lighter red and so on.

Is there a quicker way to do this? Possibly using a separate image manipulation library? I looked into ImageMagick but I can't find the exact functionality I'm looking for.

Here's my current function. It works but is slow.

Public Function doRecolorImage(ByRef inBMP As Bitmap, ByVal baseColor As Color, ByVal newColor As Color) As Bitmap
        Dim newimg As New Bitmap(inBMP.Width, inBMP.Height)

            Using gIcons As Graphics = Graphics.FromImage(newimg)
                For x = 0 To inBMP.Width - 1
                    For y = 0 To inBMP.Height - 1
                        Dim ltrans As Byte = inBMP.GetPixel(x, y).A
                        Dim lR As Byte = inBMP.GetPixel(x, y).R
                        Dim lG As Byte = inBMP.GetPixel(x, y).G
                        Dim lB As Byte = inBMP.GetPixel(x, y).B

                        Dim newpixR As Integer = CInt(newColor.R) + (CInt(lR) - CInt(baseColor.R))

                        Dim newpixG As Integer = CInt(newColor.G) + (CInt(lG) - CInt(baseColor.G))

                        Dim newpixB As Integer = CInt(newColor.B) + (CInt(lB) - CInt(baseColor.B))

                        newimg.SetPixel(x, y, System.Drawing.Color.FromArgb(ltrans, newpixR, newpixG, newpixB))
                    Next
                Next
            End Using

        Return newimg

    End Function

Here I tried using ColorMatrix but it didn't work, not sure why...maybe my understanding of how ColorMatrix works is wrong.

Public Function doNewRecolorImage(ByRef inBMP As Bitmap, ByVal baseColor As Color, ByVal newColor As Color) As Bitmap
        Dim newimg As New Bitmap(inBMP.Width, inBMP.Height)


            Dim iaImageProps As New ImageAttributes 

            Dim cmNewColors As ColorMatrix 

            cmNewColors = New ColorMatrix(New Single()() _
                {New Single() {newColor.R / baseColor.R, 0, 0, 0, 0}, _
                 New Single() {0, newColor.G / baseColor.G, 0, 0, 0}, _
                 New Single() {0, 0, newColor.B / baseColor.B, 0, 0}, _
                 New Single() {0, 0, 0, 1, 0}, _
                 New Single() {0, 0, 0, 0, 1}})

            iaImageProps.SetColorMatrix(cmNewColors) 

            Using g As Graphics = Graphics.FromImage(newimg)
                g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
                g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
                g.DrawImage(inBMP, New Rectangle(0, 0, inBMP.Width, inBMP.Height), 0, 0, inBMP.Width, inBMP.Height, GraphicsUnit.Pixel, iaImageProps)
            End Using


        Return newimg

    End Function

UPDATE 1:

Here's my updated function based on Ryan's answer below. It runs but it isn't returning the expected recolored image. The image is one solid color...the base color. So I'm passing in the base color and the new color and getting the result. However, the result should be the new color (dark blue) because the image is a solid color so it should be an exact color swap.

enter image description here

Private Function calcColorSwap(_base As Byte, _new As Byte) As Single
    If _new > _base Then
        Return (CInt(_new) - CInt(_base)) / 255.0!
    Else
        Return (CInt(_base) - CInt(_new)) / 255.0!
    End If
End Function

Public Function doNewRecolorImage(inBMP As Bitmap, baseColor As Color, newColor As Color) As Bitmap
    Dim newimg As New Bitmap(inBMP.Width, inBMP.Height)
    Dim transformation As New ColorMatrix(New Single()() {
          New Single() {1, 0, 0, 0, 0},
          New Single() {0, 1, 0, 0, 0},
          New Single() {0, 0, 1, 0, 0},
          New Single() {0, 0, 0, 1, 0},
          New Single() {calcColorSwap(baseColor.R, newColor.R), calcColorSwap(baseColor.G, newColor.G), calcColorSwap(baseColor.B, newColor.B), 0, 1}
      })

    Dim imageAttributes As New ImageAttributes()
    imageAttributes.SetColorMatrix(transformation)


    Using g As Graphics = Graphics.FromImage(newimg)
        g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
        g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        g.DrawImage(inBMP, New Rectangle(0, 0, inBMP.Width, inBMP.Height), 0, 0, inBMP.Width, inBMP.Height, GraphicsUnit.Pixel, imageAttributes)
    End Using

Return newimg

End Function

UPDATE 2 (SOLUTION):

Here's the solution: based on Ryan's answer below.

Private Function calcColorSwap(_base As Byte, _new As Byte) As Single
        Return (CInt(_new) - CInt(_base)) / 255.0!
End Function

Public Function doNewRecolorImage(inBMP As Bitmap, baseColor As Color, newColor As Color) As Bitmap
    Dim newimg As New Bitmap(inBMP.Width, inBMP.Height)
    Dim transformation As New ColorMatrix(New Single()() {
          New Single() {1, 0, 0, 0, 0},
          New Single() {0, 1, 0, 0, 0},
          New Single() {0, 0, 1, 0, 0},
          New Single() {0, 0, 0, 1, 0},
          New Single() {calcColorSwap(baseColor.R, newColor.R), calcColorSwap(baseColor.G, newColor.G), calcColorSwap(baseColor.B, newColor.B), 0, 1}
      })

    Dim imageAttributes As New ImageAttributes()
    imageAttributes.SetColorMatrix(transformation)


    Using g As Graphics = Graphics.FromImage(newimg)
        g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality
        g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
        g.DrawImage(inBMP, New Rectangle(0, 0, inBMP.Width, inBMP.Height), 0, 0, inBMP.Width, inBMP.Height, GraphicsUnit.Pixel, imageAttributes)
    End Using

Return newimg

End Function

Upvotes: 2

Views: 1903

Answers (2)

Ry-
Ry-

Reputation: 224922

The matrix equivalent to your original – adding a fixed value to R, G, and B – looks like this:

New Single()() {
    New Single() {1, 0, 0, 0, 0},
    New Single() {0, 1, 0, 0, 0},
    New Single() {0, 0, 1, 0, 0},
    New Single() {0, 0, 0, 1, 0},
    New Single() {(CInt(newColor.R) - CInt(baseColor.R)) / 255!, (CInt(newColor.G) - CInt(baseColor.G)) / 255!, (CInt(newColor.B) - CInt(baseColor.B)) / 255!, 0, 1}
}

Overall:

Public Function doRecolorImage(image As Image, baseColor As Color, newColor As Color) As Bitmap
    Dim transformation As New ColorMatrix(New Single()() {
        New Single() {1, 0, 0, 0, 0},
        New Single() {0, 1, 0, 0, 0},
        New Single() {0, 0, 1, 0, 0},
        New Single() {0, 0, 0, 1, 0},
        New Single() {(CInt(newColor.R) - CInt(baseColor.R)) / 255!, (CInt(newColor.G) - CInt(baseColor.G)) / 255!, (CInt(newColor.B) - CInt(baseColor.B)) / 255!, 0, 1}
    })

    Dim imageAttributes As New ImageAttributes()
    imageAttributes.SetColorMatrix(transformation)

    Dim result As New Bitmap(image.Width, image.Height)

    Using g As Graphics = Graphics.FromImage(result)
        g.DrawImage(image, New Rectangle(0, 0, image.Width, image.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttributes)
    End Using

    Return result
End Function

(Don’t use ByRef for no reason!)

Upvotes: 2

fmw42
fmw42

Reputation: 53081

In ImageMagick, you can do

convert gray.png \( -clone 0 -fill red -colorize 100 \) \( -clone 0 -negate \) -compose colorize -composite red.png

I answered a similar question here (without the negate) Re-coloring image preserving luminance

Another approach in ImageMagick that is simpler and may be more what you want is:

convert gray.png +level-colors red,white red.png

see http://www.imagemagick.org/Usage/color_mods/#level-colors

Upvotes: 1

Related Questions