探究 LoadImage 内部的图像缩放函数
相关的例子:下载>>> 作者:MengXP 于2009-8-16上传 

 

探究 LoadImage 内部的图像缩放函数

最近研究界面编程涉及到了位图缩放。但发现系统提供的 StretchDIBits 缩放效果很不理想。但又研究发现 LoadImage 这个 API 内部实现了缩放功能。下面是 LoadImage 原型。

HANDLE LoadImage(
HINSTANCE hinst, // handle of the instance that contains the image
LPCTSTR lpszName, // name or identifier of image
UINT uType, // type of image
int cxDesired, // desired width
int cyDesired, // desired height
UINT fuLoad // load flags
);


设置此函数的 cxDesired cyDesired 参数实现缩放,效果较好。但是这个函数只能从文件或资源中获取位图。不方便用于已读入内存的位图。而 StretchDIBits 可以实现但是效果又不理想。这使我有了研究 LoadImage 内部的冲动…… -_-!

首先看了 ReactOS 中的 LoadImage,他对 BITMAP 类型的文件使用 LoadBitmapImage 处理。

case IMAGE_BITMAP:
return LoadBitmapImage(hinst, lpszName, fuLoad);

忽略了 cxDesired 和 cyDesired。显然这一点和 Windows 不同。我只好使用OD调试 Windows 的 user32.dll。

我假设 LoadImage 内部使用 StretchDIBits 进行缩放,对 StretchDIBits 下断。果然断了下来。但发觉函数的参数不对。

(原图像宽高均40,cxDesired,cyDesired设置为30)

0012F318 77D1CAB6 /CALL 到 StretchDIBits 来自 user32.77D1CAB0
0012F31C E8011199 |hDC = E8011199
0012F320 00000000 |XDest = 0
0012F324 00000000 |YDest = 0
0012F328 0000001E |WidthDest = 1E (30.)
0012F32C 0000001E |HeightDest = 1E (30.)
0012F330 00000000 |XSrc = 0
0012F334 00000000 |YSrc = 0
0012F338 0000001E |WidthSrc = 1E (30.)
0012F33C 0000001E |HeightSrc = 1E (30.)
0012F340 001493C8 |pBits = 001493C8
0012F344 00148FA0 |pBitmapinfo = 00148FA0
0012F348 00000000 |Usage = DIB_RGB_COLORS
0012F34C 00CC0020 \ROP = SRCCOPY

参数中源宽高和目标宽高是相同的,都是缩放后的30,明显在调用这个函数之前就已经处理好了位图数据。

位图缓冲区是 001493C8,重新调试了1次后发现这个地址不变。然后我对这个地址下了硬件访问断点以得到缩放位图数据的函数。运行,断在了下面的位置

77D21C35 52 push edx
77D21C36 03C8 add ecx, eax
77D21C38 FF71 03 push dword ptr [ecx+3]
77D21C3B FF31 push dword ptr [ecx]
77D21C3D FF70 03 push dword ptr [eax+3]
77D21C40 FF30 push dword ptr [eax]
77D21C42 E8 EFB7FFFF call 77D1D436 <<<< 插值算法,根据4个像素点求1个像素点
77D21C47 8BC8 mov ecx, eax
77D21C49 C1E9 10 shr ecx, 10
77D21C4C 880F mov byte ptr [edi], cl <<<<< EIP 断在这里
77D21C4E 47 inc edi
77D21C4F 8827 mov byte ptr [edi], ah
77D21C51 47 inc edi
77D21C52 8807 mov byte ptr [edi], al

用IDA分析这个函数,发现这个函数有很多参数 -_-! SO MANY...

int __stdcall sub_77D21BD0(int a1, int BufferSrc, int LineBytes, int a4, int a5, int a6, int a7, int BufferDst, int a9, int DestWidth, int DestHeight)

有一些参数懒得猜测其意,继续向上分析

交叉参考发现这个函数在一个函数表里……用OD返回到调用他的函数。发现是这样的

77D1D71B FF10 call dword ptr [eax] <<<< 是从这里进入 sub_77D21BD0
77D1D71D 834D FC FF or dword ptr [ebp-4], FFFFFFFF
77D1D721 33C0 xor eax, eax
77D1D723 40 inc eax
77D1D724 E8 D7AEFFFF call 77D18600
77D1D729 C2 1000 retn 10

retn 10 可知此函数有 4 个参数。执行到返回。来到了下面的位置

77D1D5DF 56 push esi
77D1D5E0 53 push ebx
77D1D5E1 FF75 2C push dword ptr [ebp+2C]
77D1D5E4 8943 04 mov dword ptr [ebx+4], eax
77D1D5E7 8B45 18 mov eax, dword ptr [ebp+18]
77D1D5EA FF75 30 push dword ptr [ebp+30]
77D1D5ED 8943 08 mov dword ptr [ebx+8], eax
77D1D5F0 66:8B45 FC mov ax, word ptr [ebp-4]
77D1D5F4 66:8943 0E mov word ptr [ebx+E], ax
77D1D5F8 E8 24000000 call 77D1D621
77D1D5FD 85C0 test eax, eax <<<< 返回到这里

对他的上一句 call 下断点重新运行程序。得到4个参数

0012F33C 00146C20
0012F340 00AD0036
0012F344 00146C70
0012F348 00147098

查看内存后我发现了这个函数的原型

int __stdcall sub_77D1D621(BITMAPINFO *SrcHdr, PVOID *lpSrcBits, BITMAPINFO *DstHdr, PVOID *lpDstBits)

