使用程序集制作屏幕截图
Posted
技术标签:
【中文标题】使用程序集制作屏幕截图【英文标题】:Making a screenshot using assembly 【发布时间】:2020-05-16 15:21:50 【问题描述】:我正在从事一个装配项目。我想添加一个选项来制作屏幕截图。我将图片保存为 BMP 格式的 256 色位图。我在 320*200 图形模式下工作(在 DOSBox 中使用 16 位汇编)。 我已经为此工作了很长时间,在我的代码中找不到问题。 我正在做的只是创建一个文件(没有问题),添加标题(我正在处理 BMP 格式),将调色板复制到文件中(我正在切换红色和绿色,因为 BMP 是用 BGR 而不是 RGB 编写的)然后直接从视频内存中复制像素(我是倒着做的,因为BMP文件是倒着写的)。
我已经尝试了很长时间来解决它,但我找不到我的错误。如果有人能找到我的错误,请告诉我。 非常感谢。
proc MakeScreenshot
pusha
mov bp, sp
sub sp, 2
@MakeScreenshot@lineCounter equ [word ptr bp - 2]
; creating the file
; finding the last var in the arr
push offset ScreenShotPath
push 1000
push 0
call find
cmp [index], -1
jne @MakeScreenshot@cont6
call PError
@MakeScreenshot@cont6:
mov bl, [index]
xor bh, bh
add bx, offset ScreenShotPath
sub bx, 6
mov di, bx
; get the time
; CH = hour (0-23)
; CL = minutes (0-59)
; DH = seconds (0-59)
mov ah, 2ch
int 21h
cmp dx, [SecAndMin]
jne @MakeScreenshot@cont
inc [byte ptr di + 1]
jmp @MakeScreenshot@creat
@MakeScreenshot@cont:
mov [SecAndMin], dx
mov dl, dh
xor dh, dh
mov ax, dx
cmp ax, 10
jae @MakeScreenshot@Div4
mov [byte ptr di], '0'
inc di
add al, '0'
mov [byte ptr di], al
sub di, 4
jmp @MakeScreenshot@file
@MakeScreenshot@Div4:
; Divide the number to digits in the stack
xor si, si ; Si counts the number of digits
mov bl, 10
@MakeScreenshot@dig5:
div bl ; Divide ax by 10
mov cl, ah
xor ch, ch
push cx ; Save the digit in the stack
xor ah, ah
inc si
cmp ax, 0
jne @MakeScreenshot@dig5
mov cx, si
mov si, di
@MakeScreenshot@Sec:
pop ax
xor ah, ah
add al, '0'
mov [si], al
inc si
loop @MakeScreenshot@Sec
sub di, 3
@MakeScreenshot@file:
; get the time
; CH = hour (0-23)
; CL = minutes (0-59)
; DH = seconds (0-59)
mov ah, 2ch
int 21h
xor ch, ch
mov ax, cx
cmp ax, 10
jae @MakeScreenshot@Div5
inc di
mov [byte ptr di], '0'
inc di
add al, '0'
mov [byte ptr di], al
inc di
jmp @MakeScreenshot@creat
@MakeScreenshot@Div5:
; Divide the number to digits in the stack
xor si, si ; Si counts the number of digits
mov bl, 10
@MakeScreenshot@dig6:
div bl ; Divide ax by 10
mov cl, ah
xor ch, ch
push cx ; Save the digit in the stack
xor ah, ah
inc si
cmp ax, 0
jne @MakeScreenshot@dig6
mov cx, si
inc di
mov si, di
@MakeScreenshot@Min:
pop ax
xor ah, ah
add al, '0'
mov [byte ptr si], al
inc si
loop @MakeScreenshot@Min
@MakeScreenshot@creat:
push offset ScreenShotPath
push offset ScreenShotHandle
call CreateFile
; write the header
mov ah, 40h
mov bx, [ScreenShotHandle] ; file handle
mov cx, 54 ; number of bytes to write
mov dx, offset Fileheader ; pointer to write buffer (the header is the same and we take a picture omly after the main page so i will not be empty)
int 21h
jnc @MakeScreenshot@Cont20
mov ah, 59h
mov bx, 0
int 21h
push ax
call PrintFileError
@MakeScreenshot@Cont20:
; write the pallete
push offset ScreenShotPalette
call SavePalette
mov cx, 256
mov bx, offset ScreenShotPalette
xor di, di
@MakeScreenshot@copyPal:
; Note: The palette of BMP files are BGR and not RGB so it has to be upsidedown.
; Copy last/first color (blue)
mov al, [bx + 2] ; Get the last color of the palette to the first color (blue).
shl al, 2 ; The BMP palette is larger. ThereFore multyplayer by 4 in order ro corp it to the right size.
mov [byte ptr ScreenShotPalette2 + di], al
inc di
; Do the same on the second color (green)
mov al, [bx + 1]
shl al, 2
mov [byte ptr ScreenShotPalette2 + di], al
inc di
; Do the same on the first/last color (red)
mov al, [bx]
shl al, 2
mov [byte ptr ScreenShotPalette2 + di], al
inc di
; enter an empty byte
mov [byte ptr ScreenShotPalette2 + di], 0
inc di
add bx, 4 ; Jump to the next color (each color is a double word (4 bytes))
loop @MakeScreenshot@copyPal ; It has to be 256 time becuase there are 256 colors im the palette.
mov ah, 40h
mov bx, [ScreenShotHandle] ; file handle
mov cx, 256 ; number of bytes to write
mov dx, offset ScreenShotPalette2 ; pointer to write buffer
int 21h
jnc @MakeScreenshot@Cont2
mov ah, 59h
mov bx, 0
int 21h
push ax
call PrintFileError
@MakeScreenshot@Cont2:
; Copy the image
; Declare the address of the video memory
mov ax, 0A000h ; The video memory is stored on A000:0000
mov es, ax
; Copy
mov cx, 200 ; 200 lines
mov @MakeScreenshot@lineCounter, 0 ; Reset the line counter
@MakeScreenshot@copyImage:
push cx ; Saves the cx for the big loop
; Copy one line
; Declare where to copy from
mov di, offset ScrenshotLine
; Declare where start to write
mov si, @MakeScreenshot@lineCounter
mov ax, 320 ; We start to write a line on A000:lineNumber*320
mul si ; Saves on dx:ax
mov si, ax ; The max outcome fits into one word
; Note: si now point where to write if the pic was not upside down
; Make it
mov ax, 63679 ; Ax = ((320 * 200) - 1) - 320 which is the start of the last line of the video memory
sub ax, si ; Ax now poit to the start of the oppesite line
mov si, ax
mov cx, 320
@MakeScreenshot@copyLine:
; Copy from the stored line into the video memory
mov al, [es:si]
mov [ds:di], al ; Copy one byte from the stored line to the vide memory
inc si
inc di
loop @MakeScreenshot@copyLine
; copy the line to the file
mov ah, 40h
mov bx, [ScreenShotHandle] ; file handle
mov cx, 320 ; number of bytes to write
mov dx, offset ScreenShotPalette2 ; pointer to write buffer
int 21h
jnc @AddToLog@Cont3
mov ah, 59h
mov bx, 0
int 21h
push ax
call PrintFileError
@AddToLog@Cont3:
inc @MakeScreenshot@lineCounter
pop cx ; Return the value to cx
loop @MakeScreenshot@copyImage
; close the file
push [ScreenShotHandle]
call closeFile
add sp, 2 ; Delete local var
popa
ret
endp MakeScreenshot
【问题讨论】:
【参考方案1】:push offset ScreenShotPalette call SavePalette mov cx, 256 mov bx, offset ScreenShotPalette xor di, di @MakeScreenshot@copyPal:
SavePalette 的代码未显示,但如果它使用视频 Bios 函数 ReadBlockOfColorRegisters (AX=1017h
),则 ScreenShotPalette 处的缓冲区将容纳 768 字节(256 x 3)。如果是这样,你的add bx, 4
应该变成add bx, 3
。
mov ah, 40h mov bx, [ScreenShotHandle] ; file handle mov cx, 256 ; number of bytes to write mov dx, offset ScreenShotPalette2 ; pointer to write buffer int 21h
调色板为 1024 字节 (256 x 4)。你只写了 256!
您的 copyImage 代码有很多误导性的 cmets!我猜这段代码最初是为了将图像写入屏幕而不是从屏幕读取。无论如何,您将像素存储在 ScrenshotLine (mov di, offset ScrenshotLine
) 但要在 BMP 文件中写入这些像素,则使用完全不同的地址 ScreenShotPalette2 (mov dx, offset ScreenShotPalette2
)。
屏幕上的地址计算错误。 63679 不是屏幕上最后一行的地址。那将是 63680。
更好的消息是您不需要使用该局部变量@MakeScreenshot@lineCounter。您可以将CX
计数器用于相同目的,并避免那种困难的图片反转操作。
; Copy
mov cx, 200 ; 200 lines
@MakeScreenshot@copyImage:
push cx
dec cx ; Current line 199, 198, 197, ...
; Copy one line
mov di, offset ScrenshotLine
mov ax, 320 \
mul cx | These 3 together in 1 using "imul si, cx, 320"
mov si, ax /
mov cx, 320
@MakeScreenshot@copyLine:
mov al, [es:si]
mov [ds:di], al
inc si
inc di
loop @MakeScreenshot@copyLine
; copy the line to the file
mov ah, 40h
mov bx, [ScreenShotHandle]
mov cx, 320
mov dx, offset ScrenshotLine
int 21h
...
pop cx
loop @MakeScreenshot@copyImage
关于 DOS.function 2Ch 的日期。你不应该检索它两次。使用一次通话中的信息。
【讨论】:
非常感谢!!你帮了我很多,在修复了你所说的之后它终于奏效了!!以上是关于使用程序集制作屏幕截图的主要内容,如果未能解决你的问题,请参考以下文章