News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Bare Minimum GDIplus code to save different Image Types.

Started by Siekmanski, April 21, 2020, 06:08:10 AM

Previous topic - Next topic

Siekmanski

I have tested the PngCursor with the 2019 Resource Compiler. -> will not compile it.
I have tested the PngCursor with Pelles Resource Compiler. -> it does compile it, but it does not show up as a cursor.

I think, unlike a PngIcon a PngCursor is not a valid Windows Format....
Somebody more knowledgeable can confirm this?

We still can use awesome compressed alpha blended PngIcons as a Cursor with the work around method.
Creative coders use backward thinking techniques as a strategy.

guga

Hi Guys

How can i save a bitmap loaded from memory (with virtual alloc) that does not contains BITMAPFILEHEADER to different image types using GDIPlus (with Marinus SaveNonIndexedImage and SaveIndexedImage functions) ?

I tried to create the image from memory but it is crashing all the time.

The image data was already retrieved directly from the resources section of a PE and stored on a virtual memory containing the BITMAPINFOHEADER and the image data that are displayed on screen.

What i need is convert these data to different image formats to be exported,  but i´m failing miserably.

I tried to use SHCreateMemStream but i´m not succeeding. What i did so far was:



Proc RsrcExportBitMap:
    Arguments @Adressee, @pRsrcList
    Local @ImgWidth, @ImgHeight, @pBits, @PixFmt
    Uses ecx, edx

     ; A simple function i made on a dll to show the open and save dialogs on a easier way
    call 'FastCRT.SaveAsFile' D@Adressee, OtherSaveFilter, {B$ "Choose a file to save", 0}, {B$ 'BitMap Files (*.bmp)', 0 '*.bmp', 0 0}
    On eax = 0, ExitP

    mov esi D@pRsrcList

    ; Not working at all !
;    call 'shlwapi.SHCreateMemStream' D$esi+RosAsm_Rsrc_Data.PtrDis, D$esi+RosAsm_Rsrc_Data.SizeDis
;    on eax = 0, ExitP
;    mov D@pBits eax
;    call 'gdiplus.GdipLoadImageFromStream' eax, MyFile

; GdipCreateBitmapFromStream https://stackoverflow.com/questions/39312201/how-to-use-gdi-library-to-decode-a-jpeg-in-memory

    mov eax D$esi+RosAsm_Rsrc_Data.PtrDis | mov D@pBits eax < --- Pointer to the the full bitmap image without BITMAPFILEHEADER
    mov ecx D$eax+BITMAPINFOHEADER.biWidthDis | mov D@ImgWidth ecx
    mov ecx D$eax+BITMAPINFOHEADER.biHeightDis | mov D@ImgHeight ecx

    call CreateImageFromMemory D@pBits, D@ImgWidth, D@ImgHeight, pImage ; < Biased on Marinus routine to convert the image data direct from memory
    If eax <> &S_OK
        xor eax eax ; exiting on error
        ExitP
    End_If

    lea eax D@PixFmt
    call 'gdiplus.GdipGetImagePixelFormat' D$pImage, eax ; Since i already defined the pixel format in CreateImageFromMemory with PIXELFORMAT_32BPPARGB, this line is redundant. I just kept here to see if gdiplus would actually read tyeh pixel format directly from a image in memory

    call SaveNonIndexedImage D$pImage, OtherSaveFilter, Image_BMP, D@PixFmt ; Marinus Routine

EndP




Proc CreateImageFromMemory:
    Arguments @ImgBits, @ImageWidth, @ImageHeight, @pImage
    Local @Stride
    Uses ecx, edx

    mov eax D@ImageWidth | shl eax 2
    call 'gdiplus.GdipCreateBitmapFromScan0' D@ImageWidth, D@ImageHeight, eax, PIXELFORMAT_32BPPARGB, D@ImgBits, D@pImage

EndP




    ; save Non Indexed 16,24,32 bit Images

Proc SaveNonIndexedImage:
    Arguments @pGdiPlusImage, @pFilename, @Image_Type, @Pixel_Format
    Uses ecx, edx

    ; make a copy of the image to work with.
    call 'gdiplus.GdipCloneImage' D@pGdiPlusImage, pImageTemp ; <---- When go here, the file returns fine. It returns S_OK
    .If eax = &S_OK
        call 'gdiplus.GdipBitmapConvertFormat' D$pImageTemp, D@Pixel_Format, DITHER_TYPE_NONE, PALLETE_TYPE_CUSTOM, &NULL, 0 ; <----- But here it crashes badly.
        If eax = &S_OK
            call 'kernel32.MultiByteToWideChar' &CP_ACP, 0, D@pFilename, 0-1, FilenameW, (&MAX_PATH-1)
            mov ecx D@Image_Type | mov eax CLSID_ImageType | mov B$eax  cl
            call 'gdiplus.GdipSaveImageToFile' D$pImageTemp, FilenameW, eax, &NULL
        End_If
        call 'gdiplus.GdipDisposeImage' D$pImageTemp
    .End_If

