The MASM Forum

General => The Laboratory => Topic started by: Siekmanski on April 21, 2020, 06:08:10 AM

Title: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 21, 2020, 06:08:10 AM
Bare Minimum GDIplus code to save different Image Types.
Without the GDIplus helper functions to make the code smaller and easier to read.
Only 1 CLSID guid is used for all the Image Types.

Includes the JPEG Quality Encoder, so creating very small .jpg files is easy.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Vortex on April 21, 2020, 06:17:52 AM
Hi Siekmanski,

Your example works fine on Windows XP :thumbsup:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 21, 2020, 06:31:03 AM
Cool, thanks.  :thumbsup:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 21, 2020, 09:34:10 AM
Gratsie, works fine on my Win10 64. This will be very useful.  :thumbsup:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 21, 2020, 07:07:00 PM
Marinus,

I have a question for you, using your earlier work I got a number of file formats working but had an unusual result saving the image as a bitmap. The source bitmap was a bit over 500k, when saved it was over 800k, the only compression method I have every understood with bitmaps was RLE encodings, do you have any grasp of why the result is so much bigger ?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 21, 2020, 09:45:23 PM
These 6 types of ".BMP" Bitmaps are the most commonly used. ( there are more exotic types... )

ARGB  = 32 bits per pixel ( includes the alpha channel )
RGB   = 24 bits per pixel
RGB16 = 16 bits per pixel
RGB8  =  8 bits per pixel ( pixels are indexed using a 256 color map )
RGB4  =  4 bits per pixel ( pixels are indexed using a 16 color map )
RGB1  =  1 bits per pixel ( pixels are indexed using a 2 color map )

There are 2 types of storing, Raw uncompressed and RLE encoded.
RLE encoded, depends on the repeating patterns of equal valued pixels, therefore will vary in size.

So, I think the 800K is raw ARGB and the 500K RLE encoded.

It's possible to create those different types of BMP bitmaps with GDIplus.
Look for:

    invoke  GdipBitmapLockBits,pImage,NULL,ImageLockModeRead,PixelFormat32bppARGB,offset GDIplusBitmapData

    invoke  GdipCreateBitmapFromScan0,I_Width,I_Height,pLockedRect.Pitch,PixelFormat32bppRGB,pLockedRect.pBits,addr pImage


ImageLockModeRead               equ 1
ImageLockModeWrite              equ 2
ImageLockModeReadWrite          equ 3
ImageLockModeUserInputBuf       equ 4

PixelFormat1bppIndexed          equ 30101h
PixelFormat4bppIndexed          equ 30402h
PixelFormat8bppIndexed          equ 30803h
PixelFormat16bppGreyScale       equ 101004h
PixelFormat16bppRGB555          equ 21005h
PixelFormat16bppRGB565          equ 21006h
PixelFormat16bppARGB1555        equ 61007h
PixelFormat24bppRGB             equ 21808h
PixelFormat32bppRGB             equ 22009h
PixelFormat32bppARGB            equ 26200Ah
PixelFormat32bppPARGB           equ 0E200Bh

EDIT:
Useful functions for indexed color types (256 or less colors)

GdipInitializePalette
GdipBitmapConvertFormat
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: wind on April 24, 2020, 05:26:48 PM
great, works on my win10 64 too.  :thumbsup:

only 1 small issues, have to comment out line 19-24, or else get an assembling error:

Code: [Select]
C:\Temp\GDIplusSmall\GDIplusSmall.asm(20) : error A2163: non-benign structure redefinition: incorrect initializers : GdiplusStartupInput
C:\Temp\GDIplusSmall\GDIplusSmall.asm(21) : error A2163: non-benign structure redefinition: incorrect initializers : GdiplusStartupInput
C:\Temp\GDIplusSmall\GDIplusSmall.asm(22) : error A2163: non-benign structure redefinition: incorrect initializers : GdiplusStartupInput
C:\Temp\GDIplusSmall\GDIplusSmall.asm(23) : error A2163: non-benign structure redefinition: incorrect initializers : GdiplusStartupInput

windows.inc already has the structure defined.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 24, 2020, 06:37:18 PM
Yes, you are right.  :thumbsup:
Didn't know some GdiPlus includes were added in the latest 2012 windows.inc ( Shouldn't they belong in gdiplus.inc ?)
My windows.inc is an older version, time to update I guess. :tongue:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 24, 2020, 07:00:51 PM
I can get JPG code to work but I have not discovered how to set the last argument for "GdipSaveImageToFile". The range of reference material I can access has conflicting definitions of what is a structure. It currently converts a 6 meg bitmap to a JPG 137194 bytes and nothing I have tried changes the compression level. Using a image app that I have had for years, if I set the compression rate to about 75% I get a JPG of near identical size ad the GDIP conversion looks fine with no visible deterioration.


; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    encoder STRUCT QWORD
      count dq ?
      pguid dq ?
      dwtyp dq ?
      nmval dq ?
    encoder ENDS

 ;     typedef struct EncoderParameters {
 ;       GUID Guid:
 ;       ULONG NumberOfValues;
 ;       ULONG Type;
 ;       void * Value;
 ;     };

 ;     typedef struct EncoderParameters {
 ;        UINT Count;
 ;        EncoderParameter Parameter|i|;
 ;     };




 ;    Jose Roca
 ;    eps.count = 1
 ;    eps.Parameter(0).pGuid = $EncoderQuality
 ;    eps.Parameter(0).dwType = %EncoderParameterValueTypeLong
 ;    eps.Parameter(0).NumberOfValues = 1

  .data
    CLSID_ImageType GUID <0557CF401h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>>
    CLSID_EncoderQuality GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh,05dh,0b3h,051h,005h,0e7h,0ebh>>

   .code

; -----------------------------------------------------------

SaveJpgQualityImage proc hBMP:QWORD,pFilename:QWORD,Image_Quality:QWORD

    LOCAL hGdip :QWORD
    LOCAL pTemp :QWORD
    LOCAL pqual :QWORD
    LOCAL ptype :QWORD
    LOCAL penc  :QWORD
    LOCAL encd  :encoder

    mov ptype, ptr$(CLSID_ImageType)
    mov pqual, ptr$(CLSID_EncoderQuality)

    mov encd.count, 1                   ; count
    mrm encd.pguid, pqual               ; pointer to CLSID_EncoderQuality
    mrm encd.dwtyp, 75                   ; EncoderParameterValueTypeLong
    mov encd.nmval, 1                   ; NumberOfValues

    mov penc, ptr$(encd.count)

    invoke GdipCreateBitmapFromHBITMAP,hBMP,0,ptr$(hGdip)       ; OK
    invoke GdipCloneImage,hGdip,ptr$(pTemp)                     ; OK
    test rax, rax
    jnz  Done

    invoke GdipSaveImageToFile,pTemp,L(pFilename),ptype,penc    ; last arg does not work

    invoke GdipDisposeImage,pTemp                               ; release image copy
    invoke GdipDisposeImage,hGdip

  Done:
    ret

SaveJpgQualityImage endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤


Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 24, 2020, 08:00:53 PM
The order of the struct is this,

    LOCAL iqual :QWORD


    encoder STRUCT QWORD
      count dq ? ; = 1
      pguid dq ? ; = pointer to CLSID_EncoderQuality
      nmval dq ? ; = 1  NumberOfValues
      dwtyp dq ? ; = 4  (EncoderParameterValueTypeLong)
      QualityLevel dq ? ; pointer to Image_Quality
    encoder ENDS

    mov iqual, ptr$(Image_Quality) ; 75 in your example

    mov encd.count, 1                   ; count
    mrm encd.pguid, pqual               ; pointer to CLSID_EncoderQuality
    mov encd.nmval, 1                   ; NumberOfValues
    mov encd.dwtyp, 4                   ; EncoderParameterValueTypeLong
    mrm encd.QualityLevel, iqual        ; JPG QualityLevel

In your example you don't need to clone the image -> GdipCloneImage


Did not test it, hope it works.  :biggrin:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 24, 2020, 08:26:36 PM
Bare Minimum GDIplus code to save different Image Types and PixelFormat conversions.

Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 24, 2020, 09:13:01 PM
I modified the algo to this form but it still won't change the compression ratio. I wondered if I am using the correct GUID

 CLSID_EncoderQuality GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh,05dh,0b3h,051h,005h,0e7h,0ebh>>



; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    encoder STRUCT QWORD
      count dq ?            ; = 1
      pguid dq ?            ; = pointer to CLSID_EncoderQuality
      nmval dq ?            ; = 1  NumberOfValues
      dwtyp dq ?            ; = 4  (EncoderParameterValueTypeLong)
      qlevl dq ?            ; = pointer to Image_Quality
    encoder ENDS

  .data
    CLSID_ImageType GUID <0557CF401h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>>
    CLSID_EncoderQuality GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh,05dh,0b3h,051h,005h,0e7h,0ebh>>

   .code

; -----------------------------------------------------------

SaveJpgQualityImage proc hBMP:QWORD,pFilename:QWORD,Image_Quality:QWORD

    LOCAL hGdip :QWORD
    LOCAL pTemp :QWORD
    LOCAL pqual :QWORD
    LOCAL ptype :QWORD
    LOCAL penc  :QWORD
    LOCAL iqual :QWORD
    LOCAL encd  :encoder

    mov ptype, ptr$(CLSID_ImageType)
    mov pqual, ptr$(CLSID_EncoderQuality)

    mov iqual, ptr$(Image_Quality)

    mov encd.count, 1                   ; count
    mrm encd.pguid, pqual               ; pointer to CLSID_EncoderQuality
    mov encd.nmval, 1                   ; NumberOfValues
    mov encd.dwtyp, 4                   ; EncoderParameterValueTypeLong
    mrm encd.qlevl, iqual               ; JPG qlevl

    mov penc, ptr$(encd.count)

    invoke GdipCreateBitmapFromHBITMAP,hBMP,0,ptr$(hGdip)       ; OK
    invoke GdipSaveImageToFile,hGdip,L(pFilename),ptype,penc    ; last arg does not work
    invoke GdipDisposeImage,hGdip

    ret

SaveJpgQualityImage endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 24, 2020, 09:32:27 PM
Maybe this will work ( I'm not experienced in 64bit code )

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

    encoder STRUCT QWORD
      count dq ?            ; = 1
      pguid dq ?            ; = pointer to CLSID_EncoderQuality
      nmval dq ?            ; = 1  NumberOfValues
      dwtyp dq ?            ; = 4  (EncoderParameterValueTypeLong)
      qlevl dq ?            ; = pointer to Image_Quality
    encoder ENDS

  .data
    CLSID_ImageType GUID <0557CF401h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>>
    CLSID_EncoderQuality GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh,05dh,0b3h,051h,005h,0e7h,0ebh>>

   .code

; -----------------------------------------------------------

SaveJpgQualityImage proc hBMP:QWORD,pFilename:QWORD,Image_Quality:QWORD

    LOCAL hGdip :QWORD
    LOCAL pTemp :QWORD
    LOCAL pqual :QWORD
    LOCAL ptype :QWORD
    LOCAL penc  :QWORD
    LOCAL iqual :QWORD
    LOCAL iJPGQ :QWORD
    LOCAL encd  :encoder

    mov ptype, ptr$(CLSID_ImageType)
    mov pqual, ptr$(CLSID_EncoderQuality)

    mov rax,Image_Quality
    mov iJPGQ,rax
    mov iqual, ptr$(iJPGQ)

    mov encd.count, 1                   ; count
    mrm encd.pguid, pqual               ; pointer to CLSID_EncoderQuality
    mov encd.nmval, 1                   ; NumberOfValues
    mov encd.dwtyp, 4                   ; EncoderParameterValueTypeLong
    mrm encd.qlevl, iqual               ; JPG qlevl

    mov penc, ptr$(encd.count)

    invoke GdipCreateBitmapFromHBITMAP,hBMP,0,ptr$(hGdip)       ; OK
    invoke GdipSaveImageToFile,hGdip,L(pFilename),ptype,penc    ; last arg does not work
    invoke GdipDisposeImage,hGdip

    ret

SaveJpgQualityImage endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 24, 2020, 10:10:16 PM
I gave it a blast but no change, maybe the 64 bit version of GDIP is broken somewhere.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: nidud on April 25, 2020, 01:28:23 AM
I gave it a blast but no change, maybe the 64 bit version of GDIP is broken somewhere.

Tested the version Vortex made in 64-bit and that seems to work. It did however fail in 32-bit for some reason.

Code: [Select]

; build: asmc64 -ws -pe -Zp8 -gui test.asm

include windows.inc
include gdiplus.inc
include tchar.inc

    .data
    hBitmap HANDLE 0

    .code

WndProc proc hWnd:HWND, message:UINT, wParam:WPARAM, lParam:LPARAM

    .switch message

    .case WM_CREATE

        .new token:ULONG_PTR
        .new BmpImage:ptr GpBitmap
        .new StartupInfo:GdiplusStartupInput

        mov StartupInfo.GdiplusVersion,1

        GdiplusStartup(&token, &StartupInfo, 0)
        GdipCreateBitmapFromFile("logo.png", &BmpImage)
        GdipCreateHBITMAPFromBitmap(BmpImage, &hBitmap, 0)
        GdipDisposeImage(BmpImage)
        .endc

    .case WM_PAINT

        .new ps:PAINTSTRUCT
        .new hdc:HDC
        .new hMemDC:HANDLE
        .new bm:BITMAP

        mov hdc,BeginPaint(hWnd, &ps)
        mov hMemDC,CreateCompatibleDC(rax)

        SelectObject(rax, hBitmap)
        GetObject(hBitmap, BITMAP, &bm)
        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDC, 0, 0, SRCCOPY)
        DeleteDC(hMemDC)
        EndPaint(hWnd, &ps)
        .endc

    .case WM_DESTROY
        DeleteObject(hBitmap)
        PostQuitMessage(0)
        .endc

    .default
        .return DefWindowProc(hWnd, message, wParam, lParam)
    .endsw
    xor eax,eax
    ret

WndProc endp

_tWinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE, lpCmdLine:LPTSTR, nShowCmd:SINT

  local wc:WNDCLASSEX, msg:MSG, hwnd:HANDLE

    xor eax,eax
    mov wc.cbSize,          WNDCLASSEX
    mov wc.style,           CS_HREDRAW or CS_VREDRAW
    mov wc.cbClsExtra,      eax
    mov wc.cbWndExtra,      eax
    mov wc.hInstance,       hInstance
    mov wc.hbrBackground,   COLOR_WINDOW+1
    mov wc.lpszMenuName,    rax
    mov wc.lpfnWndProc,     &WndProc
    mov wc.lpszClassName,   &@CStr("BitmapClass")
    mov wc.hIcon,           LoadIcon(0, IDI_APPLICATION)
    mov wc.hIconSm,         rax
    mov wc.hCursor,         LoadCursor(0, IDC_ARROW)

    .ifd RegisterClassEx(&wc)

        .if CreateWindowEx(0, "BitmapClass", "Bitmap from file", WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT, 290, 90, NULL, NULL, hInstance, 0)

            mov hwnd,rax
            ShowWindow(rax, SW_SHOWNORMAL)
            UpdateWindow(hwnd)

            .while GetMessage(&msg,0,0,0)
                TranslateMessage(&msg)
                DispatchMessage(&msg)
            .endw
            mov rax,msg.wParam
        .endif
    .endif
    ret

_tWinMain endp

    end _tstart
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: nidud on April 25, 2020, 02:41:19 AM
This also works in 64-bit.

; build: asmc64 -ws -pe -Zp8 test.asm
include windows.inc
include gdiplus.inc
include tchar.inc

    .data
    StartupInfo GdiplusStartupInput <1, 0, 0, 0>
    ImageFormatBMP GUID <0557CF400h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>>

    .code

wmain proc argc:int_t, argv:wstring_t

    .if ( argc == 3 )

        .new token:ULONG_PTR       

        .ifd !GdiplusStartup(&token, &StartupInfo, 0)

            .new BmpImage:ptr GpBitmap

            mov rcx,argv
            .ifd !GdipCreateBitmapFromFile([rcx+size_t], &BmpImage)

                mov rcx,argv
                GdipSaveImageToFile(BmpImage, [rcx+size_t*2], &ImageFormatBMP, NULL)
                GdipDisposeImage(BmpImage)
            .endif
            GdiplusShutdown(token)
        .endif
    .endif
    xor eax,eax
    ret

wmain endp

    end _tstart
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 25, 2020, 03:51:57 AM
I think the JPG quality encoder parameters for 64 bit are organized in an other way as in 32 bit code?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: nidud on April 25, 2020, 06:23:08 AM
There's a sample here:
https://docs.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-setting-jpeg-compression-level-use

