0

Here is the colour menu:

Menu with colour bitmaps

Here is the same menu with some of the menu items disabled, and the bitmaps set as greyscale:

Menu with greyscale bitmaps

The code that converts to grey scale:

auto col = GetRValue(pixel) * 0.299 + 
           GetGValue(pixel) * 0.587 + 
           GetBValue(pixel) * 0.114;
pixel = RGB(col, col, col);

I am colourblind but it seems that some of them don’t look that much different. I assume it relates to the original colours in the first place?

It would just be nice if it was more obvious they are disabled. Like, it is very clear with the text.

Can we?

fhe
  • 6,099
  • 1
  • 41
  • 44
Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 1
    The colored bitmaps look worse than gray ones, because they have sharp light color edges against dark background. To fix it, edit the bitmap, you find light gray pixels for example at the end of the arrow, change it to white or whatever the background color is. Or you can just leave it. This will probably look okay with light theme background. Most people use light background theme, so most of your customers should be happy! – Barmak Shemirani Apr 18 '18 at 19:52
  • 1
    The last value should be `0.114`, not `0.144`, and requires taking `RGB` value as pointed out [here](https://stackoverflow.com/a/49877382/4603670) – Barmak Shemirani Apr 19 '18 at 05:52
  • @BarmakShemirani I coped my question code rather than the answer code. Oops. The actual screenshot was with the right values. – Andrew Truckle Apr 19 '18 at 07:22
  • 1
    I suggest to use 32 bpp images with an alpha channel to get rid of the jagged edges (especially noticable at the "weekend meeting" icon, which looks really ugly because of that). For the disabled images, set the overall transparency to something like 50% in addition to or as replacement of the grayscale effect. – zett42 Apr 19 '18 at 15:40
  • @zett42 I am rubbish at working with images. I murder them when I play with them. So I am going to leave them alone. :) I understand what you mean but as someone else pointed out they don't look as bad with lighter themes. Also, whilst I accept the answer I can't work out how to apply it in practice. I just don't grasp the knowhow to applying the intesity at 50%. I need a graphic guru. :) The images are 24 bit and I set all the backgrounds to match the background of the menus. – Andrew Truckle Apr 19 '18 at 15:46
  • 1
    I'd darken the disabled images, then IMO it will be clearer that they are disabled. – Jabberwocky Apr 19 '18 at 16:08
  • @MichaelWalz Sure. I appreciate the advice. I guess the key would be to come up with this "intensity" method so that the images can be darkened to 50% etc. But it is all relative to what theme you are using to skin the menus. – Andrew Truckle Apr 19 '18 at 16:24

2 Answers2

2

For people who are not colour blind it's pretty obvious.

Just apply the same intensity reduction to the images that you do to the text.

I did not check your values. Let's assume the text is white (100% intensity).

And the grayed out text is 50% intensity.

Then the maximum intensity of the bitmap should be 50% as well.

for each gray pixel:
  pixel_value = pixel_value / max_pixel_value * gray_text_value

This way you decrease further decrease the contrast of each bitmap and avoid having any pixel brighter than the text.

Piglet
  • 27,501
  • 3
  • 20
  • 43
  • I only apply greyscale to the menu item images themselves. The text is handled by the MFC framework. – Andrew Truckle Apr 18 '18 at 18:21
  • Could you please show me your suggestion in practice?Thanks. – Andrew Truckle Apr 18 '18 at 18:23
  • 1
    @AndrewTruckle take a screenshot, find out what intensities are used for the text using one of the many free image programs that allow to display pixel values at mouse position. the grayed out text's brightness is 92 (in the inverval 0-255). all you need to do calculate your pixel values, then determin the max pixel value and use that to normalize your values to [0-92] or simply reduce the normalization value until you're satisfied. – Piglet Apr 18 '18 at 18:28
  • 1
    Intensity reduction might look good with the dark theme shown in the screenshots but I believe it would make matters worse with a light theme. That's why I would change the overall transparency of the image to like 50% so the image blends with the background, regardless if the background is dark or light. – zett42 Apr 19 '18 at 16:25
1

