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
Full-size section... Showing the pixel-art style.
Last edited by ISAWHIM; Mar 21st, 2023 at 11:39 AM.
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.
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 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.
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.
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
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.
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
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:
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.
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.
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
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.
Re: [RESOLVED] Quick way to "colorize" a simple image
Originally Posted by VanGoghGaming
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.)
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.