The MASM Forum

General => The Workshop => Windows API => Topic started by: NoCforMe on January 17, 2024, 08:37:55 AM

Title: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 17, 2024, 08:37:55 AM
I'm trying to create a bitmap (BMP) file programmatically by drawing into a device context with GDI functions and then saving the contents to a BMP file, but I don't know quite how to do this. I have some of the pieces of the puzzle figured out, but not how the whole thing fits together.

I could just try this and try that until I get it to work; indeed, I've done this lots of times before. But even if I do get it working, there would be an uneasy filling that I don't know why it works. So rather than trying randomly I'm asking the folks here for advice. Better to understand the problem before attacking it.

I'd rather not go into specific code at this point; code is OK, but what I really would like is a conceptual understanding of what I'm trying to do. And I'd prefer to use GDI, not GDI+, although I'll use that if that's the only option. (I don't think it is, though.) So maybe use pseudocode or better yet just a good explanation.

So I know how to create a DC and draw into it with GDI. I know how to create a bitmap file, how to deal with all those various structures (BITMAPINFO and friends). What I can't figure out is this: Once I have a DC with my content, and I have the structures to create the BMP file, how do I transfer the contents of the drawn-into DC to the bits of the BMP?

I understand a device context to be an object within Windows that contains a memory bitmap of the image I want. Of course that memory bitmap isn't accessible to the outside world. But somehow I need to transfer that bitmap to my in-memory bitmap. Once I have that I know how to create the BMP file correctly. I know I can use BitBlt() to transfer between two DCs, but that doesn't help me here.

I'd like to be able to create the bitmap as a 16- or 256-color paletted image or a 24-bit image.

The attached diagram shows how I think this should work, and it shows the areas that I don't really understand. It may turn out that my idea of the logic flow here is flawed; if so, feel free to correct it.

Couple things I've dealt with: I considered using CreateDIBSection(), which would give me access to the actual bitmap bits in memory. But I don't think I can use this, because by choosing the DIB_RGB_COLORS flag, I lose the DC parameter, so I don't see how I could connect with my DC. (The other option, DIB_PAL_COLORS, is for indexed colors and is pretty much obsolete, so I can't use that.)

Ditto for CreateCompatibleBitmap(). I can use this to create a bitmap attached to a DC, but then I have no control over the format of the BMP, since "the color format of the bitmap created by the CreateCompatibleBitmap function matches the color format of the device identified by the hdc parameter.".

Help!
Title: Re: Creating a bitmap programmatically: how?
Post by: jj2007 on January 17, 2024, 08:43:49 AM
Quote from: NoCforMe on January 17, 2024, 08:37:55 AMthere would be an uneasy filling that I don't know why it works

