简体   繁体   中英

Merging a color into a bitmap using c# (universal windows application)

I have a .png resource that includes areas that need to be "colored" as described in the table below (these are my own definitions, if there are better terms that I should use feel free to educate me). I need to "color" this image based on a ColorBrush (eg BGRA=#6572D8FF) such that ...

//                        Input            Output
//    Transparent  BGRA=#FFFFFF00 --> #FFFFFF00
//    AlphaOnly    BGRA=#00000000 --> #6572D800   // Colored area
//    Alpha-Shaded BGRA=#000000aa --> #6572D8aa   // Colored area
//    Solid colors BGRA=#bbggrrFF --> #rrggbbFF

The resultant image needs to be displayed using the following XAML ...

    <Viewbox Height="{TemplateBinding Height}" Width="{TemplateBinding Width}">
        <Grid Height="100" Width="100">
            <Image x:Name="currentImage" />
        </Grid>
    </Viewbox>

Placing the control onto a red background and specifying a blue ColorBrush, I was hoping to get the bottom image (which I created by simply putting a blue ellipse behind the original image)

在此处输入图片说明

Unfortunately I get the top image. My transparency is lost and the alpha shading I was hoping to achieve is wrong. Any help (including you "idiot", you should do this like this) would be greatly appreciated.

public sealed class MyImage : Control {
    public MyImage() {
        this.DefaultStyleKey = typeof(MyImage);
        Loaded += MyImage_Loaded;
    }
    private async void MyImage_Loaded(object sender, RoutedEventArgs e) {
        ((Image)this.GetTemplateChild("currentImage")).Source = await ColorImage();
    }
    private async Task<WriteableBitmap> ColorImage() {
        // Get the image as a byte array
        StorageFile fileImage = await StorageFile.GetFileFromApplicationUriAsync(new Uri(BaseImageUri, UriKind.Absolute));
        ImageProperties propsImage = await fileImage.Properties.GetImagePropertiesAsync();
        int height = (int)propsImage.Height;
        int width = (int)propsImage.Width;
        byte[] baseImagePixels = await ReadPixels(fileImage);

        // Modify the mask by adding the accent color
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                byte B = baseImagePixels[4 * (y * height + x) + 0];
                byte G = baseImagePixels[4 * (y * height + x) + 1];
                byte R = baseImagePixels[4 * (y * height + x) + 2];
                byte A = baseImagePixels[4 * (y * height + x) + 3];
                if (R == 0x00 && G == 0x00 && B == 0x00 && A != 0xFF) {
                    baseImagePixels[4 * (y * height + x) + 0] = ColorBrush.Color.B;
                    baseImagePixels[4 * (y * height + x) + 1] = ColorBrush.Color.G;
                    baseImagePixels[4 * (y * height + x) + 2] = ColorBrush.Color.R;
                }
            }
        }
        WriteableBitmap coloredImage = new WriteableBitmap((int)propsImage.Width, (int)propsImage.Height);
        using (Stream stream = coloredImage.PixelBuffer.AsStream()) {
            await stream.WriteAsync(baseImagePixels, 0, baseImagePixels.Length);
        }
        return coloredImage;
    }
    private async Task<byte[]> ReadPixels(StorageFile file) {
        BitmapDecoder decoder = await BitmapDecoder.CreateAsync(await file.OpenAsync(FileAccessMode.Read));
        BitmapTransform transform = new BitmapTransform();
        PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
            BitmapPixelFormat.Bgra8,
            BitmapAlphaMode.Ignore,
            new BitmapTransform(),
            ExifOrientationMode.IgnoreExifOrientation,
            ColorManagementMode.DoNotColorManage);
        return pixelData.DetachPixelData();
    }
    public String BaseImageUri {
        get { return (String)GetValue(BaseImageUriProperty); }
        set {
            if (value.StartsWith("ms-appx:///")) {
                SetValue(BaseImageUriProperty, value);
            } else {
                SetValue(BaseImageUriProperty, "ms-appx:///" + value);
            }
        }
    }
    public static readonly DependencyProperty BaseImageUriProperty =
        DependencyProperty.Register("BaseImageUri", typeof(String), typeof(MyImage), new PropertyMetadata(0));
    public SolidColorBrush ColorBrush {
        get { return (SolidColorBrush)GetValue(ColorBrushProperty); }
        set { SetValue(ColorBrushProperty, value); }
    }
    public static readonly DependencyProperty ColorBrushProperty =
        DependencyProperty.Register("ColorBrush", typeof(SolidColorBrush), typeof(MyImage), new PropertyMetadata(0));
}

Well I found a solution. I have no idea why it works though ... would love to be educated on that if somebody knows why. I added a second change to the for loops.

        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int pixelIndex = 4 * (y * width + x);
                byte B = baseImagePixels[pixelIndex];
                byte G = baseImagePixels[pixelIndex + 1];
                byte R = baseImagePixels[pixelIndex + 2];
                byte A = baseImagePixels[pixelIndex + 3];
                if (!(B == 0xFF && G == 0xFF && R == 0xFF && A == 0x00)) {
                    baseImagePixels[pixelIndex] = colorBrush.Color.B;
                    baseImagePixels[pixelIndex + 1] = colorBrush.Color.G;
                    baseImagePixels[pixelIndex + 2] = colorBrush.Color.R;
                    baseImagePixels[pixelIndex + 3] = colorBrush.Color.A;
                } else if (B == 0xFF && G == 0xFF && R == 0xFF && A == 0x00) {
                    baseImagePixels[pixelIndex] = 0x00;
                    baseImagePixels[pixelIndex + 1] = 0x00;
                    baseImagePixels[pixelIndex + 2] = 0x00;
                    baseImagePixels[pixelIndex + 3] = 0x00;
                }
            }
        }

This looks like a premultiplied alpha issue to me, but that's just a wild guess.

Some graphic libraries require that pixels with alpha < 1.0 store their RGB components as:

R' := R x A
G' := G x A
B' := B x A
A' := A

Hence RGB → 0xFFFFFF with zero alpha would be invalid.

Blending algorithms which expect to be fed with premultiplied alpha pixels produce unexpected results when their RGB values exceed their alpha .

So fully transparent pixels are always encoded as 0x000000 with 0 alpha in a premultiplied alpha case.

Have a look at the Wikipedia article on Alpha compositing :

Assuming that the pixel color is expressed using straight (non-premultiplied) RGBA tuples, a pixel value of (0.0, 0.5, 0.0, 0.5) implies a pixel that has 50% of the maximum green intensity and 50% opacity. If the color were fully green, its RGBA would be (0, 1, 0, 0.5).

However, if this pixel uses premultiplied alpha, all of the RGB values (0, 1, 0) are multiplied by 0.5 and then the alpha is appended to the end to yield (0, 0.5, 0, 0.5). In this case, the 0.5 value for the G channel actually indicates 100% green intensity (with 50% opacity). For this reason, knowing whether a file uses premultiplied or straight alpha is essential to correctly process or composite it.

It also provides an explanation of how the various color merging operators work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM