Results 1 to 12 of 12

Thread: [RESOLVED] Quick way to "colorize" a simple image

  1. #1

    Thread Starter
    Addicted Member ISAWHIM's Avatar
    Join Date
    Jan 2023
    Posts
    170

    Resolved [RESOLVED] Quick way to "colorize" a simple image

    I know of a few "complex" ways to do this, but I am curious if there is a faster, better, or more simple way to do the following...

    NOTE: The attached image is reduced 66%, for example. It is pixel-art style blueprint. (4-colors, I swear) Also, ignore that it is all the same ship. I was just playing with a "max size" for one ship.)

    I have a simple 4-color image (possibly 256-color in the future). I want to be able to represent various color-schemes for various ship-classes, in my game. The easy way is to just make several 16-color BMP files or save the palettes for each variation, and swap palettes to get new variations. (Not sure how to do easy palett-swaps)

    These color variations are simply created, in my art program, by using the "Colorize" option on that ONE layer.

    The other thing I was thinking, since there are only 4-colors used. Use a 256-color palette and shift the colors to an offset value for each image. One palette, vs 6x individual palettes of 16-colors. VB6 and GDI seems to favor 2-color, 256-colors or (24-bit) 16,777,216-color, for BMPs.

    Now, these images are created with layers, which are just merged to create the final image, in my paint program. Black is just the background color and not 100% needed "in the image". The grey color is also a background color, under the coloring of the ship's blueprint. That can also be excluded and reduced to a single 2-color, transparent, image. Which just leaves the "blueprint", which has 2-colors and it could use the same "transparent form" from the "grey" outline image, previously mentioned.

    So... Any creative thoughts?

    I do plan to use GDI API for this, but I am not really interested in feeding this to GDI+ API. (Only because it is a bit of overkill, for this use, I think. Maybe not for palette-shifting or colorizing.)

    The significance of the colors are sort-of the following...
    Red: Danger, this'll FU up! (Heavily armored and stacked with weapons)
    Yellow: Caution, People on board
    Green: Inert vehicle, not a threat, just passing through
    Cyan: People and Cargo, cryo transporter, made for jumping (Ice cold)
    Blue: Clearly a color universal to "enforcement" or "police" or "security" (Surely packing and tracking)
    Violet/Magenta: It's going fast, because it can... Probably not because it's after you! (Usually not armed with attack weapons, only defensive)
    Grey: Unidentified class (Only in radar-view, or all ships in a non-upgraded "console".) * Not shown in image

    Name:  ShipColors.jpg
Views: 215
Size:  26.6 KB

    Full-size section... Showing the pixel-art style.
    Name:  Image11.png
