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.
Hi Siekmanski,
Your example works fine on Windows XP :thumbsup:
Cool, thanks. :thumbsup:
Gratsie, works fine on my Win10 64. This will be very useful. :thumbsup:
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 ?
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
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:
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.
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:
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
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
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:
Bare Minimum GDIplus code to save different Image Types and PixelFormat conversions.
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
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
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
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
I gave it a blast but no change, maybe the 64 bit version of GDIP is broken somewhere.
deleted
deleted
I think the JPG quality encoder parameters for 64 bit are organized in an other way as in 32 bit code?
deleted
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.
deleted
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
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
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
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
Great, it works well, I have tested it from 10% to 75% and the results are perfect.
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.
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.
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
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.
@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......
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 :
\PellesC\lib\Win64\gdiplus.lib
Nice that it works.
Still have to find my way in 64 bit coding, maybe the code can be improved?
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:
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
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
: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:
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?
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.
Thanks, if I need them I'll let you know. :thumbsup:
Marinus,
Have a look at this link for cursor information.
https://en.wikipedia.org/wiki/ICO_(file_format)
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
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.
Marinus,
Would using the following be better to allow for larger sizes ?
> mov hIcon, rv(LoadImage,hInstance,10,IMAGE_ICON,64,64,LR_DEFAULTCOLOR)
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.
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.
Hi Siekmanski,
You could try Pelle's resource compiler porc.exe
Mine is the same version as yours, 2015. I have just picked out the vs2019 version and attached it.
Thanks guys, I will test it over a few hours. :thumbsup:
Hello,
There is also Jeremy Gordon's resource compiler GoRC :
http://www.godevtool.com/
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.
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
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.
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
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.
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)
Probably the pImage pointer to the data is not valid?
maybe, but how to validated it ?
It do export a file with some bitmap header on it, but the data is corrupted
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.
Have a look at these sources, it does what you need.
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();
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.
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 ?
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.
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
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.
If it still doesn't work, I can write a simple example tomorrow, if you want?
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:
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
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
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.
Icon and GDI plus
perhaps :
Quote
GpStatus WINGDIPAPI GdipCreateBitmapFromHICON(HICON hicon, GpBitmap** bitmap)
GpStatus WINGDIPAPI GdipCreateHICONFromBitmap(GpBitmap* bitmap, HICON* hbmReturn)
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
;;
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
;;
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
;;
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 :)
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 ?
QuoteI 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
QuoteOne 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.
Quote from: Siekmanski on September 22, 2020, 09:24:08 AM
QuoteI 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 ?
Quote from: Siekmanski on September 22, 2020, 09:24:08 AM
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:
;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
Quote from: guga on September 22, 2020, 02:03:11 PMI´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
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
QuoteFor 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:
;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 )
; 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
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:
;;
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)
[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
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.