EndP

Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com

Siekmanski

Use "GdipCreateBitmapFromScan0" with the pointer [D@ImgBits] to the raw 32 bit image data without the image headers.

Take care of the stride (pitch) = horizontal length in bytes.

    invoke  GdipCreateBitmapFromScan0,I_Width,I_Height,Pitch,PixelFormat32bppRGB,pImgBits,addr pImage

[D@pImage] can be used to save the image in the format you like.
Creative coders use backward thinking techniques as a strategy.

guga

Hi Marinus.

I tried but it is crashing at "gdiplus.GdipBitmapConvertFormat"

I tried like this:

    mov eax D$esi+RosAsm_Rsrc_Data.PtrDis | mov ecx eax | add ecx Size_Of_BITMAPINFOHEADER | mov D@pBits ecx; <--- Go to the pixel data bypass bitmap headers achieved from resources section and saved to virtuualmemory
    mov ecx D$eax+BITMAPINFOHEADER.biWidthDis | mov D@ImgWidth ecx ; get width
    mov ecx D$eax+BITMAPINFOHEADER.biHeightDis | mov D@ImgHeight ecx ; get height


    call CreateImageFromMemory D@pBits, D@ImgWidth, D@ImgHeight, pImage ; <---  so far it was ok. The image  seems to be created from memory
    If eax <> &S_OK
        xor eax eax
        ExitP
    End_If


    lea eax D@PixFmt <---- Dummy
    call 'gdiplus.GdipGetImagePixelFormat' D$pImage, eax <---- Dummy No longer needed.

    call SaveNonIndexedImage D$pImage, OtherSaveFilter, Image_BMP, D@PixFmt <--- crashed here. It tried to clone the image but when pasing through GdipBitmapConvertFormat it then crashed
Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com

Siekmanski

You don't need "GdipCloneImage", it was only used to reuse the same image over and over again.
If you have a valid pImage member it should work.
Creative coders use backward thinking techniques as a strategy.

guga

Still not working.

I don´t know what i´m doing wrong. I added a error handling to see what error msg is related to this and this is the result:






Btw..what is being saved/exported is not the object from the dc (in WM_PAINT). It is deleted with call 'GDI32.DeleteObject' D@hBitmap on this message.

What i´m trying to export are the pixel data related to a bitmap from the rsrc section of a PE. (The data pointed by IMAGE_RESOURCE_DATA_ENTRY)
Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com

Siekmanski

Probably the pImage pointer to the data is not valid?
Creative coders use backward thinking techniques as a strategy.

guga

maybe, but how to validated it ?

It do export a file with some bitmap header on it, but the data is corrupted
Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com

Siekmanski

Skip the header info, you only need the 32 bit ARGB raw image data in memory.
"GdipCreateBitmapFromScan0" creates a pImage for you from that data.
Creative coders use backward thinking techniques as a strategy.

Siekmanski

Creative coders use backward thinking techniques as a strategy.

guga

HI Marinus

Tks, but, still not working :dazzled: :dazzled: Im clueless on what could be the error.

The inputted data from the rsrc section (with the BitmapInfoheader) is like this:

; This data is extracted directly from the resources section without any loadresources api envolved, neither lock etc. I simply get the PE address, point to it and copy on a buffer with VirtualAlloc api.
; BITMAPINFOHEADER structure
[OutBmpHdr:
OutBmpHdr.biSize: D$ 40
OutBmpHdr.biWidth: D$ 75
OutBmpHdr.biHeight: D$ 46
OutBmpHdr.biPlanes: W$ 1
OutBmpHdr.biBitCount: W$ 8
OutBmpHdr.biCompression: D$ &BI_RGB
OutBmpHdr.biSizeImage: D$ 3496
OutBmpHdr.biXPelsPerMeter: D$ 3790
OutBmpHdr.biYPelsPerMeter: D$ 3780
OutBmpHdr.biClrUsed: D$ 0
OutBmpHdr.biClrImportant: D$ 0

Followed by the Pixel data of the image.
PixData: B$ 0,  0,  0, 0, 080, 080...........]

I read the example and tried this:

[pImage: D$ 0]
......

mov eax D$esi+RosAsm_Rsrc_Data.PtrDis ; Initial address of the image data in OutBmpHdr (Stored on a Buffer with VirtAlloc)
mov ecx eax
add ecx Size_Of_BITMAPINFOHEADER
mov D@pBits ecx ; <---- True BitMap Pixel data at "PixData"

; get the width and height

    mov eax D$esi+RosAsm_Rsrc_Data.PtrDis
    mov ecx D$eax+BITMAPINFOHEADER.biWidthDis | mov D@ImgWidth ecx
    mov ecx D$eax+BITMAPINFOHEADER.biHeightDis | mov D@ImgHeight ecx

; Tried to create a new image with PIXELFORMAT_32BPPRGB = 022009h
; Where pImage is the variuable to store the pixel data after created from memory to gdiplus
; The below function returns S_ok.
call CreateImageFromMemory D@pBits, D@ImgWidth, D@ImgHeight, pImage, PIXELFORMAT_32BPPRGB

And then tried to convert with

            call 'kernel32.MultiByteToWideChar' &CP_ACP, 0, OtherSaveFilter, 0-1, FilenameW, (&MAX_PATH-1)
            mov ecx Image_BMP | mov eax CLSID_ImageType | mov B$eax  cl
            call 'gdiplus.GdipSaveImageToFile' D$pImage, FilenameW, eax, &NULL ; This is the error. Returned a value of 7 (Same buffer problem)

maybe the error is inside CreateImageFromMemory  ? Because the image is 8 bpp and not 32bpp ? If is that so, how to properly identify or convert a image without using hdc etc, just the plain and simple gdiplus fucntions ?


Proc CreateImageFromMemory:
    Arguments @ImgBits, @ImageWidth, @ImageHeight, @pImage, @PixFormat
    Local @Stride
    Uses ecx, edx

    mov eax D@ImageWidth | shl eax 2
    call 'gdiplus.GdipCreateBitmapFromScan0' D@ImageWidth, D@ImageHeight, eax, D@PixFormat, D@ImgBits, D@pImage

EndP




I read at https://stackoverflow.com/questions/39312201/how-to-use-gdi-library-to-decode-a-jpeg-in-memory that we can use SHCreateMemStream directly to try to convert, but how to do it ??? I tried this too, but, nothing :(

IStream* stream = SHCreateMemStream(buf, bufsize);
Gdiplus::Image *image = Gdiplus::Image::FromStream(stream);  ; <--- I believe it is actually pointing to GdipCreateBitmapFromStream, right ?
...
stream->Release();
Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com

Siekmanski

The stride (sometimes called pitch) must be a multiple of four.
The number of bytes in the pixel format multiplied by the width of the bitmap.
If it's not a multiple of four, pad it with zeros.

e.g. Image width is 318 and 8 bpp = 318 bytes per horizontal line.

318 / 4 = 79.5 is not a multiple of four, pad it with 2 bytes ( zeros ) = 320
So the stride must be 320 for an 8 bpp image with a 318 pixel width.

You only need to create a "Stream" if you want to save or load the image in or from memory.
"GdipCreateBitmapFromScan0", doesn't need a Stream, just a pointer to the raw image data.
Creative coders use backward thinking techniques as a strategy.

guga

I`m lost :biggrin: :biggrin: :biggrin:

So, what and how to do it ?

1 -  on GdipCreateBitmapFromScan0
Do i need to align the width by 4 and multiply again by 4 ?, like this  on CreateImageFromMemory

    mov eax D@ImageWidth | add eax 4-1 | and eax 0-4 | shl eax 2
    call 'gdiplus.GdipCreateBitmapFromScan0' D@ImageWidth, D@ImageHeight, eax, D@PixFormat, D@ImgBits, D@pImage

or simply align without multiplying again by 4 ? Like below.

    mov eax D@ImageWidth | add eax 4-1 | and eax 0-4
    call 'gdiplus.GdipCreateBitmapFromScan0' D@ImageWidth, D@ImageHeight, eax, D@PixFormat, D@ImgBits, D@pImage


2 - You only need to create a "Stream" if you want to save or load the image in or from memory.
Not sure i understood. I want to save the image to whatever format whose data are in memory allocated with virtual alloc. So, i need a stream, right ? Then...how to create this stream and put the data there to be saved ? Can we use SHCreateMemStream as the link i mentioned earlier (and how) ?


3 - If GdipCreateBitmapFromScan0 does not needs a stream, then why do we need it if we already have the image data previously allocated ?

Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com

Siekmanski

1 - Align every horizontal pixel line in the raw pixel data at 4 bytes.
2 - If your raw pixel data is situated in memory, you don't need a stream.
3 - "GdipCreateBitmapFromScan0" creates a GdiPlus bitmap of the raw pixel data from memory.
Creative coders use backward thinking techniques as a strategy.

guga

Still not working. I have no clue what i´m doing wrong.

This time i did this as you suggested:


    (...)
    mov eax D@pRsrcList
    mov esi D$eax+RosAsm_Rsrc_Data.PtrDis ; Pointer to the start of the BITMAPINFOHEADER in memory

    mov ecx D$esi+BITMAPINFOHEADER.biWidthDis | mov D@ImgWidth ecx
    mov ecx D$esi+BITMAPINFOHEADER.biHeightDis | mov D@ImgHeight ecx
    movzx eax W$esi+BITMAPINFOHEADER.biBitCountDis | shr eax 3 | mov D@BitCount eax; divide by 8 to we calculate the stride
    mov eax esi | add eax Size_Of_BITMAPINFOHEADER | mov D@StartPixImg eax ; Points to the true pixel data

    lea eax D@pBits | mov D$eax 0
    call RsrcFixImagePixels D@StartPixImg, eax, D@ImgWidth, D@ImgHeight, D@BitCount

    call CreateImageFromMemory D@pBits, D@ImgWidth, D@ImgHeight, pImage, PIXELFORMAT_32BPPRGB;5PIXELFORMAT_32BPPRGB
    If eax <> &S_OK
        xor eax eax
        ExitP
    End_If

            call 'kernel32.MultiByteToWideChar' &CP_ACP, 0, OtherSaveFilter, 0-1, FilenameW, (&MAX_PATH-1)
            mov ecx Image_BMP | mov eax CLSID_ImageType | mov B$eax  cl
            call 'gdiplus.GdipSaveImageToFile' D$pImage, FilenameW, eax, &NULL

        If eax <> 0
            call 'FastCRT.ReportWinError' {B$ "Error", 0}
        End_If
        call 'gdiplus.GdipDisposeImage' D$pImage
        ; deallocate the newly created memory
        call 'RosMem.VMemFree' D@pBits


; RsrcFixImagePixels


Proc RsrcFixImagePixels:
    Arguments @InputPixData, @pOutput, @ImgWidth, @ImgHeight, @BitCount
    Local @RawImgDataSize, @TmpMem, @NextLine, @X, @Y
    Uses esi, edi

    mov esi D@InputPixData
    mov edi D@pOutput

    ; get the total number of pixels on the image. dwPixels = bmih.biWidth * bmih.biHeight * (bmih.biBitCount / 8)
    ; BitCount =  biBitCount/8
    ; and allocate enough memory to copy the pix data to the new buffer. We must align width by 4 and mul by BitCount/8 to we get the proper stride
    mov eax D@ImgWidth | Align_On 4 eax | imul eax D@BitCount | mov D@NextLine eax | imul eax D@ImgHeight | mov D@RawImgDataSize eax

    mov D@TmpMem 0 | lea eax D@TmpMem
    call 'RosMem.VMemAlloc' eax, D@RawImgDataSize
    mov esi D@pOutput
    mov D$esi eax
    mov edi eax

    mov D@Y 0
    mov D@X 0
    mov esi D@InputPixData
    mov eax D@Y
    .While eax < D@ImgHeight
        call 'RosMem.FastMemcpy' edi, esi, D@Imgwidth ; Simply copy the whole width

        inc D@Y
        mov eax D@Y
        add esi D@NextLine ;  next pos in input (Stride included)
        add edi D@NextLine ;  next pos in input
    .End_While
EndP


CreateImageFromMemory

Proc CreateImageFromMemory:
    Arguments @ImgBits, @ImageWidth, @ImageHeight, @pImage, @PixFormat
    Local @Stride
    Uses ecx, edx

    mov eax D@ImageWidth | Align_on 4 eax | shl eax 2 ; Either i mul by 4 or not it still gets error when trying to save the new image !!!
    call 'gdiplus.GdipCreateBitmapFromScan0' D@ImageWidth, D@ImageHeight, eax, D@PixFormat, D@ImgBits, D@pImage

EndP



The same error happens on export at GdipSaveImageToFile. It keeps returning 7 in eax, instead of S_OK

It´s not converting properly the data to export the file from memory

Attached is the generated file. Open it with an hgex editor and you will see that the BitMap header is there, but the pixels data is not
Coding in Assembly requires a mix of:
80% of brain, passion, intuition, creativity
10% of programming skills
10% of alcoholic levels in your blood.

My Code Sites:
http://rosasm.freeforums.org
http://winasm.tripod.com