这样。我们就可以使用 sub_77D1D621 这个内部函数缩放位图数据啦。

可是怎么使用呢。由于是user32内部函数,没有导出。在不同的操作系统这个函数的位置是不同的。我当初想过把这个函数抠出来独立实现,可是发现这个代码写的实在是有点搞……里面利用了一些函数表,而且一些代码乱乱的。不好移植,所以干脆就使用特征码的方法搜索吧! -_-!

查看了 win2000/xp/2003/vista 的 user32.dll
,发现除win2000的函数开头有些不一致,其他版本的win大体还是相同的!

77D1D621 6A 28 push 28
77D1D623 68 30D7D177 push 77D1D730
77D1D628 E8 93AFFFFF call 77D185C0
77D1D62D 33FF xor edi, edi
77D1D62F 8B4D 08 mov ecx, dword ptr [ebp+8]
77D1D632 3979 10 cmp dword ptr [ecx+10], edi

这是winxp sp3开头的6行代码。由于2、3行涉及到了变量我们使用后三行作为特征码

特征码为 33 FF 8B 4D 08 39 79 10 共8个字节。

下面是搜索函数

GetStretchBits Proc uses esi edi ebx
Local hUser32

invoke GetModuleHandle,CTEXT("user32.dll")
mov edi,eax

@@: .if dword ptr [edi] == 4d8bff33h && dword ptr [edi+4] == 10793908h
.if word ptr [edi-12] == 286Ah ;winxp/2003/vista
lea eax,[edi-12]
ret
.endif
.if dword ptr [edi-28h] == 6AEC8B55h ;win2000
lea eax,[edi-28h]
ret
.endif
mov eax,0 ;无法识别
ret
.endif
inc edi
jmp @b

ret
GetStretchBits EndP

对于 40x40 的一个QQ头像,缩放至20x20,与 Photoshop(两次立方平滑)/StretchDIBits 对比效果如下

;/////////////////////
;result.bmp 图像区域
;/////////////////////

(为了说明问题,该图片经过放大)

从左到右依次为

1.源图像
2.user32.LoadImage 内部函数处理
3.Photoshop(两次立方平滑)
4.StretchDIBits


下面我给出大致的测试代码片段
测试平台 LENOVO_XP_PRO_SP3_CHS_090416

;MASMPlus 代码模板 - 普通的 Windows 程序代码


.586
.Model Flat,StdCall
Option CaseMap :None

Include Windows.inc
Include User32.inc
Include Kernel32.inc
Include Gdi32.inc

IncludeLib User32.lib
IncludeLib Kernel32.lib
IncludeLib Gdi32.lib

CTEXT macro Text
local szText
.data
szText byte Text, 0
.code
exitm <offset szText>
endm

.Code
GetStretchBits Proc uses esi edi ebx
Local hUser32

invoke GetModuleHandle,CTEXT("user32.dll")
mov edi,eax

@@: .if dword ptr [edi] == 4d8bff33h && dword ptr [edi+4] == 10793908h
.if word ptr [edi-12] == 286Ah
lea eax,[edi-12]
ret
.endif
.if dword ptr [edi-28h] == 6AEC8B55h
lea eax,[edi-28h]
ret
.endif
mov eax,0
ret
.endif
inc edi
jmp @b

ret
GetStretchBits EndP

Start Proc
Local hFile,dwFileSize,dwRead
Local lpBI,lpBits
Local lpSrcBuffer,lpDstBuffer
Local bi:BITMAPINFOHEADER
Local hDC

invoke CreateFile,CTEXT("d:\result.bmp"),GENERIC_ALL,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 ;40x40 24Bit Bitmap
mov hFile,eax
invoke GetFileSize,hFile,0
mov dwFileSize,eax
invoke LocalAlloc,LPTR,65536
mov lpSrcBuffer,eax
invoke ReadFile,hFile,lpSrcBuffer,dwFileSize,addr dwRead,0 ;读入lpSrcBuffer
invoke CloseHandle,hFile

mov esi,lpSrcBuffer
add esi,[esi][BITMAPFILEHEADER.bfOffBits]
mov lpBits,esi ;定位位图数据区

mov esi,lpSrcBuffer
add esi,sizeof BITMAPFILEHEADER
mov lpBI,esi ;定位 源 BITMAPFILEHEADER

invoke RtlMoveMemory,addr bi,lpBI,sizeof BITMAPINFOHEADER ;复制一份 BITMAPFILEHEADER 当作 目标 BITMAPFILEHEADER
mov bi.biWidth,20 ;设置缩放宽
mov bi.biHeight,20 ;设置缩放高

invoke LocalAlloc,LPTR,65536 ;申请目标缓冲区
mov lpDstBuffer,eax

invoke GetStretchBits ;获取函数地址
.if eax
push lpDstBuffer
lea ecx,bi
push ecx
push lpBits
push lpBI
call eax ;调用之
.endif

invoke GetDC,0 ;获取屏幕 DC
mov hDC,eax

invoke StretchDIBits,hDC,0,0,20,20,0,0,20,20,lpDstBuffer,addr bi,DIB_RGB_COLORS,SRCCOPY ;画在屏幕 0,0 处

invoke ReleaseDC,0,hDC
invoke ExitProcess,0
Start EndP
End Start

 



欢迎访问AoGo汇编小站:http://www.aogosoft.com
下一篇>>>