Reputation: 1908
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
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.
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
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
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
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