Views: 447
Size:  1.5 KB
    Last edited by ISAWHIM; Mar 21st, 2023 at 11:39 AM.

  2. #2
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,132

    Re: Quick way to "colorize" a simple image

    Not sure that I understand, but here's a whack at it.

    Source bitmap with 2 "foreground colors" white and half-white or whatever else you want. For each FG color to change fill a new bitmap with the new color and TransparentBlt() the source image onto it specifying the old color as transparent.

    Quick hack attached.
    Attached Files Attached Files

  3. #3
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,132

    Re: Quick way to "colorize" a simple image

    I tried brute force looping through a DIB, but it was slower for me by a noticeable degree.

  4. #4

    Thread Starter
    Addicted Member ISAWHIM's Avatar
    Join Date
    Jan 2023
    Posts
    170

    Re: Quick way to "colorize" a simple image

    Quote Originally Posted by dilettante View Post
    Not sure that I understand, but here's a whack at it.
    Nailed it. Looking at the code to see how it compares to the two other solutions I found. Quite similar actually.

    With an EXTENSIVE and exhausting deep search, I eventually stumbled across the API for the GDI functions GetDIBColorTable and SetDIBColorTable.

    That, in turn, led me to some really old posts. (It seems that few people use "fast" 256 color images for anything, anymore. Everyone loves bloated images as much as they love bloated code and websites.) Also, there is a TON of useless code for "windows palettes", which was the old way to change forms colors, which has nothing to do with image palettes. (That was replaced with "windows styles". Once everyone abandoned 256 color monitors, over 40 years ago.)

    The method they use, only differs from your method, by directly manipulating the palette, by index numbers. As opposed to replacing the colors of the actual image.

    The two solutions I found are a little different also. I trust the one that LaVolpe provided a bit more, for "completeness", over the first one I found, which seems like it may be missing one or two critical lines of code, related to clean-up and "selection". But I don't know enough about the methods to honestly say, for certain, if it is missing something. (Researching that still.)

    The two solution links are below...
    The one that is possibly missing code, for completeness. https://www.vbforums.com/showthread....ckly&p=3556028
    LaVolpe's solution, with a bit MORE code than needed. (I assume he had it laying around from another post solution.) https://www.vbforums.com/showthread....-Image-problem

    The code that I eventually used, which was modified a bit. (Form needs a picture-box and 3x Option-selections, as indexed controls. 1=Red, 2=Green, 3=Blue. And an image where palette #1 and #2 is the two colors being changed.) I left the "whole palette swap", but that cuold have been coded to just be the range 1-2, stating at index 1. As opposed to 0-255, starting at 0.

    Also a BIG note... RGBQUAD is actually BGR, for some stupid reason. LaVolpe coded around that. I just mentally swapped my RGB vals in VB6's RGB(R, G, B) code to RGB(B, G, R)

    Code:
    Option Explicit
    ' ??? BGR(RGBQUAD) not RGB
    Private Type RGBQUAD
            B As Byte
            G As Byte
            R As Byte
            A As Byte
    End Type
    
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)
    
    Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
    Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
    Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
    Private Declare Function GetDIBColorTable Lib "gdi32" (ByVal hdc As Long, ByVal un1 As Long, ByVal un2 As Long, pRGBQuad As RGBQUAD) As Long
    Private Declare Function SetDIBColorTable Lib "gdi32" (ByVal hdc As Long, ByVal un1 As Long, ByVal un2 As Long, pcRGBQuad As RGBQUAD) As Long
    
    Dim iPAL(0 To 255) As RGBQUAD
    
    Private Sub Form_Load()
        ' Works with 16-color image too. Just change the iPAL(0 to 15) and Set/Get values too
        ' Also works with raw BMP or RLE-BMP encoding, "OS/2" or "Windows" formats
        Set Picture1.Picture = LoadPicture("C:\testimages\test256color2.bmp")
    End Sub
    
    Private Sub Option1_Click(Index As Integer)
        Dim xRGB As Long, oDC As Long, rDC As Long
        
        oDC = CreateCompatibleDC(0)
        rDC = SelectObject(oDC, Picture1.Picture.Handle)
        If GetDIBColorTable(oDC, 0, 255, iPAL(0)) <> 0 Then
            xRGB = Choose(Index, RGB(0, 0, 64), RGB(0, 32, 0), RGB(64, 16, 0))
            CopyMemory iPAL(1), xRGB, 4
            
            xRGB = Choose(Index, RGB(0, 0, 255), RGB(0, 255, 0), RGB(255, 64, 32))
            CopyMemory iPAL(2), xRGB, 4
            
            SetDIBColorTable oDC, 0, 255, iPAL(0)
        End If
        
        oDC = SelectObject(oDC, rDC) ' Restore DC selection, if there was one
        DeleteDC oDC
        Picture1.Refresh
    End Sub
    Last edited by ISAWHIM; Mar 22nd, 2023 at 07:41 AM.

  5. #5
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    8,455

    Re: Quick way to "colorize" a simple image

    Quote Originally Posted by ISAWHIM View Post
    Also a BIG note... RGBQUAD is actually BGR, for some stupid reason. LaVolpe coded around that. I just mentally swapped my RGB vals in VB6's RGB(R, G, B) code to RGB(B, G, R)
    RGB is a kind of word order notation, not byte order. You read it like you would read a normal number like 236. The 2 in 236 is the most significant digit and the 6 is the least significant digit. It's the same with RGB. R is most significant and B is least significant. Since we are programming for Windows which runs on Intel processors, numbers are represented in little endian which means the least significant byte comes first hence BGR is the actual byte order of pixels on Windows.
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena

    Copy/move files using Windows Shell | I'm not wanted

    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. - jmcilhinney

    The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber

  6. #6

    Thread Starter
    Addicted Member ISAWHIM's Avatar
    Join Date
    Jan 2023
    Posts
    170

    Re: Quick way to "colorize" a simple image

    Quote Originally Posted by Niya View Post
    the least significant byte comes first hence BGR is the actual byte order of pixels on Windows.
    But NONE, except this one, uses BGR and calls it RGB. If it is BGR it should be called BGR, or use RGB as BGR, when everything vb6 and windows codes, uses actual RGB as RGB for everything. Where RGB() is actually RGBA, but ARGB in order in the RGB() function, where you can't set the A. (Actual BMP format is RGBA)

    BMP format is RGB, in the file and in the header. Which adds more confusion.
    Name:  zad_vga_tab_struktura.gif
