像Google Earth一样重新着色图标

KML allows you to specify a <color> for an icon.

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
  <Placemark>
    <name>Pin</name>
    <Point>
      <coordinates>0, 0</coordinates>
    </Point>
    <Style>
      <IconStyle>
        <color>ff8c4800</color>
        <scale>10</scale>
        <Icon>
          <href>http://maps.google.com/mapfiles/kml/pushpin/wht-pushpin.png</href>
        </Icon>
      </IconStyle>
    </Style>
  </Placemark>
</kml>

I'm trying to do the same thing in Go using the Porter-Duff colour blending method.

    // blendColors uses the Porter-Duff over method
    // for combing two colors with alpha channels
    func blendColors(c1 color.Color, c2 color.Color) color.Color {
        r1, g1, b1, a1 := extractComponents(c1)
        r2, g2, b2, a2 := extractComponents(c2)
        var (
            a = a1 + a2*(1.0-a1)
            r = (r1*a1 + r2*a2*(1.0-a1)) / a
            g = (g1*a1 + g2*a2*(1.0-a1)) / a
            b = (b1*a1 + b2*a2*(1.0-a1)) / a
        )
        return color.RGBA{
            R: clampColorValue(int(r)),
            G: clampColorValue(int(g)),
            B: clampColorValue(int(b)),
            A: clampColorValue(int(a * 255)),
        }
    }

See full code here

Here are some output examples with different opacity levels (from 0 - 255).

  • 0

  • 100

  • 200

  • 255

These are not satisfactory (due to the jagged edges and faded black border) and I want to know what approach I should take to get results more akin to what Google Earth does.

I managed to fix the output using two tricks.

  1. If the original pixel doesn't have 100% alpha, don't touch it. This fixes the jagged edges. If I didn't want to do this, I think I'd have to implement some type of anti-aliasing.

  2. Use the grayscale of the original pixel color to determine the alpha of the color that's being blended in.

Here's my revised blending code.

    // blendColors uses the Porter-Duff over method
    // for combing two colors with alpha channels
    func blendColors(c1 color.Color, original color.Color) color.Color {
        r1, g1, b1, _ := extractComponents(c1)
        r2, g2, b2, a2 := extractComponents(original)

        // use the origial color's greyscale value to calculate
        // how much of the new color to use
        a1 := float64(color.GrayModel.Convert(original).(color.Gray).Y) / 255

        // don't do any blending if the original pixels
        // alpha isn't 100%
        if int(a2) == 0 {
            return original
        }

        var (
            a = a1 + a2*(1.0-a1)
            r = (r1*a1 + r2*a2*(1.0-a1)) / a
            g = (g1*a1 + g2*a2*(1.0-a1)) / a
            b = (b1*a1 + b2*a2*(1.0-a1)) / a
        )
        return color.RGBA{
            R: clampColorValue(int(r)),
            G: clampColorValue(int(g)),
            B: clampColorValue(int(b)),
            A: clampColorValue(int(a * 255)),
        }
    }

edit: Requiring alpha to be 100% doesn't work well all the time. I ended up using a alpha < 0.3 threshold.