The ImageCodecInfo struct is off by 8 bytes, not sure why (alignment perhaps).
Code: [Select]
include windows.inc
include gdiplus.inc
include stdio.inc
include tchar.inc

    .data
    EncoderQuality IID { 0x1d5be4b5, 0xfa4a, 0x452d, {0x9c, 0xdd, 0x5d, 0xb3, 0x51, 0x05, 0xe7, 0xeb } }
    gdiplusStartupInput GdiplusStartupInput { 1, 0, 0, 0 }

    .code

GetEncoderClsid proc uses rsi rdi rbx format:wstring_t, pClsid:ptr CLSID

  local num:UINT  ; number of image encoders
  local size:UINT ; size of the image encoder array in bytes

    xor edi,edi
    mov num,edi
    mov size,edi

    GdipGetImageEncodersSize(&num, &size)
    .return -1 .if ( size == 0 )
   
    mov rdi,malloc(size)
    .return -1 .if ( rax == NULL ) ;; Failure

    GdipGetImageEncoders(num, size, rdi)

    .for ( esi = 0: esi < num: ++esi )
   
        imul ebx,esi,ImageCodecInfo+8
       
        .if ( wcscmp( [rdi+rbx].ImageCodecInfo.MimeType, format ) == 0 )
     
            mov rdx,pClsid
            mov oword ptr [rdx],[rdi+rbx].ImageCodecInfo.Clsid

            free(rdi)
            .return esi ;; Success
        .endif
    .endf

   free(rdi)
   .return -1 ;; Failure

GetEncoderClsid endp

wmain proc argc:int_t, argv:wstring_t

 ;; Initialize GDI+.
 local gdiplusToken:ULONG_PTR
 local encoderClsid:CLSID
 local encoderParameters:EncoderParameters
 local quality:ULONG
 local Image:ptr GpBitmap
   
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL)

    ;; Get an image from the disk.
    GdipCreateBitmapFromFile(L"Shapes.bmp", &Image)

    ;; Get the CLSID of the JPEG encoder.
    GetEncoderClsid(L"image/jpeg", &encoderClsid)

    ;; Before we call Image::Save, we must initialize an
    ;; EncoderParameters object. The EncoderParameters object
    ;; has an array of EncoderParameter objects. In this
    ;; case, there is only one EncoderParameter object in the array.
    ;; The one EncoderParameter object has an array of values.
    ;; In this case, there is only one value (of type ULONG)
    ;; in the array. We will let this value vary from 0 to 100.

    mov encoderParameters.Count,1
    mov encoderParameters.Parameter.Guid,EncoderQuality
    mov encoderParameters.Parameter.Type,EncoderParameterValueTypeLong
    mov encoderParameters.Parameter.NumberOfValues,1

    ;; Save the image as a JPEG with quality level 0.
    mov quality,0
    mov encoderParameters.Parameter.Value,&quality
    GdipSaveImageToFile(Image, L"Shapes001.jpg", &encoderClsid, &encoderParameters)
    .if (eax == 0)
        wprintf(L"%s saved successfully.\n", L"Shapes001.jpg")
    .else
        wprintf(L"%d  Attempt to save %s failed.\n", eax, L"Shapes001.jpg")
    .endif

    ;; Save the image as a JPEG with quality level 50.
    mov quality,50
    mov encoderParameters.Parameter.Value,&quality   
    GdipSaveImageToFile(Image, L"Shapes050.jpg", &encoderClsid, &encoderParameters)
    .if (eax == 0)
        wprintf(L"%s saved successfully.\n", L"Shapes050.jpg");
    .else
        wprintf(L"%d  Attempt to save %s failed.\n", eax, L"Shapes050.jpg");
    .endif

    ;; Save the image as a JPEG with quality level 100.
    mov quality,100
    mov encoderParameters.Parameter.Value,&quality   
    GdipSaveImageToFile(Image, L"Shapes100.jpg", &encoderClsid, &encoderParameters)
    .if (eax == 0)
        wprintf(L"%s saved successfully.\n", L"Shapes100.jpg");
    .else
        wprintf(L"%d  Attempt to save %s failed.\n", eax, L"Shapes100.jpg");
    .endif

    GdipDisposeImage(Image)
    GdiplusShutdown(gdiplusToken)
    .return 0;

wmain endp

    end _tstart

The input file is 480056 (WomanRGB.bmp from WomanRGB.jpg)
Shapes001.jpg is 3759
Shapes050.jpg is 18945
Shapes100.jpg is 150099
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 25, 2020, 07:37:38 PM
This is my first work in 64 bit coding.  :biggrin:
Did the same direct approach as in my 32 bit GdiPlus examples but it failed.
Don't know if the EncoderParameters object is different in 64 bit.
I'm not sure if I did the addressing of the pointer to the JpgQualityLevel parameter value correctly....

   mov   JpgQualityLevel,10
   lea   eax,JpgQualityLevel
   mov   Qptr,eax      

It saves a jpg file but the Encoder Quality has no effect.
Tried all kind of alignments for the EncoderParameters object.

@nidud
Could you make a memory dump of the ( filled ) EncoderParameters object of your working example?
So I can compare the values and alignments.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: nidud on April 25, 2020, 08:13:29 PM