Views: 78
Size:  45.3 KB

    forms/fonts/buttons/etc Color = RGB()
    BibBlt = RGB()
    HTML = RGB()
    CSS = RGB()
    GDI = RGB()
    DX = RGB()
    GL = RGB()

    It was just the fact that they clearly say RGB, but define RGB as BGR, and the actual use is BGR, but in the RGB value?!?!
    Last edited by ISAWHIM; Mar 27th, 2023 at 02:13 PM.

  7. #7
    PowerPoster
    Join Date
    Feb 2006
    Posts
    24,132

    Re: [RESOLVED] Quick way to "colorize" a simple image

    I think you are still confused about the byte order.

    The old RGB() function returns a degenerate case of OLE_COLOR where OleColorType = Default.

  8. #8

    Thread Starter
    Addicted Member ISAWHIM's Avatar
    Join Date
    Jan 2023
    Posts
    170

    Re: [RESOLVED] Quick way to "colorize" a simple image

    Quote Originally Posted by dilettante View Post
    I think you are still confused about the byte order.

    The old RGB() function returns a degenerate case of OLE_COLOR where OleColorType = Default.
    VB6 RGB() function
    https://learn.microsoft.com/en-us/of...p/rgb-function

    While VB.NET uses ARGB() function
    https://learn.microsoft.com/en-us/do...b?view=net-7.0

    Same long value, except in .NET you can actually set the A (alpha) value. In VB6, you have to set it with a CopyMemory trick, or just literally set the LONG value as a LONG.

    I use RGB() to quickly convert a long into individual Byte-Values. :P I can get the (R, G, or B), or set a long's value with individual bytes, faster, devoid of the "A" value. Using the CopyMemory trick.

    I have to deal with some of Pythons screwy graphics libraries, which uses BGR too... But they clearly have the correct type-formats for all versions. RGB, BGR, RGBA, BGRA. Even extending those to ARGB and ABGR, for even more stupidity related to "copyright toe-stepping" in functions. (So they can claim that the code is different, because they process things differently than some other library that does the same thing in RGBA standard format. I am referring, specifically to OpenCV, which I use a LOT in my AI programming.) They use RGB2BGR and BGR2RGB functions to do simple conversions. How kind... Instead of just using the actual standard formats for everything, which everything-else uses.

    I'm just going off what it says, vs what it is. The "type" was created by Microsoft themselves. Identified as RGB, but TYPED as BGR. While the actual VB6 function RGB() is the correct order, used everywhere. Microsoft's other RGB "TYPES", are actually RGB ordered. My real question is... Why, of all the GDI and GDI+ functions, is THIS used, at all, on only THIS specific function.?

    By all technical aspects, it should be named BGRQUAD, which is what it is.

    Code:
    Private Type RGBQUAD
            B As Byte
            G As Byte
            R As Byte
            A As Byte
    End Type
    https://learn.microsoft.com/en-us/wi...wingdi-rgbquad

    GDI contradiction... (DIB related. Here they say it's RGB for a palette. "The DIBColors enumeration defines how to interpret the values in the color table of a DIB.")
    https://learn.microsoft.com/en-us/op...a-ed5a48a7fda1
    "DIB_RGB_COLORS: The color table contains literal RGB values."
    https://learn.microsoft.com/en-us/wi.../nf-wingdi-rgb
    Notice the order is RGB, not BGR.

    The following is from this page. MS doesn't even have info that I can find on BMP 2.x format anymore. (Apparently it predates Win95, where BMP 4.x was used.)
    https://www.fileformat.info/format/bmp/egff.htm

    The Windows 2.x bitmap header is identical to the OS/2 1.x bitmap header except that the Width and Height fields are signed values in Windows BMP files.

    Following the header is the color palette data. A color palette is always present in a BMP file if the bitmap data contains 1-, 4-, or 8-bit data. Twenty-four-bit bitmap data never uses a color palette (nor does it ever need to). Each element of the palette is three bytes in length and has the following structure:

    Code:
    typedef struct _Win2xPaletteElement
    {
    	BYTE Blue;      /* Blue component */
    	BYTE Green;     /* Green component */
    	BYTE Red;       /* Red component */
    } WIN2XPALETTEELEMENT;
    Blue, Green, and Red hold the color component values for a pixel; each is in the range 0 to 255.

    NOTE: No-one uses the ABOVE format anymore. Not even in GDI, except in this one specific function...
    GetDIBColorTable

    Not even the following function, counterpart to GetDIBColorTable, GetDIBits uses RGB, not BGR. "lpvBits" is in the format RGBA. I'm sure the BGR is being converted to RGB, which messes-again, with the order, one step more. :P

    Code:
    typedef _WinNtBitfieldsMasks
    {
    	DWORD RedMask;         /* Mask identifying bits of red component */
    	DWORD GreenMask;       /* Mask identifying bits of green component */
    	DWORD BlueMask;        /* Mask identifying bits of blue component */
    } WINNTBITFIELDSMASKS;
    RedMask, GreenMask, and BlueMask specify which bits in a pixel value correspond to a specific color in 16- and 32-bit bitmaps. The bits in these mask values must be contiguous and must not contain overlapping fields. The bits in the pixel are ordered from most significant to least significant bits. For 16-bit bitmaps, the RGB565 format is often used to specify five bits each of red and blue values, and six bits of green:

    Code:
    RedMask   = 0xF8000000;     /* 1111 1000 0000 0000 0000 0000 0000 0000 */
    GreenMask = 0x07E00000;     /* 0000 0111 1110 0000 0000 0000 0000 0000 */
    BlueMask  = 0x001F0000;     /* 0000 0000 0001 1111 0000 0000 0000 0000 */
    For 32-bit bitmaps, the RGB101010 format can be used to specify 10 bits each of red, green, and blue:

    Code:
    RedMask   = 0xFFC00000;     /* 1111 1111 1100 0000 0000 0000 0000 0000 */
    GreenMask = 0x003FF000;     /* 0000 0000 0011 1111 1111 0000 0000 0000 */
    BlueMask  = 0x00000FFC;     /* 0000 0000 0000 0000 0000 1111 1111 1100 */
    BMP Version 4 (Microsoft Windows 95)
    Version 4.x BMP files begin with the same 14-byte header as v2.x and v3.x BMP files. The file header is also followed by a bitmap header, which is an expanded version of the v3.x bitmap header, incorporating the mask fields of the NT BMP format. This v4.x bitmap header is 108-bytes in size and contains 17 additional fields:

    Code:
    typedef struct _Win4xBitmapHeader
    {
    	DWORD Size;            /* Size of this header in bytes */
    	LONG  Width;           /* Image width in pixels */
    	LONG  Height;          /* Image height in pixels */
    	WORD  Planes;          /* Number of color planes */
    	WORD  BitsPerPixel;    /* Number of bits per pixel */
    	DWORD Compression;     /* Compression methods used */
    	DWORD SizeOfBitmap;    /* Size of bitmap in bytes */
    	LONG  HorzResolution;  /* Horizontal resolution in pixels per meter */
    	LONG  VertResolution;  /* Vertical resolution in pixels per meter */
    	DWORD ColorsUsed;      /* Number of colors in the image */
    	DWORD ColorsImportant; /* Minimum number of important colors */
    	/* Fields added for Windows 4.x follow this line */
    
    	DWORD RedMask;       /* Mask identifying bits of red component */
    	DWORD GreenMask;     /* Mask identifying bits of green component */
    	DWORD BlueMask;      /* Mask identifying bits of blue component */
    	DWORD AlphaMask;     /* Mask identifying bits of alpha component */
    	DWORD CSType;        /* Color space type */
    	LONG  RedX;          /* X coordinate of red endpoint */
    	LONG  RedY;          /* Y coordinate of red endpoint */
    	LONG  RedZ;          /* Z coordinate of red endpoint */
    	LONG  GreenX;        /* X coordinate of green endpoint */
    	LONG  GreenY;        /* Y coordinate of green endpoint */
    	LONG  GreenZ;        /* Z coordinate of green endpoint */
    	LONG  BlueX;         /* X coordinate of blue endpoint */
    	LONG  BlueY;         /* Y coordinate of blue endpoint */
    	LONG  BlueZ;         /* Z coordinate of blue endpoint */
    	DWORD GammaRed;      /* Gamma red coordinate scale value */
    	DWORD GammaGreen;    /* Gamma green coordinate scale value */
    	DWORD GammaBlue;     /* Gamma blue coordinate scale value */
    } WIN4XBITMAPHEADER;
    It was just a note in the program, to be aware of. Someone is going to see RGBQUAD and try filling it with an actual RGB value, and wonder why it's drawing the wrong colors.
    Last edited by ISAWHIM; Mar 27th, 2023 at 07:06 PM.
    Please, chime-in on my latest WIP.
    I can use all the help I can get.
    [VB6 Game], (In Development), "Galactic-Bondsman"

  9. #9
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    8,455

    Re: [RESOLVED] Quick way to "colorize" a simple image

    Quote Originally Posted by ISAWHIM View Post
    VB6 RGB() function
    https://learn.microsoft.com/en-us/of...p/rgb-function

    While VB.NET uses ARGB() function
    https://learn.microsoft.com/en-us/do...b?view=net-7.0

    Same long value, except in .NET you can actually set the A (alpha) value. In VB6, you have to set it with a CopyMemory trick, or just literally set the LONG value as a LONG.
    VB.Net actually uses word order, so it's actually BGRA. If you do this in .Net:-
    Code:
            Dim c = Color.FromArgb(10, 15, 20).ToArgb()
    
            Debug.WriteLine("First byte: " & (c And &HFF).ToString)
            Debug.WriteLine("Last byte: " & ((c And &HFF0000) >> 16).ToString)
    You get this:-
    Code:
    First byte: 20
    Last byte: 10
    Blue is first and red is last so the order is actually BGRA. Contrast that with VB6:-
    Code:
        Dim c As Long
       
        c = RGB(10, 15, 20)
        
        Debug.Print "First byte: " & CStr(c And &HFF)
        Debug.Print "Last byte: " & CStr((c And &HFF0000) / (2 ^ 16))
    You get this:-
    Code:
    First byte: 10
    Last byte: 20
    Here red is first and blue is last so the actual order is RGB.

    The bottom line is that even Microsoft can't make up their mind whether what byte order they want. In my own experience, I've seen BGRA more often than ARGB.
    Last edited by Niya; Mar 27th, 2023 at 07:12 PM.
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena

    Copy/move files using Windows Shell | I'm not wanted

    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. - jmcilhinney

    The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber

  10. #10
    Fanatic Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    539

    Cool Re: [RESOLVED] Quick way to "colorize" a simple image

    The VB6 RGB() function takes three parameters p1, p2, p3 and returns "p1+p2*256+p3*65536", that's all it does and it doesn't care about the order of parameters that you supply. Here's a better replacement for it that also preserves the alpha byte:

    Code:
    Private Sub ARGB(cRGB As Long, cRGBQ As RGBQUAD, Optional bFromRGBQ As Boolean = True)
        If bFromRGBQ Then cRGB = AsLong(cRGBQ) Else AsLong(cRGBQ) = cRGB
    End Sub
    Where AsLong is a Property Get/Let which encapsulates the "GetMem4" and "PutMem4" functions.

  11. #11

    Thread Starter
    Addicted Member ISAWHIM's Avatar
    Join Date
    Jan 2023
    Posts
    170

    Re: [RESOLVED] Quick way to "colorize" a simple image

    Quote Originally Posted by VanGoghGaming View Post
    Here's a better replacement for it that also preserves the alpha byte:
    ... which encapsulates the "GetMem4" and "PutMem4" functions.
    I'd give you more rep for that post, but I hit my REP limit for you.

    Yes, I used something similar, for various things. Usually my main concern is speed in conversions. That's why I exploit the RGB() function, which is faster than doing the math. No math is done when converting RGB() into a long, it's just inserting the individual bytes into the "LONG" datatype.

    -RGB = RGB()
    XXXX = Long

    If I want to add the missing "-" (A) value, I just have to math that out, or use a direct CopyMemory. (Or one of the ones you mentioned would also work.)

    That is faster than... (In all my time-demos)
    Code:
    With RGBQUAD
        .R = 200
        .G = 0
        .B = 300
    End With
    or, correcting the RGB...
    Code:
    With RGBQUAD
        .R = MyRGB.B
        .G = MyRGB.G
        .B = MyRGB.R
    End With
    Or the above "math conversion" to virtually "shift" the bits, with math.

    Oh how a >> or a << function would come in handy in VB6!

    NOTE: I used only CopyMemory in my time-demo, not the two other ""GetMem4" and "PutMem4" functions". I'm not sure they would be any faster, being more calls, which is what killed the time-demos with "CopyMemory", vs using RGB() function to set byte-values into a long datatype. (The ones that could be set.)
    Please, chime-in on my latest WIP.
    I can use all the help I can get.
    [VB6 Game], (In Development), "Galactic-Bondsman"

  12. #12
    Fanatic Member VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    539

    Talking Re: [RESOLVED] Quick way to "colorize" a simple image

    GetMem and PutMem are faster than CopyMemory simply because CopyMemory does additional processing beside just moving the data from one place to another. The specifics elude me because I didn't care much at the time I was reading that info. You should get the TypeLib from http://www.xbeat.net/vbspeed/i_VBVM6Lib.html#Casting to get "AsLong" instead. There are several other useful functions and properties in that TypeLib.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width