使用程序集制作屏幕截图

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 的日期。你不应该检索它两次。使用一次通话中的信息。

【讨论】:

非常感谢!!你帮了我很多,在修复了你所说的之后它终于奏效了!!

以上是关于使用程序集制作屏幕截图的主要内容,如果未能解决你的问题,请参考以下文章

如何使用c ++为最小尺寸的窗口制作屏幕截图

iOS:以编程方式制作屏幕截图的最快、最高效的方法是啥?

如何在 iOS 中从 MTKView 制作屏幕截图?

从屏幕截图中分类程序

iOS制作整个屏幕问题的截图

iOS - 以编程方式截取跳板(主屏幕)的屏幕截图