EncoderParameter (https://github.com/nidud/asmc/blob/master/include/gdiplusimaging.inc#L127)    STRUC
Guid                GUID <>
NumberOfValues      ULONG ?
Type                ULONG ?
Value               PVOID ?
EncoderParameter    ENDS

EncoderParameters   STRUC
Count               UINT ?
Parameter           EncoderParameter <>
EncoderParameters   ENDS

from the list file:

EncoderParameter . . . . . . . .              20 (8)
  Guid . . . . . . . . . . . . .               0   GUID
  NumberOfValues . . . . . . . .              10   ULONG
  Type . . . . . . . . . . . . .              14   ULONG
  Value  . . . . . . . . . . . .              18   PVOID
EncoderParameters  . . . . . . .              28 (8)
  Count  . . . . . . . . . . . .               0   UINT
  Parameter  . . . . . . . . . .               8   EncoderParameter

Note that UINT/ULONG is DWORD and PVOID is QWORD.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 25, 2020, 08:28:17 PM
I tried a whole collection of variations on the JPG code in 64 bit but could not get the extra parameters to work. This is the library format that I got to work but no extra parameters for the last argument,

    invoke GdipSaveImageToFile,hGdip,L(filename),pCLSID,NULL

Algo

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

SaveAsJPGx proc bmHandle:QWORD,filename:QWORD

    LOCAL hGdip  :QWORD
    LOCAL pCLSID :QWORD

  .data
    CLSID_ImageType1 GUID <0557CF401h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>>
  .code

    mov pCLSID, ptr$(CLSID_ImageType1)

    invoke GdipCreateBitmapFromHBITMAP,bmHandle,0,ptr$(hGdip)
    invoke GdipSaveImageToFile,hGdip,L(filename),pCLSID,NULL
    invoke GdipDisposeImage,hGdip

    ret

SaveAsJPGx endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 25, 2020, 09:20:03 PM
nidud, thank you very much for the list dump.  :thumbsup:
It made things clear to me and now I did succeed to do the direct approach in 64 bit as well.

@Hutch, problem solved.  :biggrin:

EDIT: source code update

Code: [Select]
    include \masm32\include64\masm64rt.inc

.data?
pImage                  dq ?
JpgQualityLevel         dd ?
FilenameW               dw  MAX_PATH dup (?)

.data
CLSID_ImageType         GUID <0557CF401h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>> ; JPG image type
JPG_EncoderParameters   dq 1    ; Number of parameters in this structure
CLSID_EncoderQuality    GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh,05dh,0b3h,051h,005h,0e7h,0ebh>>
                        dd 1    ; Number of the parameter values
                        dd 4    ; ValueTypeLong
                        dq offset JpgQualityLevel

szWomanImage            db "Woman.bmp",0
szWoman                 db "Woman.jpg",0
   
.code
entry_point proc

    conout lf,"  64bit GDIplus Jpg Quality Encoder",lf,lf

    GdiPlusBegin                    ; initialise GDIPlus

    invoke  MultiByteToWideChar,CP_ACP,0,addr szWomanImage,-1,addr FilenameW,MAX_PATH-1
    invoke  GdipCreateBitmapFromFile,addr FilenameW,addr pImage
    test    rax,rax
    jnz     Done
   
    mov     JpgQualityLevel,50      ; 0 to 100

    invoke  MultiByteToWideChar,CP_ACP,0,addr szWoman,-1,addr FilenameW,MAX_PATH-1
    invoke  GdipSaveImageToFile,pImage,addr FilenameW,addr CLSID_ImageType,addr JPG_EncoderParameters

    invoke  GdipDisposeImage,pImage
Done:

    waitkey "  Press any key to continue ..."

    GdiPlusEnd                      ; GdiPlus cleanup

    invoke  ExitProcess,0
    ret

entry_point endp

end
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 25, 2020, 09:55:59 PM
Great, it works well, I have tested it from 10% to 75% and the results are perfect.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 25, 2020, 11:54:55 PM
Made the code into module format, works fine.

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

SaveImgAsJpg proc BmpHndl:QWORD,filename:QWORD,quality:QWORD

    LOCAL pImage :QWORD

  ; ------------------------------------------------------------------------

  .data?
    QualityLevel@@@@@@@@   dd ?    ; mangle to avoid accidental duplicates

  .data
    CLSID_ImageType1 GUID <0557CF401h,01A04h,011D3h,<09Ah,073h,000h, \
                           000h,0F8h,01Eh,0F3h,02Eh>> ; JPG image type

    JPG_EncoderParameters  dd 1    ; Number of parameters in this structure
                           dd 0    ; alignment !
    CLSID_EncoderQuality   GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh, \
                                 05dh,0b3h,051h,005h,0e7h,0ebh>>
                           dd 1    ; Number of the parameter values
                           dd 4    ; ValueTypeLong
    JpgQualityParameterPTR dq offset QualityLevel@@@@@@@@

  .code

  ; ------------------------------------------------------------------------

    cmp quality, 100
    ja default
    jmp next

  default:
    mov quality, 75                                             ; default for quality error

  next:
    rcall GdipCreateBitmapFromHBITMAP,BmpHndl,0,ptr$(pImage)    ; convert BMP handle
    mov rax, quality                                            ; set the quality level
    mov QualityLevel@@@@@@@@, eax                               ; 100 = highest, 0 = lowest
    invoke GdipSaveImageToFile,pImage,L(filename), \
           ADDR CLSID_ImageType1,ADDR JPG_EncoderParameters     ; write JPG image to file
    rcall GdipDisposeImage,pImage                               ; delete GDIP handle

    ret

SaveImgAsJpg endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤


I should mangle all of the names to ensure no accidents.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 26, 2020, 12:32:17 AM
Cool.  :cool:

Finally started exploring 64bit coding.
Working my way thru MACROS and addressing modes, still have a lot to learn...

Made my first Routine in 64bit.

Code: [Select]
    include \masm32\include64\masm64rt.inc

.const
Image_BMP       equ 0
Image_JPG       equ 1
Image_GIF       equ 2
Image_TIF       equ 5
Image_PNG       equ 6

.data?
pImage                  dq ?
pImageTemp              dq ?
JpgQualityLevel         dd ?

.data
CLSID_ImageTypes        GUID <0557CF400h,01A04h,011D3h,<09Ah,073h,000h,000h,0F8h,01Eh,0F3h,02Eh>> ; For all image types
JPG_EncoderParameters   dq 1    ; Number of parameters in this structure
CLSID_EncoderQuality    GUID <01d5be4b5h,0fa4ah,0452dh,<09ch,0ddh,05dh,0b3h,051h,005h,0e7h,0ebh>>
                        dd 1    ; Number of the parameter values
                        dd 4    ; ValueTypeLong
                        dq offset JpgQualityLevel
.code

SaveJpgQualityImage proc pGdiPlusImage:QWORD,pFilename:QWORD,Image_Quality:DWORD

    invoke  GdipCloneImage,pGdiPlusImage,addr pImageTemp ; make a copy of the image to work with.

    mov     eax,Image_Quality
    mov     JpgQualityLevel,eax
    mov     byte ptr [CLSID_ImageTypes],Image_JPG
   
    invoke  GdipSaveImageToFile,pImageTemp,L(pFilename),addr CLSID_ImageTypes,addr JPG_EncoderParameters
    invoke  GdipDisposeImage,pImageTemp
    ret
SaveJpgQualityImage endp
   
entry_point proc

    conout lf,"  64bit GDIplus Jpg Quality Encoder",lf,lf

    GdiPlusBegin                    ; initialise GDIPlus

; Load a test Image
    invoke  GdipCreateBitmapFromFile,L("Woman.bmp"),addr pImage
    test    rax,rax
    jnz     Done

    invoke  SaveJpgQualityImage,pImage,"Woman.jpg",50

    invoke  GdipDisposeImage,pImage
Done:

    waitkey "  Press any key to continue ..."

    GdiPlusEnd                      ; GdiPlus cleanup

    invoke  ExitProcess,0
    ret

entry_point endp

end
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 26, 2020, 01:18:04 AM
Once you get a feel for it, its great stuff, lots more registers, massive amounts of memory and without having to keep making prototypes, you can code very fast with practice.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 26, 2020, 03:56:24 AM
@Hutch
Having fun already, prepare for some questions later.  :biggrin:

Bare Minimum 64 bit GDIplus code to save different Image Types, PixelFormat and colorconversions.
Only 1 GUID used for all the Image Types.
Now you can find a balance between Image quality and file size.

You need to replace the old 64bit "gdiplus.lib" with a newer version.
The lib is included in the Lib folder, it's from the Microsoft SDK Windows v7.1A.

Hope I didn't violate the 64 bit coding rules, else let me know......
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Vortex on April 26, 2020, 05:40:20 AM
Hello Siekmanski,

I was able to build your example GDIplus64_color_conversions with Pelle's 64-bit import library. An alternative to SDK Windows v7 :

Code: [Select]
\PellesC\lib\Win64\gdiplus.lib
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 26, 2020, 06:20:21 AM
Nice that it works.
Still have to find my way in 64 bit coding, maybe the code can be improved?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: jj2007 on April 29, 2020, 07:07:49 AM
A little bit off topic: I am trying (successfully) to implement...

GdipDrawImageRectRectI(*graphics, GpImage *image, INT dstx, INT dsty, INT dstwidth, INT dstheight, INT srcx, INT srcy, INT srcwidth, INT srcheight, GpUnit srcUnit, GpImageAttributes* imageAttributes, DrawImageAbort callback, VOID * callbackData)

... to zoom into a picture. It works fine. Then I want to save the image using GdipSaveImageToFile

GdipSaveImageToFile(GpImage *image, WCHAR* filename, CLSID* clsidEncoder, EncoderParameters* encoderParams)

That works fine, too, but it saves the original image, not the zoomed one. Does Draw write to the bitmap/the image?

My understanding was that graphics is the equivalent of a DC in "normal" GDI, while image is the bitmap. Since GdipSaveImageToFile saves the bitmap, I expected to see the zoomed version. What's wrong with my logic? And how could I get access to the zoomed (or otherwise modified) image?

Unfortunately, the documentation is lousy :cool:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 29, 2020, 07:28:04 AM
I don't have a clue, I had the same thoughts as you.
Maybe this works and sets the image size to 128*128 for the output image file?
Didn't test it ....

GdipGetImageThumbnail,pImage,128,128,addr pThumbnail,NULL,NULL
GdipSaveImageToFile,pThumbnail,offset FilenameW,offset clsidEncoder,NULL
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on April 29, 2020, 07:36:35 AM
Aaahh, you want the zoomed portion to be saved.

Found this:
https://stackoverflow.com/questions/36821859/c-sharp-gdi-scaletransform-ok-on-picturebox-but-image-saved-is-original
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on April 29, 2020, 07:59:02 AM
 :biggrin:

> Hope I didn't violate the 64 bit coding rules, else let me know......

Win 64 lets you know, if it runs, you have done something right, if it builds OK but does not run and dies silently, you have the PHUN of finding out why.  :tongue:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on May 06, 2020, 11:33:29 PM
I'm working on saving PngIcons, this works very well.  :eusa_dance:
But saving Cursors (.cur) with embedded .Png images fails.
The resource compiler will not handle them.

I have done my research on finding cursor file specifications.
Some say cursors can have embedded .Png images, but without any file format specifications.
Others only say cursors have embedded .Bmp format with XOR / AND maps.

Anyone of you know more about this phenomena?
Or is it a fact, cursors can't have embedded .Png images?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on May 07, 2020, 01:29:07 AM
I think a cursor works similar to a bitmap and in the past I have seen icons used as bitmaps but I would imagine that however a cursor is created, it will have to be converted to cursor format. Its an old format that dates from the Win3.0 era and while later ones depending on the OS version can run coloured cursors, some of the older graphics card hardware did not support them.

If I need a custom cursor which is not very often, I use Axialis Cursor Workshop so if you need some samples to play with let me know.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on May 07, 2020, 02:01:56 AM
Thanks, if I need them I'll let you know.  :thumbsup:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on May 07, 2020, 12:44:03 PM
Marinus,

Have a look at this link for cursor information.

https://en.wikipedia.org/wiki/ICO_(file_format)
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on May 07, 2020, 01:31:53 PM
That's the page I based my PngIco code on.  :thumbsup:
Icon and cursor headers are almost identical, that's why it is strange the resource compiler chokes on cursor files.
Maybe the latest resource compiler can do the job?
Maybe you or someone else can provide me with the latest 2019-2020 RC.Exe and RcDll.Dll?
The one I have is from 2015.

I managed to crunch one of your icons from 16958 bytes to 5431 bytes without loss of alpha and color information.

BTW I found a work around to save the cursor as a compressed icon, load it as an icon and turn it into a cursor again in code.

in the wc:windowclass
Code: [Select]
    invoke  LoadIcon,hInst,100      ; Load a compressed PNG icon and turn it into a cursor
    invoke  GetIconInfo,eax,offset Icon_Info
    mov     Icon_Info.fIcon,FALSE   ; FALSE specifies a cursor
    mov     Icon_Info.xHotspot,0    ; The x-coordinate of a cursor's hot spot
    mov     Icon_Info.yHotspot,0    ; The y-coordinate of a cursor's hot spot
    invoke  CreateIconIndirect,offset Icon_Info
    mov     wc.hCursor,eax          ; Now it's a cursor...
I'm going to write an Image Tool so we can do all of this automated.

Here is the template for this tool where PngIcons are inserted and 1 converted to a cursor in code.
Also included your crunched Icon.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on May 07, 2020, 06:06:58 PM
Marinus,

Would using the following be better to allow for larger sizes ?

>     mov hIcon, rv(LoadImage,hInstance,10,IMAGE_ICON,64,64,LR_DEFAULTCOLOR)
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on May 07, 2020, 07:48:07 PM
Here is an example of using LoadImage() where you can scale the cursor image up or down. The static cursor is a 32 pixel square RGBA type and I have scaled it up to 64 pixel.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on May 07, 2020, 09:20:49 PM
Yes, I know this, but it does not solve the problem I have with the Resource Compiler (2015) on my computer.
It will not compile cursors with embedded .Png images.

So, my hope is in one of you who want to provide me with the latest Resource Compiler of 2019-2020.  :thumbsup:
Maybe this version can handle cursors I create with embedded .Png images.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Vortex on May 07, 2020, 09:25:56 PM
Hi Siekmanski,

You could try Pelle's resource compiler porc.exe
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: hutch-- on May 07, 2020, 09:26:51 PM
Mine is the same version as yours, 2015. I have just picked out the vs2019 version and attached it.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on May 07, 2020, 09:33:07 PM
Thanks guys, I will test it over a few hours.   :thumbsup:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Vortex on May 07, 2020, 09:59:59 PM
Hello,

There is also Jeremy Gordon's resource compiler GoRC  :

http://www.godevtool.com/
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on May 08, 2020, 01:00:07 AM
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.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 20, 2020, 02:58:42 AM
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:

Code: [Select]

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

Code: [Select]

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


Code: [Select]
    ; 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
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 20, 2020, 03:20:12 AM
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.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 20, 2020, 03:53:07 AM
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
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 20, 2020, 04:57:55 AM
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.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 20, 2020, 05:23:07 AM
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:



(https://i.ibb.co/0nVDxz0/cvvc-Image1.png) (https://ibb.co/tHPm910)


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)
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 20, 2020, 05:32:45 AM
Probably the pImage pointer to the data is not valid?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 20, 2020, 05:36:27 AM
maybe, but how to validated it ?

It do export a file with some bitmap header on it, but the data is corrupted
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 20, 2020, 05:40:38 AM
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.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 20, 2020, 05:49:51 AM
Have a look at these sources, it does what you need.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 20, 2020, 01:58:28 PM
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 ?

Code: [Select]
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();
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 20, 2020, 05:36:18 PM
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.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 20, 2020, 06:36:45 PM
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 ?

Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 21, 2020, 03:34:06 AM
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.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 21, 2020, 05:17:18 AM
Still not working. I have no clue what i´m doing wrong.

This time i did this as you suggested:

Code: [Select]
    (...)
    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

Code: [Select]
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
Code: [Select]
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
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 21, 2020, 06:25:40 AM
Try PixelFormat32bppARGB, it's 32 bits per pixel, the stride is 4 times the width of the image.
So, there is no padding involved.

Be sure the raw image data in memory is valid, thus in 32 bit ARGB format.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 21, 2020, 06:44:02 AM
If it still doesn't work, I can write a simple example tomorrow, if you want?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 21, 2020, 08:46:23 AM
Tks, Marinus. If you have time, please, build a small example.

It´s still don´t working.

The goal is to convert to/from whatever image format is stored on a virtual memory buffer (after it collected the image (or pixel data from RT_BITMAP on this case) from the resource section. )


Note: I´m currently updating RosAsm for the next release and i´m fixing these kind of issues on the resources editor. Currently i implemented it to assemble/disassemble and debug SSE3, SSE4, upgraded the debugger, the disassembler and now i´m fixing some of the old resources features for the next release. Doing this i could then continue working on the google watermark remover more properly because i´ll then have the necessary tools to build SSE4 or SSE3 variations of algos if needed :azn:
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: daydreamer on September 21, 2020, 11:36:57 PM
one simple GDI+ question I have after I seen you can rotate images with a GDI+ function:does GDI+ have caps to load and save .ico files?,I want to save size of icons by load .ico file and rotate it as many times I need to have for example icons pointing at 4 or 8 directions,which also could be useful as cursors
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 21, 2020, 11:58:59 PM
Hi guga,

Here is an example to get access to the pixels in VirtualMem and converts it in a Gdiplus Bitmap.
You can now save it in all the different Color and Image formats you want.

If I have time this week, I will write a routine that loads all Image formats from:

- Resource
- File
- Memory
- and raw pixels from virtual memory
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 22, 2020, 12:06:52 AM
Hi Magnus,

As far as I know you can not save Icons with Gdiplus but, I wrote a program that uses Gdiplus with an ico header in front of a .PNG file.
It works perfect and creates very small Icons.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: TouEnMasm on September 22, 2020, 01:15:03 AM

Icon and GDI plus
perhaps :
Quote
GpStatus WINGDIPAPI GdipCreateBitmapFromHICON(HICON hicon, GpBitmap** bitmap)
GpStatus WINGDIPAPI GdipCreateHICONFromBitmap(GpBitmap* bitmap, HICON* hbmReturn)
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 22, 2020, 01:50:14 AM
Hi Marinus. Many tks :thumbsup: :thumbsup: :thumbsup:

I´m looking at it right now :)