This is not directly related to your question, but since you are changing colors you can also fix the corner pixels which stand out (by corner pixels I don't mean pixels at the edges of bitmap rectangle, I mean the corner of human recognizable image)

Example, in image below, there is a red pixel at the corner of the page. We want to find that red pixel and blend it with background color so that it doesn't stand out.

enter image description here

To find if the corner pixels, check the pixels at left and top, if both left and top are the background color then you have a corner pixel. Repeat the same for top-right, bottom-left, and bottom-right. Blend the corner pixels with background.

Instead of changing to grayscale you can change the alpha transparency as suggested by zett42.

void change(HBITMAP hbmp, bool enabled)
{
    if(!hbmp)
        return;
    HDC memdc = CreateCompatibleDC(nullptr);

    BITMAP bm;
    GetObject(hbmp, sizeof(bm), &bm);
    int w = bm.bmWidth;
    int h = bm.bmHeight;
    BITMAPINFO bi = { sizeof(BITMAPINFOHEADER), w, h, 1, 32, BI_RGB };

    std::vector<uint32_t> pixels(w * h);
    GetDIBits(memdc, hbmp, 0, h, &pixels[0], &bi, DIB_RGB_COLORS);

    //assume that the color at (0,0) is the background color
    uint32_t old_color = pixels[0];

    //this is the new background color
    uint32_t bk = GetSysColor(COLOR_MENU);

    //swap RGB with BGR
    uint32_t new_color = RGB(GetBValue(bk), GetGValue(bk), GetRValue(bk));

    //define lambda functions to swap between BGR and RGB
    auto bgr_r = [](uint32_t color) { return GetBValue(color); };
    auto bgr_g = [](uint32_t color) { return GetGValue(color); };
    auto bgr_b = [](uint32_t color) { return GetRValue(color); };

    BYTE new_red = bgr_r(new_color);
    BYTE new_grn = bgr_g(new_color);
    BYTE new_blu = bgr_b(new_color);

    //change background and modify disabled bitmap
    for(auto &p : pixels)
    {
        if(p == old_color)
        {
            p = new_color;
        }
        else if(!enabled)
        {
            //blend color with background, similar to 50% alpha
            BYTE red = (bgr_r(p) + new_red) / 2;
            BYTE grn = (bgr_g(p) + new_grn) / 2;
            BYTE blu = (bgr_b(p) + new_blu) / 2;
            p = RGB(blu, grn, red); //<= BGR/RGB swap
        }
    }

    //fix corner edges
    for(int row = h - 2; row >= 1; row--)
    {
        for(int col = 1; col < w - 1; col++)
        {
            int i = row * w + col;
            if(pixels[i] != new_color)
            {
                //check the color of neighboring pixels:
                //if that pixel has background color,
                //then that pixel is the background 

                bool l = pixels[i - 1] == new_color; //left pixel is background
                bool r = pixels[i + 1] == new_color; //right  ...
                bool t = pixels[i - w] == new_color; //top    ...
                bool b = pixels[i + w] == new_color; //bottom ...

                //we are on a corner pixel if:
                //both left-pixel and top-pixel are background or
                //both left-pixel and bottom-pixel are background or
                //both right-pixel and bottom-pixel are background or
                //both right-pixel and bottom-pixel are background
                if(l && t || l && b || r && t || r && b)
                {
                    //blend corner pixel with background
                    BYTE red = (bgr_r(pixels[i]) + new_red) / 2;
                    BYTE grn = (bgr_g(pixels[i]) + new_grn) / 2;
                    BYTE blu = (bgr_b(pixels[i]) + new_blu) / 2;
                    pixels[i] = RGB(blu, grn, red);//<= BGR/RGB swap
                }
            }
        }
    }

    SetDIBits(memdc, hbmp, 0, h, &pixels[0], &bi, DIB_RGB_COLORS);
    DeleteDC(memdc);
}

Usage:

CBitmap bmp1, bmp2;

bmp1.LoadBitmap(IDB_BITMAP1);
bmp2.LoadBitmap(IDB_BITMAP2);

change(bmp1, enabled);
change(bmp2, disabled);
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Thank you I will try this as soon as I can. – Andrew Truckle Apr 20 '18 at 21:52
  • I got a catach 22 here. The disabled icons looks much more obvious to me, so that is good. But ... the normal icons are not as good. Now the menu background is not being applied anymore and the colour of some of the images is changing. – Andrew Truckle Apr 21 '18 at 06:25
  • I changed one line of code (the swapping of the colors). And, I had to runt he program outside of VS. In VS the colours look odd but outside VS they are fine. :) – Andrew Truckle Apr 21 '18 at 06:34
  • oops, that was an error. I fixed the code, see "*** edit". Is that the part you fixed? Now I don't know why it looks different inside or outside of VS, maybe it's an unrelated issue (mix-up between debug and release version?) There is a section called "//fix corner pixels", you might want to remove that whole section. – Barmak Shemirani Apr 21 '18 at 07:27
  • I edited your code to “swap the pixel colours” for that line. Since that is what you did in your previous code to my other question. Or was it not needed? The VS issue might be related to WindowsBlinds. I will check this afternoon. – Andrew Truckle Apr 21 '18 at 07:32
  • 1
    I made yet another edit, this time I tested with dark background. Copy paste the whole thing for testing. – Barmak Shemirani Apr 21 '18 at 08:11
  • I think parts of your answer can also go into the original accepted answer because I now see how you reduce the intensity by 50%, by dividing the individual channels by 2. – Andrew Truckle Apr 21 '18 at 19:14
  • I admit though I can't visually understand what the "corner pixels" has really done but it is all looking good. So thanks. :) – Andrew Truckle Apr 21 '18 at 19:16
  • 1
    Good. I added some more explanation to the code. The part about BGR/RGB swap almost fried my brain, I added lambda functions to make it easier to follow, not sure if it makes it simpler. – Barmak Shemirani Apr 22 '18 at 00:42
  • Your revised code is not making sense. You already swapped to BGR for new_color. But then you reverse it again back to RGB when you split it down. I don’t even know why we have to swap the channels like this. – Andrew Truckle Apr 22 '18 at 06:03
  • Ok, I understand why you swap channels. So that we blend the colours. But my other comment stands. new_color by sedition is BGR already. But then you use new lambdas and put it back to RGB when you split it. – Andrew Truckle Apr 22 '18 at 06:18
  • 1
    Bitmap bits are stored in reverse order, BGR instead of RGB. In some parts I read the colors and swap, and then swap the colors and write. So the two swaps seem to cancel each other. But technically they should be there. Anyway, ideally this should have been done in GDI+ to avoid all this low level code. – Barmak Shemirani Apr 22 '18 at 06:35
  • I see. What is the difference with GDI+? I am only using this code and the other answers code as that was kindly provided. But I will update my code to match your answer. – Andrew Truckle Apr 22 '18 at 06:51
  • I see this: https://www.codeproject.com/Articles/3962/Using-GDI-with-MFC-or-native-C-C – Andrew Truckle Apr 22 '18 at 06:59