# This program reads a 24-bit BMP file in to memory, fiddles with it, # and writes it back out in a new file. # # This program expects to be given at least two arguments. # arg 1: input file name root. The filename extension is assumed to be .bmp, # and should not be included on the command line. # arg 2: filter request, a positive integer 0..n # filter function # 0 copy from input to output with no change # 1 flip image top to bottom # 2 mirror image from right to left # arg 3: One or more additional filters can be specified after the # first one. They will be applied sequentially. # # The output file name is built up from the input file names root, the # list of filter numbers and the extension ".bmp". If the file already # exists, it will be overwritten. # .data filename: .space 256 # room for 255 characters and null-terminator fileextension: .asciiz ".bmp" strHyphen: .asciiz "-" strEOL: .asciiz "\n" msgUsageError: .asciiz "\nUsage: []\n" msgFileOpenError: .asciiz "File open error " msgFileFormatError: .asciiz "File format error " statusBMPHeader: .asciiz "Image data byte count: " statusReadingFile: .asciiz "Reading file " statusApplyingFilter: .asciiz "Applying filter " statusWritingFile: .asciiz "Writing file " .align 2 header: .space 54 # space for the 54-byte BMP file header .text main: subu $sp,$sp,56 # create stack frame sw $ra,48($sp) # save registers sw $s7,44($sp) # sw $s6,40($sp) # sw $s5,36($sp) # sw $s4,32($sp) # sw $s3,28($sp) # sw $s2,24($sp) # sw $s1,20($sp) # sw $s0,16($sp) # blt $a0,2,usageError # must have file name and at least one filter number move $s4,$a0 # get number of args sub $s4,1 # $s4 = number of filters left to go move $s0,$a1 # $s0 = address(argv[0]) la $s3,filename # $s3 = address(file name buffer) # prepare to open input file move $a0,$s3 # $a0 = address(file name buffer) sb $zero,0($a0) # null terminator at start of buffer lw $a1,0($s0) # $a1 = address(file name root) jal strcat # copy the file name root to the buffer move $a0,$v0 # recover address(file name buffer) la $a1,fileextension # address(file name extension) jal strcat # append the extension # tell the user what we are about to do la $a0,statusReadingFile # description li $v0,4 # print_str syscall # move $a0,$s3 # $a0 = address(file name buffer) li $v0,4 # print_str syscall # la $a0,strEOL # new line li $v0,4 # print_str syscall # # open input file move $a0,$s3 # address(file name buffer) li $a1,0x8000 # _O_BINARY file mode li $a2,0 # no perms needed li $v0,11 # open input file syscall # bltz $v0,fileOpenError # exit if error move $s1,$v0 # remember input file handle # read the BMP formatted header move $a0,$s1 # input file handle la $a1,header # header buffer jal readBMPHeader # get the header info move $s2,$v0 # remember byte count # tell the people what we saw la $a0,statusBMPHeader # description li $v0,4 # print_str syscall move $a0,$s2 # required byte count li $v0,1 # print_int syscall la $a0,strEOL # new line li $v0,4 # print_str syscall blez $s2, fileFormatError # exit if error # allocate the needed memory move $a0,$s2 # required byte count li $v0,9 # sbrk syscall # move $s5,$v0 # $s5 = address(input buffer) move $a0,$s2 # required byte count li $v0,9 # sbrk syscall # move $s6,$v0 # $s6 = address(output buffer) # read the image data move $a0,$s1 # input file handle move $a1,$s5 # input buffer address move $a2,$s2 # byte count to read li $v0,12 # _read syscall # close the input file move $a0,$s1 # input file handle li $v0,14 # close file syscall # show the bottom left pixel move $a0,$s5 jal printPixel # initialize the first part of the output file name move $a0,$s3 # $a0 = address(file name buffer) sb $zero,0($a0) # null terminator at start of buffer lw $a1,0($s0) # $a1 = address(file name root) jal strcat # copy the file name root to the buffer addiu $s0,$s0,4 # $s0 = address(argv[1]) # apply each filter argument in turn mainFilterLoop: # tell the user about each filter as we get to it la $a0,statusApplyingFilter # description li $v0,4 # print_str syscall # lw $a0,0($s0) # address(filter number string) li $v0,4 # print_str syscall # li $v0,4 # print_str la $a0,strEOL # new line syscall # # append the filter number to the output file name move $a0,$s3 # $a0 = address(file name buffer) la $a1,strHyphen # a separator character jal strcat # append move $a0,$v0 # recover address(filename) lw $a1,0($s0) # address(filter number string) jal strcat # append # decode the filter number lw $a0,0($s0) # address(filter number string) jal decode_int # move $s7,$v0 # remember the filter number # filter from input to output move $a0,$s7 # filter number la $a1,header # header array move $a2,$s5 # input image data move $a3,$s6 # output image data jal dispatchFilterRequest # show the bottom left pixel move $a0,$s6 jal printPixel # swap buffers move $t0,$s5 # temp = move $s5,$s6 # new = old move $s6,$t0 # new = temp # loop if more to do sub $s4,$s4,1 # decrement number of filters to go add $s0,$s0,4 # point to next arg string address bnez $s4, mainFilterLoop # All filters have been applied. Write out the result ... # add the ".bmp" extension to the file name move $a0,$s3 # $a0 = address(file name buffer) la $a1,fileextension # address(file name extension) jal strcat # append the extension # tell the user what we are about to do la $a0,statusWritingFile # description li $v0,4 # print_str syscall # move $a0,$s3 # $a0 = address(file name buffer) li $v0,4 # print_str syscall # la $a0,strEOL # new line li $v0,4 # print_str syscall # # open output file move $a0,$s3 # $a0 = address(file name buffer) li $a1,0x8302 # _O_BINARY | _O_TRUNC | _O_CREAT | _O_RDWR li $a2,0x180 # _S_IREAD | _S_IWRITE li $v0,11 # open output file syscall bltz $v0,fileOpenError # exit if error move $s1,$v0 # remember output file handle # write the output file move $a0,$s1 # output handle la $a1,header # header address li $a2,54 # length of header li $v0,13 # write file syscall # move $a0,$s1 # output handle move $a1,$s5 # buffer address (remember, buffers were swapped) move $a2,$s2 # length of buffer li $v0,13 # write file syscall # # close the output file move $a0,$s1 # file handle li $v0,14 # close file syscall allDone: lw $ra,48($sp) # restore registers lw $s7,44($sp) # lw $s6,40($sp) # lw $s5,36($sp) # lw $s4,32($sp) # lw $s3,28($sp) # lw $s2,24($sp) # lw $s1,20($sp) # lw $s0,16($sp) # addu $sp,$sp,56 # release stack frame j $ra usageError: la $a0,msgUsageError # usage error li $v0,4 # print_str syscall # j allDone # fileOpenError: move $s1,$v0 # remember the error code for just a second la $a0,msgFileOpenError # error message li $v0,4 # print_str syscall # move $a0,$s1 # the error code li $v0,1 # print_int syscall la $a0,strEOL # new line li $v0,4 # print_str syscall # j allDone # fileFormatError: la $a0,msgFileFormatError # error message li $v0,4 # print_str syscall # la $a0,strEOL # new line li $v0,4 # print_str syscall # move $a0,$s1 # input file handle li $v0,14 # close file syscall j allDone # # ----------------------------------------------------------------------- # Dispatch a filter request to the proper procedure. # All the procedures take the same list of arguments as this # dispatcher does. # When you add filter procedures, you need to update the # filterJumpTable to include them, and also update the variable # filterJumpTableSize to reflect the added filters. # a0 - filter request code # a1 - 54-byte BMP file header array # a2 - word aligned input image data buffer # a3 - word aligned output image data buffer .data filterJumpTable: # table of filter addresses .word copyFilter # ,flipFilter,mirrorFilter # CHANGE THIS filterJumpTableSize: # number of addresses in above table .word 1 # <-- CHANGE THIS when you add more filters filterRequestErrorMessage: # no such filter .asciiz " : No such filter.\n" .text dispatchFilterRequest: subu $sp,$sp,24 # create stack frame sw $ra,16($sp) # save registers andi $t0,$a0,0xFF # only use low order byte for filter number lw $t1,filterJumpTableSize # number of addresses in jump table bge $t0,$t1,filterRequestError # no such filter la $t1,filterJumpTable # base address of table sll $t0,$t0,2 # byte offset for filter add $t1,$t0,$t1 # address in jump table lw $t1,0($t1) # jump address jalr $t1 # dispatch filterRequestExit: lw $ra,16($sp) # restore registers addu $sp,$sp,24 # release stack frame j $ra # return filterRequestError: move $a0,$t0 # filter number li $v0,1 # print_int syscall # la $a0,filterRequestErrorMessage # li $v0,4 # print_string syscall # j filterRequestExit # # ----------------------------------------------------------------------- # Copy Filter => copy image from input to output unchanged # a0 - filter request code # a1 - 54-byte BMP file header array # a2 - word aligned input image data buffer # a3 - word aligned output image data buffer # Note that the number of bytes in the image data is a multiple # of 4 because each line of pixel triplets is padded out to a # full word if necessary. Therefore this filter can get away with # copying full words and ignoring how the pixels are stored # individually. However, if you are copying individual pixels from # one buffer to the other, you need to pay careful attention to how # the bytes are arranged in each row of the image storage. copyFilter: ulw $t0,34($a1) # t0 = biSizeImage (# of image bytes) add $t1,$a2,$t0 # t1 = address of first byte after input buffer copyFilterLoop: lw $t2,0($a2) # get input word sw $t2,0($a3) # store output word addi $a2,4 # point to next input word addi $a3,4 # point to next output word bne $a2,$t1,copyFilterLoop # loop if needed j $ra # all done # ----------------------------------------------------------------------- # Flip Filter => flip image top to bottom # ----------------------------------------------------------------------- # Mirror Filter => mirror image side to side # ----------------------------------------------------------------------- # Other Filter => whatever you pick # ----------------------------------------------------------------------- # Read BMP header and validate certain fields. # This function only handles uncompressed 24-bit images # a0 handle of open file # a1 address of word-aligned 54-byte array for BITMAPFILEHEADER and # BITMAPINFOHEADER. The first 54 bytes of the file are read into # this array and so are available to the caller after return. # # v0 -1 => not a 24-bit BMP file, else => the number of bytes # of image data in this file. Can be used for allocating more memory. # Note: Since it is legal for biSizeImage to be 0 for 24-bit images, # the returned value is actually calculated in this function. # After calculation, the value is stored in the header in # field biSizeImage as well as returned in v0. .data typeBMP: .ascii "BM" .text readBMPHeader: # $a0 = input file handle # $a1 = address(header) li $a2,54 # $a2 = header is 54 bytes long li $v0,12 # _read syscall bne $v0,54,notBMP # must have complete header # check BITMAPFILEHEADER lhu $t0,typeBMP # the desired tag lhu $t1,0($a1) # the actual tag bne $t0,$t1,notBMP # must start with 'BM' # check BITMAPINFOHEADER lhu $t0,28($a1) # biBitCount bne $t0,24,notBMP # must be 24 lhu $t0,30($a1) # biCompression bnez $t0,notBMP # must be BI_RGB (=0) # calculate image byte count # bytes_per_line = ((((3*image_width)-1)/4)+1)*4 # byte count = image_height * bytes_per_line ulw $t0,18($a1) # biWidth li $t1,3 # 3 bytes per pixel mul $t0,$t0,$t1 # content bytes = n sub $t0,$t0,1 # 1..n => 0..n-1 srl $t0,$t0,2 # words used 0..m-1 addi $t0,$t0,1 # words used 1..m sll $t0,$t0,2 # bytes used 4..4m ulw $t1,22($a1) # biHeight mul $v0,$t0,$t1 # total number of bytes usw $v0,34($a1) # biSizeImage j $ra # return notBMP: sub $v0,$zero,1 # format error j $ra # return # --------------------------------------------------------------------------- # Converts an integer value from a textual representation to its machine representation # Requires: # a0 the address of the start of the textual representation # Must be a null terminated string. # Returns: # v0 the machine representation of the integer value # -1 => decode error # Uses: # t0 temp product # t1 temp product # t2 next char # t3 value of next char # t4 target of slt # t5 0=>decimal, 1=>hex # t6 number base, 10 or 16 decode_int: move $t5,$zero # (t5==0) => base 10 li $t6, 10 # base 10 multiplier li $t0,48 # t0 = '0' lbu $t1,0($a0) # t1 = 1st char bne $t0,$t1,decodeStart # not hex li $t0,88 # t0 = 'X' lbu $t1,1($a0) # t1 = 2nd char andi $t1,$t1,0xDF # force upper case bne $t0,$t1,decodeStart # not hex addi $t5,1 # (t5==1) => base 16 li $t6,16 # base 16 multiplier addi $a0,2 # skip over the '0x' decodeStart: move $v0,$zero # cumulative decoded value = 0 lbu $t2,0($a0) # t2 = c = next char beqz $t2,decodeDone # quit if char is null decodeLoop: addi $t3,$t2,-48 # t3 = val = c - '0' bltz $t3,decodeError # if (val < 0) error slt $t4,$t3,10 # t4 = (val<10) bnez $t4,decodeUseIt # if (val < 10) add it to cumulative value beqz $t5,decodeError # if (base 10), error andi $t3,$t2,0xDF # force upper case addi $t3,$t3,-65 # t3 = val = c - 'A' bltz $t3,decodeError # if (val < 0) error slti $t4,$t3,6 # t4 = (val<6) beqz $t4,decodeError # if (val >= 6) error addi $t3,10 # add in the value of 0xA decodeUseIt: mul $v0,$v0,$t6 # v0 = v0*base add $v0,$v0,$t3 # v0 = v0+val addi $a0,1 # a0++ lbu $t2,0($a0) # t2 = next char bnez $t2,decodeLoop # loop if char is non-null decodeDone: jr $ra # return decodeError: addi $v0,$zero,-1 # decoding error jr $ra # return # --------------------------------------------------------------------------- # Print three values in RGB order, given the address of a BGR triplet. # $a0 address of the first of three bytes in BGR order .data pixelSpacer: .asciiz " " pixelEOL: .ascii "\n" .text printPixel: addi $t0,$a0,2 # get byte address of red value lbu $a0,0($t0) # Red value li $v0,1 # print_int syscall la $a0,pixelSpacer # space li $v0,4 # print_str syscall addi $t0,$t0,-1 # previous value lbu $a0,0($t0) # Green li $v0,1 # print_int syscall la $a0,pixelSpacer # space li $v0,4 # print_str syscall addi $t0,$t0,-1 # previous value lbu $a0,0($t0) # Blue li $v0,1 # print_int syscall la $a0,pixelEOL # new line li $v0,4 # print_str syscall j $ra # ----------------------------------------------------------------------- # char *strcat(char *s1, const char *s2) # # Requires # # $a0 address of s1, the base string and result buffer. The buffer # MUST be long enough to hold BOTH s1 and s2, including a terminating # null-byte. # # $a1 address of string s2 to append on the end of s1. # # Neither string is required to be aligned. No length checking # or reasonableness checking is done in any way. # # Returns # # $v0 the result address s1 # .text strcat: move $v1,$a0 # remember string s1 lb $v0,0($v1) # get first byte of s1 beqz $v0,scCopy # if null, go copy s2 addu $a0,$a0,1 # point to next character scFind: lb $v0,0($a0) # load next byte of s1 addu $a0,$a0,1 # point to next character bnez $v0,scFind # done looking if null addu $a0,$a0,-1 # back up to overwrite the null scCopy: lbu $v0,0($a1) # get first byte of s2 sb $v0,0($a0) # store it at the end of s1 sll $v0,$v0,24 # clear the top of the register addu $a1,$a1,1 # point to next byte addu $a0,$a0,1 # point to next byte bnez $v0,scCopy # done copying if null move $v0,$v1 # return string address s1 j $ra # return