During this morning i succeeded to save it from the resources section, but i´m not sure if it will save it correctly. I´m take a look at yours and see if it´s easier then the way i found this morning. Your´s is, as usual, simpler and easier to follow :thumbsup:

What i did was create a HBitmap from an image loaded in memory and then using GdipCreateBitmapFromHBITMAP to save it as bitmap and later export to other formats. Tooo overhead, i think :mrgreen: :mrgreen: Your´s seems to be way easier. I`ll give a try on it to see if it works for the 8 bpp  bitmap images as well.

What i did was:

1 - GdipCreateBitmapFromBmpMem
Code: [Select]

;;
    GdipCreateBitmapFromBmpMem
        This function creates a Bitmap object from a BitMapInfo structure existent in RT_BITMAP data type of
        the resources section on a PE file. It can also be used to create a HBitmap directly from
        a bitmap loaded in memory to be used in GdiPlus Apis.

    Parameters:
        InputData(In) - Pointer to the start of a BITMAPINFOHEADER structure of a image. The structure can
                        be either loaded from memory or the one existant inside the resources sections of a PE
                        at the RT_BITMAP type from the RESOURCE_HEADER structure
        pImage(out) -   Pointer to a DWORD variable that receives a pointer to a Bitmap object.

    Return value:

        If the function succeeds, it returns TRUE, and it will stored the Bitmap Object onto pImage parameter.
        If the function fails it returns FALSE

;;

Proc GdipCreateBitmapFromBmpMem::
    Arguments @InputData, @pImage
    Local @rhPalette, @hBmp, @ReturnValue
    Uses ecx, edx

    xor eax eax
    On D@pImage = 0, ExitP
    mov D@ReturnValue &FALSE

    lea eax D@rhPalette | mov D$eax 0
    call CreatehBitmapFromBmpMem D@InputData, eax
    mov D@hBmp eax

    .If eax = 0
        If D@rhPalette <> 0
            call 'GDI32.DeleteObject' D@rhPalette
        End_If
        xor eax eax
        ExitP
    .End_If

    mov eax D@pImage | mov D$eax 0
    call 'gdiplus.GdipCreateBitmapFromHBITMAP' D@hBmp, D@rhPalette, D@pImage
    If eax = &S_OK
        mov D@ReturnValue &TRUE
    End_If

    call 'GDI32.DeleteObject' D@rhPalette
    call 'GDI32.DeleteObject' D@hBmp

    mov eax D@ReturnValue

EndP

2 - CreatehBitmapFromBmpMem

Code: [Select]


;;
    CreatehBitmapFromBmpMem
        This function creates a HBitmap from a BitMapInfo structure existent in RT_BITMAP data type of
        the resources section on a PE file. It can also be used to create a HBitmap directly from
        a bitmap loaded in memory.

    Parameters:
        pbmih(In) - Pointer to the start of a BITMAPINFOHEADER structure of a image. The structure can
                    be either loaded from memory or the one existant inside the resources sections of a PE
                    at the RT_BITMAP type from the RESOURCE_HEADER structure
        rhPalette(out) - Pointer to a variable that will hold the handle to a logical palette

    Return value:

        If the function succeeds, the return value is a handle to the compatible bitmap.
        If the function fails, the return value is NULL.
;;

[BITMAPFILEHEADER.bfTypeDis 0
 BITMAPFILEHEADER.bfSizeDis 2
 BITMAPFILEHEADER.bfReserved1Dis 6
 BITMAPFILEHEADER.bfReserved2Dis 8
 BITMAPFILEHEADER.bfOffBitsDis 10]

[Size_Of_BITMAPFILEHEADER 14] ; always unaligned

[BITMAPINFOHEADER.biSizeDis 0
 BITMAPINFOHEADER.biWidthDis 4
 BITMAPINFOHEADER.biHeightDis 8
 BITMAPINFOHEADER.biPlanesDis 12
 BITMAPINFOHEADER.biBitCountDis 14
 BITMAPINFOHEADER.biCompressionDis 16
 BITMAPINFOHEADER.biSizeImageDis 20
 BITMAPINFOHEADER.biXPelsPerMeterDis 24
 BITMAPINFOHEADER.biYPelsPerMeterDis 28
 BITMAPINFOHEADER.biClrUsedDis 32
 BITMAPINFOHEADER.biClrImportantDis 36]

[Size_Of_BITMAPINFOHEADER 40]


[BITMAPINFO.bmiHeader.biSizeDis 0
 BITMAPINFO.bmiHeader.biWidthDis 4
 BITMAPINFO.bmiHeader.biHeightDis 8
 BITMAPINFO.bmiHeader.biPlanesDis 12
 BITMAPINFO.bmiHeader.biBitCountDis 14
 BITMAPINFO.bmiHeader.biCompressionDis 16
 BITMAPINFO.bmiHeader.biSizeImageDis 20
 BITMAPINFO.bmiHeader.biXPelsPerMeterDis 24
 BITMAPINFO.bmiHeader.biYPelsPerMeterDis 28
 BITMAPINFO.bmiHeader.biClrUsedDis 32
 BITMAPINFO.bmiHeader.biClrImportantDis 36
 BITMAPINFO.bmiColors.rgbBlueDis 40
 BITMAPINFO.bmiColors.rgbGreenDis 41
 BITMAPINFO.bmiColors.rgbRedDis 42
 BITMAPINFO.bmiColors.rgbReservedDis 43]

[Size_of_BITMAPINFO 44]

[BITMAPCOREHEADER.bcSizeDis 0
 BITMAPCOREHEADER.bcWidthDis 4
 BITMAPCOREHEADER.bcHeightDis 6
 BITMAPCOREHEADER.bcPlanesDis 8
 BITMAPCOREHEADER.bcBitCountDis 10]

[Size_Of_BITMAPCOREHEADER 12]


; https://cpp.hotexamples.com/pt/site/file?hash=0x87ae29357002cdaaf6145c64b282397a0fd7de4a38023f9193fd5f26f6072cc8
Proc CreatehBitmapFromBmpMem::
    Arguments @pbmih, @rhPalette
    Local @hDC, @iNumColors, @hBitmapFinal
    Uses ecx, edx

    call 'USER32.GetDC' &NULL
    mov D@hDC eax

    mov D@iNumColors 0

    lea eax D@iNumColors
    call CreateDIBPalette D@pbmih, eax
    mov edx D@rhPalette
    mov D$edx eax
    If eax <> 0
        call 'GDI32.SelectPalette' D@hDC, eax, &FALSE
        call 'GDI32.RealizePalette' D@hDC
    End_If

    mov ecx D@pbmih | mov edx D@pbmih
    add edx D$ecx+BITMAPINFOHEADER.biSizeDis
    mov eax D@iNumColors | lea ecx D$edx+eax*4
    call 'GDI32.CreateDIBitmap' D@hDC, D@pbmih, &CBM_INIT, ecx, D@pbmih, &DIB_RGB_COLORS
    mov D@hBitmapFinal eax
    call 'USER32.ReleaseDC' &NULL, D@hDC
    mov eax D@hBitmapFinal

EndP

3 -  CreateDIBPalette
Code: [Select]

;;
    CreateDIBPalette
        This function creates a logical Palette from the BitmapInfo Header of a bitmap file.

        lpbmi(in) - Pointer to the start of a BITMAPINFOHEADER structure of a image. The structure can
                    be either loaded from memory or the one existant inside the resources sections of a PE
                    at the RT_BITMAP type from the RESOURCE_HEADER structure
        riNumColors(out) - Pointer to a variable that will hold the total amount of color Indexes ued on the
                           generated palette

    Return value:

        If the function succeeds, the return value is a handle to the logical palette.
        If the function fails, the return value is NULL.

;;

[LOGPALETTE.palVersionDis 0
 LOGPALETTE.palNumEntriesDis 2
 LOGPALETTE.palPalEntry.peRedDis 4
 LOGPALETTE.palPalEntry.peGreenDis 5
 LOGPALETTE.palPalEntry.peBlueDis 6
 LOGPALETTE.palPalEntry.peFlagsDis 7]

[Size_Of_LOGPALETTE 8]

Proc CreateDIBPalette::
    Arguments @lpbmi, @riNumColors
    Local @NumColors, @lpPal, @TmpMem, @hPal
    Uses esi, edi, ecx, edx

    mov esi D@lpbmi
    movzx ecx W$esi+BITMAPINFOHEADER.biBitCountDis

    ; pick number of colors
    xor eax eax ; No palette needed for 24 BPP DIB
    If ecx <= 8 ; we only need for 8 bpp or less
        mov eax 1 | shl eax cl
    Else_If D$esi+BITMAPINFOHEADER.biClrUsedDis > 0
        mov eax D$esi+BITMAPINFOHEADER.biClrUsedDis
    End_If
    mov ecx D@riNumColors
    mov D$ecx eax
    On eax = 0, ExitP ; No palette if no colors
    mov D@NumColors eax

    ; Build palette from bitmap info

    ; get the total size of the pallette
    mov ecx D@NumColors
    lea edx D$ecx*4+4 ; sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * (riNumColors-1)) = 8 + 4*(NumColors-1) = 8 + 4*NumColors - 4 => 4*(1+NumColors)
    mov D@TmpMem 0 | lea eax D@TmpMem
    call 'RosMem.VMemAlloc' eax, edx
    mov D@lpPal eax
    mov edi eax

    mov W$edi+LOGPALETTE.palVersionDis 0300
    mov eax D@NumColors | mov W$edi+LOGPALETTE.palNumEntriesDis ax

    xor ecx ecx
    Do
        mov al B$esi+ecx*4+BITMAPINFO.bmiColors.rgbRedDis | mov B$edi+ecx*4+LOGPALETTE.palPalEntry.peRedDis al
        mov al B$esi+ecx*4+BITMAPINFO.bmiColors.rgbGreenDis | mov B$edi+ecx*4+LOGPALETTE.palPalEntry.peGreenDis al
        mov al B$esi+ecx*4+BITMAPINFO.bmiColors.rgbBlueDis | mov B$edi+ecx*4+LOGPALETTE.palPalEntry.peBlueDis al
        mov B$edi+ecx*4+LOGPALETTE.palPalEntry.peFlagsDis 0
        inc ecx
    Loop_Until ecx >= D@NumColors

    call 'GDI32.CreatePalette' D@lpPal
    mov D@hPal eax
    call 'RosMem.VMemFree' D@lpPal ; release the allocated pallette
    mov eax D@hPal

EndP



It works like:


    mov eax D@pRsrcList
    mov ecx D$eax+RosAsm_Rsrc_Data.PtrDis | mov D@InputData ecx

    lea eax D@pImage | mov D$eax 0
    call 'FastCRT.GdipCreateBitmapFromBmpMem' D@InputData, eax

    call SaveNonIndexedImage D@pImage, OtherSaveFilter, Image_PNG, PixelFormat32bppRGB


As you see, is way complicated to work then yours (My head will blow...didn´t sleep again) :bgrin: :bgrin:. I´ll  take a look at yours and see if i can make it work with it insetad the huge bloated code i did :)
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 22, 2020, 08:41:51 AM
Hi Marinus

Im testing the different output formats for bitmap. 2 of them seems not to be working

PIXELFORMAT_16BPPGRAYSCALE
and
PIXELFORMAT_1BPPINDEXED

Are not converting to gray or monochrome.


These other formats below i didn´t tested yet:
PIXELFORMAT_48BPPRGB
PIXELFORMAT_64BPPARGB
PIXELFORMAT_64BPPPARGB

All the rest seems to be working fine.

I tried again using your routine on the example, but it only works when i convert the 8bit image through mine routine GdipCreateBitmapFromBmpMem. I didn´t understood completely what is the math related to those conversions of different formats  to point to the start of the pixel data and not the start of a palette, for example (which seems to be the case when dealing with 8bpp or less right ?)

Below is what i´m currently doing to test all the routines :)
(https://i.ibb.co/02KMr62/cvvc-Image1.png) (https://ibb.co/T218t72)


And the attached file is the output in  PIXELFORMAT_32BPPRGB


I´ll now try to export in png and later in jpg, heic and emf file.


One question...I know how to decode a webp file using libwebp, but how to make it work directly from gdiplus ?
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 22, 2020, 09:24:08 AM
Quote
I tried again using your routine on the example, but it only works when i convert the 8bit image through mine routine GdipCreateBitmapFromBmpMem. I didn´t understood completely what is the math related to those conversions of different formats  to point to the start of the pixel data and not the start of a palette, for example (which seems to be the case when dealing with 8bpp or less right ?)

This is what I always do,
First load the image and convert it to 32 bit ARGB -> [ invoke      GdipBitmapLockBits,pImage,NULL,ImageLockModeRead,PixelFormat32bppARGB,offset GDIplusBitmapData ]

Processing the pixels if I need to.

Then save it in the color format I want with the help of GdiPlus. ( as I did in GDIplusMemory.zip )
Never did I use Pixelformats that has more than 8 bit per color element.

PIXELFORMAT_16BPPGRAYSCALE
I have to check the formats with more than 8 bit per color element.
I wonder how you project more than 256 pure gray colors to the screen.....

PIXELFORMAT_1BPPINDEXED works OK
Function: SaveColorIndexedImage -> set the Color_Count to 2

Quote
One question...I know how to decode a webp file using libwebp, but how to make it work directly from gdiplus ?

Don't know.
You could try to save the raw ARGB data to memory ( if libwebp allows you to do that ) and use GdipCreateBitmapFromScan0.
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 22, 2020, 02:03:11 PM
Quote
I tried again using your routine on the example, but it only works when i convert the 8bit image through mine routine GdipCreateBitmapFromBmpMem. I didn´t understood completely what is the math related to those conversions of different formats  to point to the start of the pixel data and not the start of a palette, for example (which seems to be the case when dealing with 8bpp or less right ?)

This is what I always do,
First load the image and convert it to 32 bit ARGB -> [ invoke      GdipBitmapLockBits,pImage,NULL,ImageLockModeRead,PixelFormat32bppARGB,offset GDIplusBitmapData ]

Ok, then you are using it on stream right ? From 2d_image_loader_saver.asm, i see that you locked the data from the resoruce3s section 1st after using global alloc to create a copy oif the image on memory.

This seems to work only for the data located in the RT_RCDATA type of the PE and not from the RT_BITMAP, for example , since RT_BITMAP, the BITMAPFILEHEADER is not present.

I´ll later try testing the routine from images stored with RT_RCDATA type, but after making it work for the RT_BITMAP ones.


But, why 2D_Image_loader_saver seems to work with GdipBitmapLockBits and GDiPlusMemory don´t ?

Can you do a small test ?  Create a file and insert a 8bpp bitmap (or even a lower quality ones) on RT_BITMAP (And not RT_RCDATA) and then copy the data onto a virtualloc buffer (so, without GlobalAlloc or GdipBitmapLockBits etc) and try to make it convert from the generated buffer in memory, so i can try see exactly what i´m doing wrong ?


It seems that there are 2 different kind of files that gdiplus can handle the images. One containing a full image file stored in RT_RCDATA and other containing incomplete bitmaps (without the header) from the RT_BITMAP. Maybe gdiplus handle those kind of files on a different way ?

Processing the pixels if I need to.

Then save it in the color format I want with the help of GdiPlus. ( as I did in GDIplusMemory.zip )
Never did I use Pixelformats that has more than 8 bit per color element.

Ok, but on GDIplusMemory you created the pixels manually and pointed to GdipCreateBitmapFromScan0 the start of the data in the buffer.

How do i located the correct pixels data when a image is not in 32bpp originally and contains a palette ? I mean, like the images i´m testing that does not have the BITMAPFILEHEADER and seems to contains a palette because they are encoded with 256 colors (or less)
 So, how do i use the information from their BITMAPINFOHEADER to  locate the pixel data and correct them to 32 bits when needed ?

Quote
PIXELFORMAT_16BPPGRAYSCALE
I have to check the formats with more than 8 bit per color element.
I wonder how you project more than 256 pure gray colors to the screen.....

PIXELFORMAT_1BPPINDEXED works OK
Function: SaveColorIndexedImage -> set the Color_Count to 2

For the PIXELFORMAT_1BPPINDEXED this is how i did, but it didn´t exported the data. Same error on returning 2 in eax.

The routine i did to select how many colors to be exported are like this:

Code: [Select]
;PIXELFORMAT_1BPPINDEXED 030101
 ;PIXELFORMAT_4BPPINDEXED 030402
 ;PIXELFORMAT_8BPPINDEXED 030803
    mov eax D@PixFormat
    .If al <= 3
        If al =  3
            mov eax 256 ; 2^8
        Else_If al = 2
            mov eax 16 ; 2^4
        Else
            mov eax 2 ; 2^1
        End_If
        call SaveColorIndexedImage D$pImage, OtherSaveFilter, D@ImgType, eax ; RLE encoding
    .Else
        call SaveNonIndexedImage D@pImage, OtherSaveFilter, D@ImgType, D@PixFormat
    .End_If


The only type of image that will be passed through SaveColorIndexedImage are these, right ?

PIXELFORMAT_8BPPINDEXED = 256 colors
PIXELFORMAT_4BPPINDEXED = 16 colors
PIXELFORMAT_1BPPINDEXED = 2 colors
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: jj2007 on September 22, 2020, 06:39:56 PM
I´ll later try testing the routine from images stored with RT_RCDATA type, but after making it work for the RT_BITMAP ones.

Streaming works exactly like loading from file. Therefore the resource must provide you with a pointer to the file content, with no extra manipulation by the OS. So you need the RCDATA format. Attention, the resource compiler does not complain if you call it RC_DATA or RT_RCDATA, but it will simply not run...!

include \masm32\MasmBasic\Res\MbGui.asm
Event Paint
  GuiImage 123, fit  ; stream resource #123, fit image to client area
GuiEnd
Rsrc
123 RCDATA "pics\\beach.jpg"
Rsrc
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on September 22, 2020, 07:03:53 PM
Hi guga,

Quote
I´ll later try testing the routine from images stored with RT_RCDATA type, but after making it work for the RT_BITMAP ones.

I think you are making it more complicated than it should be.
Sure you can load it as RT_BITMAP without the BITMAPFILEHEADER but, it has 2 disadvantages.
- you bloat your executable with the uncompressed bitmap data.
- you need to write your own routines to convert it to the format you want.

It's easier to store a .JPG or .PNG in the resource section ( or in the .DATA section ) and let GdiPlus handle all the things for us.
I'm using my own Resource ID (INCBIN) to load all data types.

Quote
Can you do a small test ?  Create a file and insert a 8bpp bitmap (or even a lower quality ones) on RT_BITMAP (And not RT_RCDATA) and then copy the data onto a virtualloc buffer (so, without GlobalAlloc or GdipBitmapLockBits etc) and try to make it convert from the generated buffer in memory, so i can try see exactly what i´m doing wrong ?

As I mentioned in Reply #64,
If I have time this week, I will write a routine that loads all Image formats from:
- Resource
- File
- Memory
- and raw pixels from virtual memory

Quote
For the PIXELFORMAT_1BPPINDEXED this is how i did, but it didn´t exported the data. Same error on returning 2 in eax.

The routine i did to select how many colors to be exported are like this:

Code: [Select]
;PIXELFORMAT_1BPPINDEXED 030101
 ;PIXELFORMAT_4BPPINDEXED 030402
 ;PIXELFORMAT_8BPPINDEXED 030803
    mov eax D@PixFormat
    .If al <= 3
        If al =  3
            mov eax 256 ; 2^8
        Else_If al = 2
            mov eax 16 ; 2^4
        Else
            mov eax 2 ; 2^1
        End_If
        call SaveColorIndexedImage D$pImage, OtherSaveFilter, D@ImgType, eax ; RLE encoding
    .Else
        call SaveNonIndexedImage D@pImage, OtherSaveFilter, D@ImgType, D@PixFormat
    .End_If

The only type of image that will be passed through SaveColorIndexedImage are these, right ?

PIXELFORMAT_8BPPINDEXED = 256 colors
PIXELFORMAT_4BPPINDEXED = 16 colors
PIXELFORMAT_1BPPINDEXED = 2 colors

Look at my routine, it handles everything, you only need to set the Color_Count and it selects the right indexed PIXELFORMAT_ for you.
Remember, you can have Color Palettes with any arbitrary number of entries. ( 2- 256, it saves memory )

Code: [Select]
; save Color Indexed images with 2 to 256 colors. ( any number between 2 to 256 )
SaveColorIndexedImage proc pGdiPlusImage:DWORD,pFilename:DWORD,Image_Type:DWORD,Color_Count:DWORD

    mov     ecx,Color_Count
    mov     eax,offset ColorPalette
    mov     dword ptr [eax],PaletteFlagsGrayScale
    mov     dword ptr [eax+4],ecx
    invoke  GdipInitializePalette,eax,PaletteTypeOptimal,dword ptr [eax+4],FALSE,pGdiPlusImage
    test    eax,eax
    jnz     Done

    mov     ecx,Color_Count
    mov     eax,PixelFormat8bppIndexed ; -> 17 to max 256 colors
    cmp     ecx,16
    ja      ConvertFormat   
    mov     eax,PixelFormat4bppIndexed ; -> 3 to max 16 colors
    cmp     ecx,2
    ja      ConvertFormat   
    mov     eax,PixelFormat1bppIndexed ; 2 colors
   
ConvertFormat:
    invoke  GdipBitmapConvertFormat,pGdiPlusImage,eax,DitherTypeSolid,PaletteTypeOptimal,offset ColorPalette,0
    test    eax,eax
    jnz     Done

    invoke  MultiByteToWideChar,CP_ACP,0,pFilename,-1,offset FilenameW,MAX_PATH-1
    mov     eax,Image_Type
    mov     byte ptr[CLSID_ImageTypes],al
    invoke  GdipSaveImageToFile,pGdiPlusImage,offset FilenameW,offset CLSID_ImageTypes,NULL
Done:
    ret

SaveColorIndexedImage endp
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: guga on September 25, 2020, 03:17:47 AM
Hi JJ and Marinus many thanks

JJ. I saw that, but i´m currently working on RT_BITMAP and not RCDATA yet. The main problem is that bitmaps in this kind of resources have their main header stripped and it fools gdipus when the file is smaller then 24 bpp. making it not work from memory when using functions like: GdipCreateBitmapFromScan0 and after passing through GdipSaveImageToFile.

When we have no header (the BM tag) gdiplus appears only works correctly when the data is in a Dword chunk, so each dword contains 4 colors (R, G, B and Alpha). But for those weird bitmaps containing a palette without the header that points to the proper pixel offset, it fails to save. (A win32 error is generated internally).

One way to solve could be getting the data pixels from files 1, 4, 8 bpps and converting them to 32 pixels manually. before pointing to  GdipCreateBitmapFromScan0, but this is too overhead. I made a workaround for handling such files (at least, while Marinus don´t do the routines for us to test this weekend when he have more time)


While i was making this alternative way of loading these files, i made a small routine to point to the proper pixels data to see if what i was doing was correct. The function is like this:

Code: [Select]

;;
    GetPixelPosFromBmpNfoHdr
        This function retrieves the Pixel Address from the BITMAPINFOHEADER structure of a bitmap

    Parameters:
        pBmi - Pointer to a BITMAPINFOHEADER from where the position of the pixel data will be found.

    Return value:
        The function will return in eax the adress of the start of the pixel data on a Bitmap

    Remarks:
        This function is particulary usefull to locate the pixels address from bitmaps (1bpp, 4bpp, 256bp etc) located
        on the resoruces section stored as a &RT_BITMAP. This is because the bitmaps stored in this field does not contains
        the BITMAPFILEHEADER structure making it harder to directly manipulate the contents, or saving etc.
       
        If the file contains the BITMAPFILEHEADER structure, you need to add the size of it (14 bytes) to the returning value.

        The function works for:
        BITMAPINFOHEADER, BITMAPV2INFOHEADER, BITMAPV3INFOHEADER, BITMAPV4INFOHEADER, BITMAPV5INFOHEADER

    References: http://fileformats.archiveteam.org/wiki/BMP
                https://github.com/drewnoakes/metadata-extractor/blob/master/Source/com/drew/metadata/bmp/BmpReader.java
                https://rpg.hamsterrepublic.com/source/wip/bitmap.bi

;;

Proc GetPixelPosFromBmpNfoHdr::
    Arguments @pBmi
    Uses esi, ecx

    xor eax eax
    mov esi D@pBmi
    ; Compute the offset to the array of color indices.
    mov ecx D$esi+BITMAPINFOHEADER.biSizeDis | On ecx = 0, ExitP
    mov eax D$esi+BITMAPINFOHEADER.biClrUsedDis
    ...If eax = 0
        ; Convert the color format to a count of bits.
        movzx eax W$esi+BITMAPINFOHEADER.biPlanesDis
        movzx ecx W$esi+BITMAPINFOHEADER.biBitCountDis
        imul eax ecx

        .If ax <= 16
            If ax <= 1
                mov eax 1 ; 1 shl 1 = 2
            Else_If ax <= 4
                mov eax 4 ; 1 shl 4 = 16
            Else_If ax <= 8
                mov eax 8 ; 1 shl 8 = 256
            Else
                mov eax 16 ; 1 shl 16 = 65536
            End_If
            mov ecx eax
            mov eax 1 | shl eax cl
        .Else
            xor eax eax
        .End_If
        mov ecx D$esi+BITMAPINFOHEADER.biSizeDis
    ...End_If

    ; The Size_Of_BITMAPFILEHEADER is only needed to be added below if the file contains a header. Calculations are: 40+2*4 48+14 = 3e => 40+16*4+14 = 076 ==> 40+256*4+14
    lea eax D$ecx+eax*4
    add eax esi

EndP

equates used on the function above. (Note: I implemented the equates and structures up to BITMAPV5INFOHEADER, but on this function it´s not needed to add extra equates, since the used members of the structure (biSize, biClrUsed, biPlanes and biBitCount) are located at the same offset)
Code: [Select]

[BITMAPINFOHEADER.biSizeDis 0
 BITMAPINFOHEADER.biWidthDis 4
 BITMAPINFOHEADER.biHeightDis 8
 BITMAPINFOHEADER.biPlanesDis 12
 BITMAPINFOHEADER.biBitCountDis 14
 BITMAPINFOHEADER.biCompressionDis 16
 BITMAPINFOHEADER.biSizeImageDis 20
 BITMAPINFOHEADER.biXPelsPerMeterDis 24
 BITMAPINFOHEADER.biYPelsPerMeterDis 28
 BITMAPINFOHEADER.biClrUsedDis 32
 BITMAPINFOHEADER.biClrImportantDis 36]

[Size_Of_BITMAPINFOHEADER 40]



I´m also analysing how those gdiplus scan and savings functions works internally from ReactOS and wine sources

https://doxygen.reactos.org/d4/d51/dll_2win32_2gdiplus_2image_8c_source.html#l04455
Title: Re: Bare Minimum GDIplus code to save different Image Types.
Post by: Siekmanski on October 03, 2020, 01:15:52 AM
Sources to load bitmap images from resource, from file and from the .data segment.
Copy bitmap data to memory so, you can manipulate the pixels and save it later in the format or type you want.