Easy: fill yourself a glass of red wine and read what BitBlt & friends do (https://stackoverflow.com/questions/9524393/how-to-capture-part-of-the-screen-and-save-it-to-a-bmp). There's also M$ Learn (https://learn.microsoft.com/en-gb/windows/win32/gdi/bitmap-storage?redirectedfrom=MSDN) :cool:
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 17, 2024, 08:58:58 AM
Sorry to say this, JJ, but that wasn't very helpful. (Well, the red wine part was!) I'm sure you can do better than that.

The first link may have some clues, and more on that below. The 2nd link I already know all about (dealing with bitmap file data).

That Stack Overflow page is interesting, mainly this:
bool screenCapturePart(int x, int y, int w, int h, LPCSTR fname){
    HDC hdcSource = GetDC(NULL);
    HDC hdcMemory = CreateCompatibleDC(hdcSource);

    int capX = GetDeviceCaps(hdcSource, HORZRES);
    int capY = GetDeviceCaps(hdcSource, VERTRES);

    HBITMAP hBitmap = CreateCompatibleBitmap(hdcSource, w, h);
    HBITMAP hBitmapOld = (HBITMAP)SelectObject(hdcMemory, hBitmap);

    BitBlt(hdcMemory, 0, 0, w, h, hdcSource, x, y, SRCCOPY);
    hBitmap = (HBITMAP)SelectObject(hdcMemory, hBitmapOld);

    DeleteDC(hdcSource);
    DeleteDC(hdcMemory);

    HPALETTE hpal = NULL;
    if(saveBitmap(fname, hBitmap, hpal)) return true;
    return false;
}

Now, this is for capturing the screen, so I can just substitute my own DC for the first one (they used GetDC(NULL) to capture the screen, I can just create a DC and draw into it). And it will eventually give me a handle to a BMP which I can deal with.

But one problem (at least): how do I dictate the format (color depth) of the bitmap? Or can I even, or am I stuck with whatever format my DC is in?

These are the kinds of questions I need answers to.
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 17, 2024, 09:06:02 AM
I may have figured out part of this. Regarding the format (color depth) of the bitmap file, since the format I want may be different from the DC format, it turns out that BitBlt() will take care of this for me, since according to Micro$oft
QuoteIf the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format.

So I might go ahead and try that code above and see how it works.
Title: Re: Creating a bitmap programmatically: how?
Post by: HSE on January 17, 2024, 09:13:57 AM
Hi NoC!

Quote from: NoCforMe on January 17, 2024, 08:37:55 AMtrying to create a bitmap (BMP) file programmatically by drawing into a device context with GDI

  MAthArt: 32 / 64 bit demo program ... (https://masm32.com/board/index.php?topic=4216.0)

  MathArt's Interface and the BITMAP thing (https://masm32.com/board/index.php?topic=6339.0)

Not exactly what you want, but perhaps some ideas.

Regards, HSE

Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 17, 2024, 10:06:17 AM
Thanks, HSE, but all of that is waaaay too complicated.

I wish someone would approach my questions here from a conceptual standpoint, not with complex code examples. Surely someone here understands how GDI works better than I do.
Title: Re: Creating a bitmap programmatically: how?
Post by: sinsi on January 17, 2024, 03:56:53 PM
Quote from: NoCforMe on January 17, 2024, 08:37:55 AMSo I know how to create a DC and draw into it with GDI.
Can you post some code that shows how?

You may have to get all of the sections assembled, convert the palette and write everything.
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 17, 2024, 04:19:45 PM
At this point I really, really would rather engage in a general discussion of GDI, device contexts, etc. I want to know how these things work. I think I have at this point a pretty good general idea of how to pull this project off, but there are nagging questions. The current one is this: assuming I can get those DC bits transferred to my BMP bits, what about the palette (assuming a 16- or 256-color image)? Do I need to construct the palette myself? (I know how to do that, but am not sure of how it fits in with the overall scheme.) Or can I somehow magically retrieve a palette that's been constructed for the image?

Do you see what I'm getting at here?

Yes, I will have to assemble all the sections (BMP file header, BMPINFO header, palette and bitmap bits) myself. I do know how to do that.
Title: Re: Creating a bitmap programmatically: how?
Post by: sinsi on January 17, 2024, 04:39:02 PM
So, as I see it, you just need to convert a palette?
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 17, 2024, 05:06:01 PM
I need to get a palette. If, for instance, I know the image is grayscale, 256 colors, then I can easily create a palette. But I'm not sure I can know that.

I'll reveal a little bit more of what I'm trying to do: I plan on writing text into the DC using GDI. I want bitmaps of font characters. So I have no idea what colors I'm going to get when I render text into my DC. Will it be grayscale? or a whole rainbow of colors? (which is likely if the font quality is CLEARTYPE_QUALITY.)

So I need to know: how do I obtain the palette for the BMP image? Will it be generated by the OS and somehow come from the DC? or do I need to create it?

Of course I could sidestep this issue for the time being by creating a 24-bit bitmap, which has no palette. But I still won't know how to deal with paletted images, which I definitely want to be able to do.

Which is just one of the conceptual issues I'm dealing with here.

Even if someone came along 5 minutes from now and gave me code that works, that's not what I want. I want to understand how this all works.
Title: Re: Creating a bitmap programmatically: how?
Post by: daydreamer on January 17, 2024, 07:02:47 PM
If you want rainbow colored text, creating 256 color rainbow palette and write to empty. Bmp file to start with
768 bytes palette included in the start of 8 bit. Bmp file
Invoke loadbitmap
Title: Re: Creating a bitmap programmatically: how?
Post by: _japheth on January 17, 2024, 08:19:34 PM
Quote from: NoCforMe on January 17, 2024, 05:06:01 PMI need to get a palette.
...
Even if someone came along 5 minutes from now and gave me code that works, that's not what I want. I want to understand how this all works.

Well, even if it doesn't is what you want, here's a small sample with just 2 procs that writes the desktop screen to a .bmp file. It's very simple, using a generated filename in the TEMP directory. But it handles the "palette case".


;--- save graphics screen as a .BMP file

.386
.Model Flat, StdCall
Option Casemap :None   

Include \masm32\include\windows.inc
Include \masm32\include\kernel32.inc
INCLUDE \masm32\include\Gdi32.inc
Include \masm32\include\user32.inc
include macros.inc

IncludeLib \masm32\lib\kernel32.lib
INCLUDELIB \masm32\lib\Gdi32.lib
IncludeLib \masm32\lib\user32.lib

.code

SaveBitmapAsFile proc uses ebx hFile:dword, hdc:DWORD, hBitmap:DWORD

local dwSize:DWORD
local dwSizeClr:DWORD
local dwWritten:DWORD
local lpBits:DWORD
local rgbquad[255]:RGBQUAD
local bmi:BITMAPINFO
local bmfh:BITMAPFILEHEADER

mov lpBits, 0
invoke RtlZeroMemory, addr bmfh, sizeof BITMAPFILEHEADER
invoke RtlZeroMemory, addr bmi.bmiHeader, sizeof BITMAPINFOHEADER
mov bmi.bmiHeader.biSize,sizeof BITMAPINFOHEADER
;--- set biBitCount to 0 to get the current bitmap format
mov bmi.bmiHeader.biBitCount,0
mov bmi.bmiHeader.biCompression,BI_RGB
invoke GetDIBits, hdc, hBitmap, 0, 0, 0, addr bmi, DIB_RGB_COLORS
.if (!eax)
invoke MessageBox, 0, CStr("1. call of GetDIBits() failed"), 0, MB_OK
            jmp exit
        .endif
mov eax, bmi.bmiHeader.biHeight
mul bmi.bmiHeader.biWidth
movzx ecx, bmi.bmiHeader.biBitCount
mul ecx
shr eax, 3
mov dwSize, eax

invoke LocalAlloc, LMEM_FIXED, dwSize
.if (!eax)
invoke MessageBox, 0, CStr("insufficient memory"), 0, MB_OK
            jmp exit
.endif
mov lpBits, eax

invoke GetDIBits, hdc, hBitmap, 0, bmi.bmiHeader.biHeight, lpBits, addr bmi, DIB_RGB_COLORS
        .if (!eax)
invoke MessageBox, 0, CStr("2. call of GetDIBits() failed"), 0, MB_OK
            jmp exit
        .endif

.if (bmi.bmiHeader.biBitCount == 8)
mov eax, sizeof RGBQUAD * 256
.elseif (bmi.bmiHeader.biCompression == BI_BITFIELDS)
mov eax, sizeof DWORD * 3
.else
xor eax, eax
.endif
mov dwSizeClr, eax

mov bmfh.bfType, "MB"
        mov eax, dwSize
mov bmfh.bfSize, eax
        mov eax, dwSizeClr
lea eax, [eax + sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER]
mov bmfh.bfOffBits, eax
invoke WriteFile, hFile, addr bmfh, sizeof BITMAPFILEHEADER, addr dwWritten, 0

invoke WriteFile, hFile, addr bmi.bmiHeader, sizeof BITMAPINFOHEADER, addr dwWritten, 0
.if (dwSizeClr)
invoke WriteFile, hFile, addr bmi.bmiColors, dwSizeClr, addr dwWritten, 0
.endif
invoke WriteFile, hFile, lpBits, dwSize, addr dwWritten, 0
exit:
.if (lpBits)
invoke LocalFree, lpBits
        .endif
ret
SaveBitmapAsFile endp
       

CreateScreenBitmap proc

    local hDCSrc ,hDCMemory, hBitmap
    local dwWidth, dwHeight
    local hFile, dwFileIdx
local szTempPath[MAX_PATH]:byte
local szTempName[MAX_PATH]:byte

    invoke GetSystemMetrics,SM_CXSCREEN
    mov dwWidth,eax
    invoke GetSystemMetrics,SM_CYSCREEN
    mov dwHeight,eax
    invoke GetDC,0
    mov hDCSrc,eax   
    invoke CreateCompatibleDC,eax
    mov hDCMemory,eax
    invoke CreateCompatibleBitmap,hDCSrc,dwWidth,dwHeight
    mov hBitmap,eax
    invoke SelectObject,hDCMemory,eax
    invoke BitBlt,hDCMemory,0,0,dwWidth,dwHeight,hDCSrc,0,0,SRCCOPY

invoke GetTempPath, MAX_PATH, addr szTempPath
    mov dwFileIdx, 0
.while (1)
inc dwFileIdx
        invoke wsprintf, addr szTempName, CStr("%s\~hx%u.BMP"), addr szTempPath, dwFileIdx
invoke CreateFile, addr szTempName, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0
.break .if (eax != -1)
.endw
mov hFile, eax
    invoke SaveBitmapAsFile, hFile, hDCMemory, hBitmap
invoke CloseHandle, hFile
    invoke ReleaseDC,0,hDCSrc
    invoke DeleteDC, hDCMemory
    invoke DeleteObject, hBitmap
    ret
   
CreateScreenBitmap endp

start:
   invoke CreateScreenBitmap
   invoke ExitProcess,NULL

end start

Title: Re: Creating a bitmap programmatically: how?
Post by: Greenhorn on January 18, 2024, 02:32:38 AM
Quote from: NoCforMe on January 17, 2024, 05:06:01 PMEven if someone came along 5 minutes from now and gave me code that works, that's not what I want. I want to understand how this all works.

Progamming Windows, 5th Edition (https://vulms.vu.edu.pk/Courses/CS410/Downloads/Charles%20Petzold%20-%20Programming%20Windows%20-%205th%20Ed.pdf)

Read the section about Bitmaps, that should help.
Title: Re: Creating a bitmap programmatically: how?
Post by: Vortex on January 18, 2024, 05:42:21 AM
Hi NoCforMe,

Maybe this code can be helpful for you, this command line tools captures the desktop and saves it to a file :

; References :
; https://msdn.microsoft.com/en-us/library/windows/desktop/dd145119%28v=vs.85%29.aspx
; https://msdn.microsoft.com/en-us/library/windows/desktop/dd183402%28v=vs.85%29.aspx

include     SaveDesktop.inc

.data
message     db 'SaveDesktop.exe filename.bmp [optional-32bits]',0

.data?
buffer      db 512 dup(?)

.code

start PROC

    call    main
    invoke  ExitProcess,0

start ENDP

main PROC uses rsi rdi

LOCAL argc       :QWORD
LOCAL hwnd       :QWORD
LOCAL pMem       :QWORD
LOCAL hBmp       :QWORD
LOCAL hDC        :QWORD
LOCAL tempDC     :QWORD
LOCAL x          :QWORD
LOCAL y          :QWORD
LOCAL hOldBmp    :QWORD
LOCAL hFile      :QWORD
LOCAL BmpSize    :QWORD
LOCAL BWritten   :QWORD
LOCAL SizeofDIB  :QWORD
LOCAL bm         :BITMAP
LOCAL Bits       :QWORD
LOCAL BmpFileHdr :BITMAPFILEHEADER
LOCAL BmpInfoHdr :BITMAPINFOHEADER

    lea     rsi,buffer
    invoke  ParseCmdLine,rsi
    cmp     rax,2
    jnb     @f

    invoke  StdOut,ADDR message
    ret
@@:
    mov     argc,rax

    invoke  GetConsoleWindow
    mov     hwnd,rax
    invoke  ShowWindow,rax,SW_HIDE
    invoke  Sleep,300

    lea     rdi,bm
    invoke  GetDC,0                                 ; get the DC of desktop
    mov     hDC,rax
    invoke  CreateCompatibleDC,rax                  ; create a DC compatible with hDC
    mov     tempDC,rax
   
    invoke  GetSystemMetrics,SM_CXSCREEN            ; get the width and height of the desktop
    mov     x,rax
    invoke  GetSystemMetrics,SM_CYSCREEN
    mov     y,rax
   
    invoke  CreateCompatibleBitmap,hDC,x,rax        ; create a compatible bitmap
    mov     hBmp,rax
    invoke  SelectObject,tempDC,rax
    mov     hOldBmp,rax

    invoke  BitBlt,tempDC,0,0,x,y,hDC,\
            0,0,SRCCOPY                             ; copy the screen to the target DC

    invoke  GetObject,hBmp,sizeof(BITMAP),rdi       ; the BITMAP structure of the copied image

    cmp     argc,3
    je      @f
    mov     BITMAP.mBitsPixel[rdi],24               ; Fill the BITMAP structure
@@:
    xor     rdx,rdx
    movzx   rax,BITMAP.mPlanes[rdi]
    mul     BITMAP.mBitsPixel[rdi]
    mov     Bits,rax

    lea     rcx,BmpInfoHdr

    mov     BITMAPINFOHEADER.biSize[rcx],\
            sizeof(BITMAPINFOHEADER)

    mov     rax,x
    mov     BITMAPINFOHEADER.biWidth[rcx],eax

    mov     rax,y
    mov     BITMAPINFOHEADER.biHeight[rcx],eax
   
    mov     ax,BITMAP.mPlanes[rdi]
    mov     BITMAPINFOHEADER.biPlanes[rcx],ax

    mov     ax,BITMAP.mBitsPixel[rdi]
    mov     BITMAPINFOHEADER.biBitCount[rcx],ax

    xor     rax,rax                                 ; Fill the BITMAPINFOHEADER structure
    mov     BITMAPINFOHEADER.biCompression[rcx],BI_RGB   
    mov     BITMAPINFOHEADER.biSizeImage[rcx],eax
    mov     BITMAPINFOHEADER.biXPelsPerMeter[rcx],eax
    mov     BITMAPINFOHEADER.biYPelsPerMeter[rcx],eax 
    mov     BITMAPINFOHEADER.biClrUsed[rcx],eax
    mov     BITMAPINFOHEADER.biClrImportant[rcx],eax

    xor     rdx,rdx
    xor     rax,rax
    mov     eax,BITMAP.bmWidth[rcx]

;   Bitmap size = (( BITMAPINFO.bmiHeader.biWidth * BITMAP.bmPlanes * BITMAP.bmBitsPixel + 31 ) & ~31 ) / 8 *
;                    BITMAPINFO.bmiHeader.biHeight

;   ~31 = NOT 31 = NOT 00011111 = 11100000

;   BITMAP.bmWidthBytes : The number of bytes in each scan line.
;   This value must be divisible by 2,because the system assumes
;   that the bit values of a bitmap form an array that is word aligned.

;   shl     rax,2
 
    mul     Bits
    add     rax,31
    and     rax,-32                                  ; align the width to the
    shr     rax,3                                    ; next DWORD boundry
    mul     BITMAP.bmHeight[rcx]
    mov     BmpSize,rax

    invoke  VirtualAlloc,0,rax,MEM_COMMIT,\
            PAGE_READWRITE

    mov     pMem,rax

    invoke  GetDIBits,tempDC,hBmp,0,\
            QWORD PTR BITMAP.bmHeight[rdi],\
            pMem,ADDR BmpInfoHdr,DIB_RGB_COLORS

    xor     rax,rax
    invoke  CreateFile,QWORD PTR [rsi+8],\          ; write the bitmap structures and the color info
            GENERIC_WRITE,rax,rax,CREATE_ALWAYS,\   ; to the disc
            rax,rax

    mov     hFile,rax

    mov     rax,BmpSize
   
    add     rax,\
            sizeof(BITMAPFILEHEADER) + \
            sizeof(BITMAPINFOHEADER)

    mov     SizeofDIB,rax

    lea     rdx,BmpFileHdr
    mov     BITMAPFILEHEADER.bfOffBits[rdx],\
            sizeof(BITMAPFILEHEADER) + \
            sizeof(BITMAPINFOHEADER)

    mov     rax,SizeofDIB
    mov     BITMAPFILEHEADER.bfSize[rdx],eax

    mov     BITMAPFILEHEADER.bfReserved1[rdx],0
    mov     BITMAPFILEHEADER.bfReserved2[rdx],0

    mov     BITMAPFILEHEADER.bfType[rdx],'MB'

    lea     rsi,BWritten
    invoke  WriteFile,hFile,ADDR BmpFileHdr,\
            sizeof(BITMAPFILEHEADER),rsi,0
           
    invoke  WriteFile,hFile,ADDR BmpInfoHdr,\
            sizeof(BITMAPINFOHEADER),rsi,0
           
    invoke  WriteFile,hFile,pMem,BmpSize,rsi,0

    invoke  CloseHandle,hFile

    invoke  VirtualFree,pMem,0,MEM_RELEASE          ; return back resources to the OS
    invoke  SelectObject,tempDC,hOldBmp             ; return back the old handle
    invoke  DeleteObject,hBmp
    invoke  DeleteDC,tempDC
    invoke  ReleaseDC,0,hDC

    invoke  ShowWindow,hwnd,SW_SHOW
    ret
   
main ENDP

END
Title: Re: Creating a bitmap programmatically: how?
Post by: jj2007 on January 18, 2024, 07:38:03 AM
Quote from: NoCforMe on January 17, 2024, 08:58:58 AMSorry to say this, JJ, but that wasn't very helpful. (Well, the red wine part was!) I'm sure you can do better than that.

Sure:
- press Alt PrintSc
- launch attached SaveImage.exe
- size the image according to your taste
- right-click and save as test.bmp (or whatever.png or image.jpg or my.gif)


include \masm32\MasmBasic\Res\MbGui.asm
GuiControl MyCanvas, "canvas"
Event CanvasPaint
  GuiImage clipboard, fit
GuiEnd

(https://i.postimg.cc/rdb89m89/Save-Image.png) (https://postimg.cc/rdb89m89)
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 18, 2024, 12:45:14 PM
Quote from: Greenhorn on January 18, 2024, 02:32:38 AM
Quote from: NoCforMe on January 17, 2024, 05:06:01 PMEven if someone came along 5 minutes from now and gave me code that works, that's not what I want. I want to understand how this all works.

Progamming Windows, 5th Edition (https://vulms.vu.edu.pk/Courses/CS410/Downloads/Charles%20Petzold%20-%20Programming%20Windows%20-%205th%20Ed.pdf)

Read the section about Bitmaps, that should help.

No. No! Nyet! No no no!

Goddamnit, I'm starting to get pissed. NONE OF THIS HELPS!

I have Petzold's book (the physical one, AKA "the doorstop" or "the boat anchor"). I've read the stuff about bitmaps. None of it helps in this case.

So unless you can actually answer my questions without posting unexplained chunks of code, maybe just STFU, OK?
Title: Re: Creating a bitmap programmatically: how?
Post by: HSE on January 18, 2024, 02:10:14 PM
Quote from: NoCforMe on January 18, 2024, 12:45:14 PMNo. No! Nyet! No no no!


:biggrin:

You have some code to talk about? 
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 18, 2024, 03:14:45 PM
I'm working on it. Tomorrow I'll probably be in more of a programming mood. I have a glimmer of a clue what to do. Will report when I know for sure.
Title: Re: Creating a bitmap programmatically: how?
Post by: _japheth on January 18, 2024, 05:21:31 PM
QuoteNo. No! Nyet! No no no!

Goddamnit, I'm starting to get pissed. NONE OF THIS HELPS!

....

So unless you can actually answer my questions without posting unexplained chunks of code, maybe just STFU, OK?

This is getting a bit silly. A member with such a huge post count like yourself and unable or unwilling to look into 50-lines of Win32 code samples? Had you done that, you would have realized that there's more or less just ONE Windows API function ( GetDIBits() ) that you are to fully understand and all your "questions" are answered.



Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 18, 2024, 05:42:25 PM
Quote from: _japheth on January 18, 2024, 05:21:31 PMHad you done that, you would have realized that there's more or less just ONE Windows API function ( GetDIBits() ) that you are to fully understand and all your "questions" are answered.

I am coming to the realization that GetDIBits() is what I've been looking for. If only you (or someone else) had mentioned this earlier. Anyhow, more later ...
Title: Re: Creating a bitmap programmatically: how?
Post by: HSE on January 18, 2024, 10:29:41 PM
You have to read the code people take time to post. If you can not download the code... you are alone.
Title: Re: Creating a bitmap programmatically: how?
Post by: daydreamer on January 19, 2024, 12:24:55 AM
Thanks all for example code  :thumbsup:
Magnus
Title: Re: Creating a bitmap programmatically: how?
Post by: Greenhorn on January 19, 2024, 01:59:20 AM
Quote from: NoCforMe on January 18, 2024, 12:45:14 PMGoddamnit, I'm starting to get pissed. NONE OF THIS HELPS!

...

So unless you can actually answer my questions without posting unexplained chunks of code, maybe just STFU, OK?

OK, got it. You established to get on my ignore list.
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 19, 2024, 09:24:58 AM
Quote from: HSE on January 18, 2024, 10:29:41 PMYou have to read the code people take time to post. If you can not download the code... you are alone.

That would be true, if it was code I was asking for. If you'll notice, I specifically asked for conceptual help with my problem, not code. Go back through my posts and you'll see I've been very consistent about that. I want to know how the process works--in words--without having to plow through pages of code (which may or may not explain what I'm trying to understand). Do you get that?

But nobody seemed to get that. So now I'm the bad guy because I won't read every line of code that everyone posts here, eh? Well, whatever.

I am starting to work on the code now, so we'll see what develops.
Title: Re: Creating a bitmap programmatically: how?
Post by: HSE on January 19, 2024, 11:26:19 AM
> Do you get that?

:biggrin:  You don't get that you must have Vortex's code at hand.

A good opportunity to remark that for new members  :thumbsup:
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 19, 2024, 11:33:50 AM
Quote from: HSE on January 19, 2024, 11:26:19 AM:biggrin:  You don't get that you must have Vortex's code at hand.
Why? Remember, I didn't ask for code, I asked for words.

Besides, I do have some of Vortex's code here (related to bitmaps), and I have to say that it has helped me immensely in the past, so I do appreciate that.

Coding stuff up, getting closer ...
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 19, 2024, 02:34:14 PM
OK, I give up. I started coding this up, was able to create a bitmap file, but it came out blank (all black).

I'm posting code below, the core of the program which creates the bitmap. First, here's what I'm doing, in words, not code:

Problems:
1. I've tried 3 ways of creating the bitmap (the internal structure, not the BMP file). The first one runs but creates a blank (black) bitmap; the other two crash the program with exceptions. Here they are:
; 1st call creates blank (black) bitmap; other 2 cause exceptions:
    INVOKE    CreateBitmap, BMPwidth, BMPheight, 1, BMPbpp, OutputBMPbuffer
;    INVOKE    CreateCompatibleBitmap, bmpDC, BMPwidth, BMPheight
;    INVOKE    CreateDIBSection, bmpDC, ADDR bmi, DIB_RGB_COLORS, ADDR bmpBitsPtr, NULL, 0
    MOV    bmpHandle, EAX
Part of the problem is that I really don't know whether I need a DIB or a DDB. And yes, I've read all about that in Petzold, the differences between device-independent and device-dependent bitmaps. I still do not really understand the difference. It's a big problem since Petzold's book is so out-of-date: most of the stuff regarding DIB/DDB is long since obsolete (no more 16-color displays). So I would really appreciate it if someone could help by explaining the difference, in words, not code!

2. Not sure if GetDIBits() is actually transferring anything to my buffer or not. Oh, and I have figured one thing out, something which is really poorly explained by all of the documentation I've seen on this subject: that function transfers both the actual bitmap bits AND the palette, if there is one. At least it's supposed to ...

I know the text is being rendered in the text-rendering window because I can see it.

So here's the code. Have at it; go nuts; tear the shit out of it. Oh, BTW, "$BMPHEADER" is my private structure that combines the BITMAPFILEHEADER and BITMAPINFOHEADER into one chunk for ease of use. It's been verified through previous usage.
;====================================
; MakeTheBMP()
;
; On entry, the following globals are set:
;  o BMPwidth, BMPheight: size of bitmap to create
;  o BMPbpp: bits/pixel (currently set to 8 for 256-color BMP)
;  o TextRenderHandle is a handle to text-rendering window
;  o OutputBMPbuffer is a pointer to heap-allocated memory (128 Kb)
;====================================

MakeTheBMP    PROC
    LOCAL    srcDC:HDC, bmpDC:HDC, bitsSize:DWORD, bmpHandle:HBITMAP, actualBMPsize:DWORD
    LOCAL    bmpBitsPtr:DWORD, bmpFileHandle:HFILE, dummy:DWORD, bm:BITMAP, bmi:BITMAPINFO
    local    buffer[256]:byte

; Set up BITMAPINFO struct for GetDIBits():
    MOV    bmi.bmiHeader.biSize, SIZEOF BITMAPINFOHEADER
    MOV    EAX, BMPwidth
    MOV    bmi.bmiHeader.biWidth, EAX
    MOV    EAX, BMPheight
    MOV    bmi.bmiHeader.biHeight, EAX
    MOV    bmi.bmiHeader.biPlanes, 1
    MOV    EAX, BMPbpp
    MOV    bmi.bmiHeader.biBitCount, AX
    MOV    bmi.bmiHeader.biCompression, BI_RGB
    MOV    bmi.bmiHeader.biSizeImage, 0
    MOV    bmi.bmiHeader.biXPelsPerMeter, 0
    MOV    bmi.bmiHeader.biYPelsPerMeter, 0
    MOV    bmi.bmiHeader.biClrUsed, 0
    MOV    bmi.bmiHeader.biClrImportant, 0

; Render the text into the rendering window:
    INVOKE    InvalidateRect, TextRenderHandle, NULL, FALSE

    INVOKE    GetDC, TextRenderHandle
    MOV    srcDC, EAX
    INVOKE    CreateCompatibleDC, EAX
    MOV    bmpDC, EAX

; 1st call creates blank (black) bitmap; other 2 cause exceptions:
    INVOKE    CreateBitmap, BMPwidth, BMPheight, 1, BMPbpp, OutputBMPbuffer
;    INVOKE    CreateCompatibleBitmap, bmpDC, BMPwidth, BMPheight
;    INVOKE    CreateDIBSection, bmpDC, ADDR bmi, DIB_RGB_COLORS, ADDR bmpBitsPtr, NULL, 0
    MOV    bmpHandle, EAX
    INVOKE    SelectObject, bmpDC, bmpHandle

    INVOKE    BitBlt, bmpDC, 0, 0, BMPwidth, BMPheight, srcDC, 0, 0, SRCCOPY

; Calculate actual bitmap size including any needed padding:
    MOV    EAX, BMPwidth
    MUL    BMPbpp
    ADD    EAX, 31
    AND    EAX, NOT 31
    SHR    EAX, 3
    MUL    BMPheight
    MOV    actualBMPsize, EAX

; Construct BMP file header:
    PUSH    EDI
    MOV    EDI, OutputBMPbuffer        ;Point to heap-allocated buffer.

    MOV    [EDI].$BMPHEADER.bfType, "MB"
    MOV    [EDI].$BMPHEADER.bfHdrSize, 40
    MOV    EAX, BMPwidth
    MOV    [EDI].$BMPHEADER.bfWidth, EAX
    MOV    EAX, BMPheight
    MOV    [EDI].$BMPHEADER.bfHeight, EAX
    MOV    [EDI].$BMPHEADER.bfPlanes, 1
    MOV    EAX, BMPbpp
    MOV    [EDI].$BMPHEADER.bfBitCount, AX
    MOV    [EDI].$BMPHEADER.bfCompression, BI_RGB
    MOV    EAX, actualBMPsize
;    MOV    [EDI].$BMPHEADER.bfSizeImage, EAX
    MOV    [EDI].$BMPHEADER.bfSizeImage, 0
    MOV    [EDI].$BMPHEADER.bfXPelsPerMeter, 0
    MOV    [EDI].$BMPHEADER.bfYPelsPerMeter, 0
    CMP    BMPbpp, 24
    JNE    @F
    XOR    EAX, EAX            ;24bpp = no palette.
    JMP    SHORT setCus

@@:    MOV    EAX, 1
    MOV    ECX, BMPbpp
    SHL    EAX, CL
setCus:    MOV    [EDI].$BMPHEADER.bfClrUsed, EAX
    MOV    [EDI].$BMPHEADER.bfClrImportant, 0

    invoke    wsprintf, addr buffer, offset actualsizefmt, actualBMPsize
    invoke    MessageBox, NULL, addr buffer, NULL, MB_OK

; Copy bitmap bits from DC to file buffer:
    MOV    EDX, EDI
    ADD    EDX, SIZEOF $BMPHEADER
    INVOKE    GetDIBits, bmpDC, bmpHandle, 0, BMPheight, EDX,
        ADDR bmi, DIB_RGB_COLORS

; Calculate total file size:
    MOV    EAX, BMPbpp
    CMP    EAX, 4
    JE    is4
    CMP    EAX, 8
    JE    is8

; Must be 24bpp, so no palette:
    XOR    EAX, EAX
    JMP    SHORT @F

is4:    MOV    EAX, 16 * 4
    JMP    SHORT @F

is8:    MOV    EAX, 256 * 4
@@:    ADD    EAX, SIZEOF $BMPHEADER
    MOV    [EDI].$BMPHEADER.bfOffBits, EAX
    ADD    EAX, actualBMPsize
    MOV    [EDI].$BMPHEADER.bfFileSize, EAX

    INVOKE    CreateFile, OFFSET BMPfilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL
    MOV    bmpFileHandle, EAX
    INVOKE    WriteFile, bmpFileHandle, EDI, [EDI].$BMPHEADER.bfFileSize, ADDR dummy, NULL
    INVOKE    CloseHandle, bmpFileHandle

    INVOKE    ReleaseDC, TextRenderHandle, srcDC
    INVOKE    DeleteDC, bmpDC
    INVOKE    DeleteObject, bmpHandle

    POP    EDI

exit99:    RET

MakeTheBMP    ENDP





Title: Re: Creating a bitmap programmatically: how?
Post by: _japheth on January 19, 2024, 06:42:46 PM
Well, the first problem is: CreateCompatibleBitmap() must be used, not CreateBitmap().

Perhaps you should make yourself familar with the concept of Device Independent bitmaps?

hint: When calling CreateCompatibleBitmap(), you have to supply a "real" device context as parameter, not a memory context ( because the latter is rather "small" after creation, just 1 "pixel" ).
Title: Re: Creating a bitmap programmatically: how?
Post by: Vortex on January 20, 2024, 07:05:17 AM
Hi NoCforMe,

Your list of the steps to create a bitmap looks OK. Japheth is right, you should focus on device independent bitmaps :

QuoteAccording to Microsoft support:[5]

A device-independent bitmap (DIB) is a format used to define device-independent bitmaps in various color resolutions. The main purpose of DIBs is to allow bitmaps to be moved from one device to another (hence, the device-independent part of the name). A DIB is an external format, in contrast to a device-dependent bitmap, which appears in the system as a bitmap object (created by an application...). A DIB is normally transported in metafiles (usually using the StretchDIBits() function), BMP files, and the Clipboard (CF_DIB data format).

https://en.wikipedia.org/wiki/BMP_file_format (https://en.wikipedia.org/wiki/BMP_file_format)

QuoteThe device-independent bitmap (DIB) is by far the more widely used for in-memory operations. It has become the common denominator format because it is so simple: it describes just the image, without reference to characteristics of the device(s) upon which it may later be displayed.

https://help.accusoft.com/ImageGear/v18.2/Windows/ActiveX/IGAX-08-08.html (https://help.accusoft.com/ImageGear/v18.2/Windows/ActiveX/IGAX-08-08.html)

I modified my SaveDesktop example to meet your first requirement : Render text into a window.

The attached application displays a window and expects keyboard input to save the client area to a bitmap file.
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 20, 2024, 07:16:37 AM
OK, new day, some progress.

Warning: Like many of my posts, you may find this TL:DR. Fine, but I ask one thing of you: if you're going to comment on what I've written, then please read through the entire thing. I find it very annoying when folks make comments but they obviously haven't read what I've written!

So, I successfully wrote a BMP file. Here's what I've learned:

@japheth is absolutely correct: I needed to use CreateCompatibleBitmap() instead of CreateBitmap(), and I needed to use my original source DC (a "real" DC) as opposed to the memory DC I created.

In so doing, I had to change my logic. What I was trying to do, which failed miserably, was to force the bitmap to my preferred format (8 bits/pixel in this case). This didn't work, since the bitmap created had the same format as the source DC, which was 32 bits/pixel as would be expected of a display DC. So I used the actual format (returned by GetObject() on the bitmap in the source DC) for the subsequent operations, most importantly GetDIBits() which was failing and crashing the program.

So now I know how to do that, and I think I understand what's going on "behind the scenes" here, which as I've repeatedly stated is what I'm really after here, not just code that works.

But I'm not satisfied. I would still like to know if it's possible for me to create a bitmap here which is not in the same format as my source DC (32bpp). What if I want to create a 256-color bitmap? Is that possible? (With GDI, remember, not GDI+.) Or am I stuck with only being able to use the "native" format here?

I suppose it's not really a show-stopper, as it's super-easy to load the BMP into Paint Shop Pro and downsample it. But it'd be a nice feature.

Here's the code I ended up with that works:
; Render the text into the rendering window:
    INVOKE    InvalidateRect, TextRenderHandle, NULL, FALSE

    INVOKE    GetDC, TextRenderHandle
    MOV    srcDC, EAX
    INVOKE    CreateCompatibleDC, EAX
    MOV    bmpDC, EAX

; Measure the text we've drawn to get the size of the bitmap:
    INVOKE    SelectObject, srcDC, FontHandle
    MOV    EAX, OFFSET TextBuffer
    CALL    strlen
    MOV    EDX, EAX
    INVOKE    GetTextExtentPoint32, srcDC, OFFSET TextBuffer, EDX, ADDR sizer
    MOV    EAX, sizer.x

; Make sure text isn't wider than the rendering window:
    CMP    EAX, TextRenderWinWidth
    JBE    @F
    MOV    EAX, TextRenderWinWidth
@@:    MOV    BMPwidth, EAX
    MOV    EAX, sizer.y
    MOV    BMPheight, EAX

    INVOKE    CreateCompatibleBitmap, srcDC, BMPwidth, BMPheight
    MOV    bmpHandle, EAX

    invoke    GetObject, bmpHandle, SIZEOF BITMAP, addr bm
    movzx    eax, bm.bmBitsPixel
    mov    BMPbpp, eax

; Set up BITMAPINFO struct for GetDIBits():
    MOV    bmi.bmiHeader.biSize, SIZEOF BITMAPINFOHEADER
    MOV    EAX, BMPwidth
    MOV    bmi.bmiHeader.biWidth, EAX
    MOV    EAX, BMPheight
    MOV    bmi.bmiHeader.biHeight, EAX
    MOV    bmi.bmiHeader.biPlanes, 1
    MOV    EAX, BMPbpp
    MOV    bmi.bmiHeader.biBitCount, AX
    MOV    bmi.bmiHeader.biCompression, BI_RGB
    MOV    bmi.bmiHeader.biSizeImage, 0
    MOV    bmi.bmiHeader.biXPelsPerMeter, 0
    MOV    bmi.bmiHeader.biYPelsPerMeter, 0
    MOV    bmi.bmiHeader.biClrUsed, 0
    MOV    bmi.bmiHeader.biClrImportant, 0

    INVOKE    SelectObject, bmpDC, bmpHandle

    INVOKE    BitBlt, bmpDC, 0, 0, BMPwidth, BMPheight, srcDC, 0, 0, SRCCOPY

; Calculate actual bitmap size including any needed padding:
    MOV    EAX, BMPwidth
    MUL    BMPbpp
    ADD    EAX, 31
    AND    EAX, NOT 31
    SHR    EAX, 3
    MUL    BMPheight
    MOV    actualBMPsize, EAX

; Construct BMP file header:
    PUSH    EDI
    MOV    EDI, OutputBMPbuffer        ;Point to heap-allocated buffer.

    MOV    [EDI].$BMPHEADER.bfType, "MB"
    MOV    [EDI].$BMPHEADER.bfHdrSize, 40
    MOV    EAX, BMPwidth
    MOV    [EDI].$BMPHEADER.bfWidth, EAX
    MOV    EAX, BMPheight
    MOV    [EDI].$BMPHEADER.bfHeight, EAX
    MOV    [EDI].$BMPHEADER.bfPlanes, 1
    MOV    EAX, BMPbpp
    MOV    [EDI].$BMPHEADER.bfBitCount, AX
    MOV    [EDI].$BMPHEADER.bfCompression, BI_RGB
    MOV    EAX, actualBMPsize
;    MOV    [EDI].$BMPHEADER.bfSizeImage, EAX
    MOV    [EDI].$BMPHEADER.bfSizeImage, 0
    MOV    [EDI].$BMPHEADER.bfXPelsPerMeter, 0
    MOV    [EDI].$BMPHEADER.bfYPelsPerMeter, 0
    CMP    BMPbpp, 24
    JNE    @F
    XOR    EAX, EAX            ;24bpp = no palette.
    JMP    SHORT setCus

@@:    MOV    EAX, 1
    MOV    ECX, BMPbpp
    SHL    EAX, CL
setCus:    MOV    [EDI].$BMPHEADER.bfClrUsed, EAX
    MOV    [EDI].$BMPHEADER.bfClrImportant, 0

; Copy bitmap bits from DC to file buffer:
    MOV    EDX, EDI
    ADD    EDX, SIZEOF $BMPHEADER
    INVOKE    GetDIBits, bmpDC, bmpHandle, 0, BMPheight, EDX,
        ADDR bmi, DIB_RGB_COLORS

; Calculate total file size:
    MOV    EAX, BMPbpp
    CMP    EAX, 4
    JE    is4
    CMP    EAX, 8
    JE    is8

; Must be 24bpp or greater, so no palette:
    XOR    EAX, EAX
    JMP    SHORT @F

is4:    MOV    EAX, 16 * 4
    JMP    SHORT @F

is8:    MOV    EAX, 256 * 4
@@:    ADD    EAX, SIZEOF $BMPHEADER
    MOV    [EDI].$BMPHEADER.bfOffBits, EAX
    ADD    EAX, actualBMPsize
    MOV    [EDI].$BMPHEADER.bfFileSize, EAX

    INVOKE    CreateFile, OFFSET BMPfilename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, NULL
    MOV    bmpFileHandle, EAX
    INVOKE    WriteFile, bmpFileHandle, EDI, [EDI].$BMPHEADER.bfFileSize, ADDR dummy, NULL
    INVOKE    CloseHandle, bmpFileHandle

    INVOKE    ReleaseDC, TextRenderHandle, srcDC
    INVOKE    DeleteDC, bmpDC
    INVOKE    DeleteObject, bmpHandle

    POP    EDI
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 20, 2024, 08:55:11 AM
BTW, if you ever wondered what letters look like up close, here they are. (Bodoni Book BT, size 48**, ClearType quality.) Interesting to see how the characters are anti-aliased.
(https://i.postimg.cc/fJF6BzYX/ABCD-up-close.jpg) (https://postimg.cc/fJF6BzYX)

** "Logical" size, not point size. 36 points.
Title: Re: Creating a bitmap programmatically: how?
Post by: jj2007 on January 20, 2024, 10:04:49 AM
Quote from: NoCforMe on January 20, 2024, 07:16:37 AMI find it very annoying when folks make comments but they obviously haven't read what I've written!

Yeah, I know that feeling. There are people who don't even open attachments carefully prepared by members of this forum :cool:

QuoteWith GDI, remember, not GDI+.

You would see much better anti-aliasing if you could overcome this limitation.

Re converting bitmaps to 256 colours: Probably there are some API calls around, probably you'll find something on StackOverflow or CodeProject. The main stumbling block here is that 32-bit bitmaps don't need a palette, while 256 colour bitmaps do require one. And you need to transform a 32-bit pixels (unused alpha, R, G, B) into a one byte palette index. That means you have to squeeze 4 Gigabytes of colours into one 256 bit palette value. PaintShop, for example, offers several algorithms, see below. It's not trivial.
(https://i.postimg.cc/yDBTcV2g/Dec-Col-Depth16.png) (https://postimg.cc/yDBTcV2g)

P.S.: As you might have noticed, I am using words, not code, to communicate with you.
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 20, 2024, 12:42:03 PM
Quote from: jj2007 on January 20, 2024, 10:04:49 AM
Quote from: NoCforMe on January 20, 2024, 07:16:37 AMI find it very annoying when folks make comments but they obviously haven't read what I've written!

Yeah, I know that feeling. There are people who don't even open attachments carefully prepared by members of this forum :cool:
Yeah, except that the code was unasked for. I asked for apples and got oranges.

Quote
QuoteWith GDI, remember, not GDI+.
You would see much better anti-aliasing if you could overcome this limitation.
Well, that would be an interesting experiment. Maybe after I get this thing put together I'll try GDI+ and see what differences there are.

QuoteRe converting bitmaps to 256 colours: Probably there are some API calls around, probably you'll find something on StackOverflow or CodeProject. The main stumbling block here is that 32-bit bitmaps don't need a palette, while 256 colour bitmaps do require one. And you need to transform a 32-bit pixels (unused alpha, R, G, B) into a one byte palette index. That means you have to squeeze 4 Gigabytes of colours into one 256 bit palette value. PaintShop, for example, offers several algorithms, see below. It's not trivial.
(https://i.postimg.cc/yDBTcV2g/Dec-Col-Depth16.png) (https://postimg.cc/yDBTcV2g)

As I said, not being able to use an alternative format isn't a show-stopper, but it's disappointing. I might root around some after getting this working. Some of what I've read implies that Windows might be able to synthesize a palette in some cases. Though as you say it isn't trivial and I wouldn't be so sure about the quality of the down-conversion.

Pretty sure 32-bit images are 16+ million colors, no? Because as you say the alpha channel is unused, so isn't it 256 x 256 x 256?

Yep, I'm very familiar with that reduce-#-of-colors dialog. Have used it hundreds of times.

QuoteP.S.: As you might have noticed, I am using words, not code, to communicate with you.
That, my friend, is appreciated.
Title: Re: Creating a bitmap programmatically: how?
Post by: _japheth on January 20, 2024, 05:04:48 PM
Quote from: NoCforMe on January 20, 2024, 12:42:03 PMAs I said, not being able to use an alternative format isn't a show-stopper, but it's disappointing. I might root around some after getting this working.

There might exist multiple ways to achieve this. One way that I know of is using CreateDIBSection() - because I once created a monochrome bitmap of the screen with it. You have to carefully setup the BITMAPINFO structure that this function needs as input. For my monochrome picture I just supplied black and white colors, of course.

Title: Re: Creating a bitmap programmatically: how?
Post by: TimoVJL on January 20, 2024, 08:28:14 PM
QuoteThe best shoot is probably to convert to the old Netscape palette, which is a color palette with 216 colors, which has 6 levels (00H, 33H, 66H, 99H, CCH and FFH) for each color (R, G, B). There are many links like this one: http://www.webmaster.crevier.org/tags/palette.html , which describes this palette.
Fixed color palette (https://www.webmaster.crevier.org/tags/palette.html)
Title: Finished version (v1.0)
Post by: NoCforMe on January 21, 2024, 11:28:49 AM
OK, here it is, the finished product, let's call it v1.0. Creates a 32 BPP BMP. Please play around with it, let me know whatcha think, if anything. (Hopefully I didn't forget to include any files like I usually do!)

Try the font quality combobox: most dramatic difference is selecting "non-antialiased".
Title: Re: Creating a bitmap programmatically: how?
Post by: NoCforMe on January 21, 2024, 11:34:29 AM
Quote from: TimoVJL on January 20, 2024, 08:28:14 PM
QuoteThe best shoot is probably to convert to the old Netscape palette, which is a color palette with 216 colors, which has 6 levels (00H, 33H, 66H, 99H, CCH and FFH) for each color (R, G, B). There are many links like this one: http://www.webmaster.crevier.org/tags/palette.html , which describes this palette.
Fixed color palette (https://www.webmaster.crevier.org/tags/palette.html)

I like my "universal palette" better. Totally user-defined. A little bit of this, a little bit of that.
(https://i.postimg.cc/XpvNJ95b/Universal-Palette.jpg) (https://postimg.cc/XpvNJ95b)