Разработка программы для просмотра bmp файлов

Тип работы:
Курсовая
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ

Федеральное государственное бюджетное образовательное учреждение

высшего профессионального образования

«Чувашский государственный университет имени И.Н. Ульянова»

Факультет дизайна и компьютерных технологий

Кафедра компьютерных технологий

КУРСОВОЙ ПРОЕКТ

по дисциплине «Система программного обеспечение»

на тему «Разработка программа для просмотра *. bmp файлов»

Выполнил: студент гр. ДиКТ 21−10

Киселёв Р.С.

Проверил: ст. пр. Харитонова А. М.

Чебоксары 2012

Содержание

Введение

1. Теория

2. Блок-схема

3. Код программы

4. Результат работы программы

Заключение

Список используемых источников

Введение

Не смотря на то, что язык программирования Ассемблера, в чистом виде, относительно редко используется на практике, его изучение является необходимой частью подготовки профессиональных программистов, поскольку позволяет шире понять принципы работы ЭВМ, операционных систем и трансляторов с языков высокого уровня.

1. Теория

Assembler — это символическое представление машинного языка, то есть каждой машинной команде соответствует команда ассемблера и наоборот.

Программируя на ассемблере, прежде всего надо помнить, что Вы даёте команды процессору, а для процессора любое устройство является внешним. Понимание этого поможет понять логику программ. Переход с языка высокого уровня (ЯВУ) на ассемблер обычно происходит со скрипом. Первое время достаточно болезнено воспринимается отсутствие оператора print/write/printf/echo, но потом чувствуешь себя хозяином положения и ощущаешь полную власть над компьютером. Нет неграмотных, медленнодействующих операторов, нет таинственных модулей. Всё открыто. В качестве примера совершенно неграмотного модуля можно вспомнить egavga. bgi в Паскале, который вызывает прерывание для рисования каждой отдельной точки.

Конечно при программировании на ассемблере ощущаются и свои недостатки. Главный из них — трудная программируемость некоторых устройств.

Основные команды ассемблера:

mov эквивалент оператора присваивания в ЯВУ. Принимает строго 2 операнда: куда и что. Например, если мы напишем mov ax, bx — в регистр ax запишется значение, хранящееся в регистре bx (ax=bx).

add арифметическое сложение. Принимает 2 операнда: к чему и сколько. Пример: add ax, bx выполняет ax = ax + bx.

sub вычитание. синтаксис аналогичен add.

shl сдвиг влево на заданное количество бит. При этом старшие биты теряются (кроме последнего, он записывается в флаг CF регистра флагов).

shr сдвиг вправо аналогично shl.

jmp безусловный переход. Операнд — адрес перехода (вместо адреса можно использовать метку, которая при трансляции заменится на соответствующий адрес).

and/or/xor/not логические операции: конъюнкция, дизъюнкция, исключающее или (сложение по модулю 2), инверсия.

inc инкремент (увеличение операнда на 1).

dec декремент (уменьшение оперенда на 1).

call перейти к подпрограмме. Операнд — адрес начала подпрограммы.

ret вернуться из подпрограммы.

int вызвать прерывание. Операнд — номер прерывания.

push поместить операнд в стек.

pop извлечь операнд из стека.

in принять байт от устройства. in al, 60h принимает байт в регистр al от регистра данных контроллера клавиатуры.

out послать байт устройству. mov al, 0EDh out 60h, al mov al, 111b out 60h, al — эта последовательность из четырёх коменд зажигает все светодиоды клавиатуры.

nop no operation — команда ничего не делает (и, как ни странно, имеет применение).

loop команда делает переход на указанную позицию и уменьшает значение CX. Если CX=0, то происходит выход из цикла.

jcc группа команд, осуществляющих переход на указанную позицию, если выполнено условие. Например, команда jz передаёт управление в случае, если флаг zf (zero flag) установлен в «1». Этот флаг устанавливается в «1», если после выполнения какой-либо операции операнд становится равным 0 (независимо от его значения до выполнения операции).

cmp сравнить. Команда не выполняет каких-либо «видимых» действий. Она просто устанавливает флаги в соответствии с результатом сравнения. После этой команды обычно следует оператор группы jcc. Рассмотрим основные (o1, o2 — операнды команды cmp):

jz zf=1

je o1=o2

jc cf=1

jb, jnae o1 < o2, без знака

ja, jnbe o1 > 02, без знака

jnb, jae o1 >= o2, без знака

jbe, jna o1 <= o2, боз знака

jl, jnge o1 < o2, со знаком

jg, jnle o1 > o2, со знаком

jnl, jge o1 <= o2, со знаком

jle, jng o1 >= o2, со знаком

lodsb загрузить байт по адресу ds: si в регистр al.

lodsw загрузить слово (2 байла) по адресу ds: si в регистр ax.

lodsd загрузить двойное слово (4 байта) по адресу ds: si в регистр eax.

stosb послать байт по адресу es: di из регистра al.

stosw послать слово (2 байла) по адресу es: di из регистра ax.

stosd послать двойное слово (4 байта) по адресу es: di из регистра eax.

Команды обмена данными: MOV, XCHG.

MOV приемник, источник

Копирует байт (byte)/слово (word)/двойное слово (dword) из операнда источник в операнд приемник. Позволяет копировать данные из одного регистра общего назначения (РОН) в другой, из РОН в память, из памяти в РОН. Командой MOV нельзя напрямую переслать данные из одной области памяти в другую -- для этого придется использовать в качестве посредника один из РОН. Копирование данных в таком случае осуществляется за две команды: сначала из исходной области памяти в РОН, а затем -- из РОН в целевую область памяти. При помощи команды MOV также можно помещать в регистр или память непосредственное значение, копировать содержимое сегментного регистра в РОН или память, содержимое РОН или памяти в сегментный регистр, содержимое регистра управления или отладки в РОН, содержимое РОН в регистр управления или регистр отладки. Команда MOV может быть обработана компилятором только если размеры источника и приемника совпадают. Ниже следуют примеры возможных вариантов использования команды MOV:

mov bx, ax; копировать содержимое РОН в РОН

mov [char], al; РОН в память

mov bl,[char]; память в РОН

mov dl, 32; значение в РОН

mov [char], 32; значение в память

mov ax, ds; сегментный регистр в РОН

mov [bx], ds; сегментный регистр в память

mov ds, ax; РОН в сегментный регистр

mov ds,[bx]; память в сегментный регистр

mov eax, cr0; регистр управления в РОН

mov cr3, ebx; РОН в регистр управления

XCHG операнд1, операнд2

Используется для двунаправленной пересылки данных между операндами. Размер обоих операндов может быть байтом, словом или двойным словом, но оба операнда должны быть одинакового размера. Команда XCHG помещает содержимое первого операнда во второй, а второго -- в первый. Один из операндов всегда должен быть РОН, а другой может быть областью памяти или также РОН.

xchg ax, bx; обменять содержимое РОН и РОН

xchg al,[char]; обменять содержимое РОН и памяти

Обмен данными через стек: PUSH, POP.

Стек -- область памяти, специально выделяемая каждой программе для временного хранения промежуточных данных. Обычно адреса в памяти растут от ноля к максимальному адресу. В стеке все наоборот: он растет от дна (максимальный адрес сегмента стека) к нолю. Для того, чтобы поместить данные в стек, применяется команда PUSH, ее синтаксис следующий:

PUSH источник

Эта команда уменьшает указатель на текущий кадр стека (регистр ESP) и копирует содержимое операнда-источника по адресу вершины стека, содержащемуся в ESP. В роли операнда может выступать РОН, память, сегментный регистр, значение размером в слово или двойное слово. Если в качестве операнда указано непосредственное значение, то в 16-битном режиме оно по умолчанию воспринимается компилятором как слово, а в 32-битном -- как двойное слово. Мнемоники PUSHW и PUSHD указывают компилятору, что значение необходимо сохранить как слово или как двойное слово соответственно независимо от режима, в котором работает компилятор. Если за командой PUSH следуют несколько операндов, разделенных пробелами, то они будут обработаны компилятором как последовательность из нескольких команд PUSH с этими операндами по отдельности. PUSHA сохраняет в стеке содержимое всех восьми регистров общего назначения. Примеры использования команды PUSH:

push ax; сохранить РОН

push es; сохранить сегментный регистр

pushw [bx]; сохранить память

push 1000h; сохранить значение

push ebx, esi, edi; сохранить по очереди три регистра

pusha; сохранить все 8 РОН

POP приемник

Команда POP копирует слово или двойное слово, содержащееся на вершине стека, в указанный операнд-приемник, затем увеличивает ESP так, чтобы он указывал на новую вершину стека. Операндом может быть РОН, память, сегментный регистр. Эта команда предназначена для извлечения из стека данных, сохраненных командой PUSH. Следует помнить, что извлекаются данные в обратном порядке, так что, если вы сохранили в стеке EAX, EBX и потом ECX, то извлекать надо сперва ECX, затем EBX и EAX. Команда POP является полной противоположностью команды PUSH, поэтому мнемоники POPW, POPD и POPA работают по аналогии с описанными выше мнемониками PUSH, но выполняют обратные действия.

Примеры:

pop bx; восстановить РОН

pop ds; восстановить сегментный регистр

popw [si]; восстановить память

pop edi, esi, ebx; восстановить по очереди три регистра

popa; восстановить все 8 РОН

Арифметические команды.

INC операнд

Команда увеличивает значение операнда на единицу. Операнд может быть РОН или памятью. Размер операнда -- байт, слово или двойное слово. Примеры:

inc ax; увеличить значение в регистре

inc byte[bx]; увеличить значение в памяти

ADD приемник, источник

Складывает оба операнда и помещает результат в приемник. Если результат превысил размер приемника, устанавливает флаг переноса (CF). Размером операндов может быть байт, слово или двойное слово. Приемник может быть РОН или памятью. Источник может быть РОН или значением. Источник может также быть памятью при условии, что приемник является регистром. Примеры:

add ax, bx; прибавить к регистру регистр

add ax,[si]; прибавить к регистру память

add [di], al; прибавить к памяти регистр

add al, 48; прибавить к регистру значение

add [char], 48; прибавить к памяти значение

ADC приемник, источник

Складывает оба операнда и добавляет единицу в случае, если флаг переноса установлен. Помещает результат в приемник. Правила для операндов те же, что и у ADD. Команда ADD в связке с ADC может использоваться для сложения чисел, не помещающихся целиком в регистр процессора.

DEC операнд

Команда уменьшает значение операнда на единицу. Правила для операнда те же, что и у INC.

SUB приемник, источник

Вычитает источник из приемника, помещает результат в приемник. Если источник был больше приемника, то устанавливается флаг переноса CF. Если источник был равен приемнику (результат 0), то устанавливается флаг ноля ZF. Правила для операндов те же, что и у ADD.

SBB приемник, источник

Вычитает источник из приемника, вычитает единицу в случае, если флаг переноса установлен. Помещает результат в приемник. Правила для операндов те же, что и у ADD. Команда SUB в связке с SBB может использоваться для вычитания чисел, не помещающихся целиком в регистр процессора.

CMP приемник, источник

Команда осуществляет сравнение приемника с источником способом вычитания источника из приемника, но, в отличие от SUB, результат никуда не сохраняет. По установленным командой флагам можно отследить результат такого сравнения и выполнить условный переход (JZ, JO, JC и т. п.).

NEG приемник

Изменяет знак операнда на противоположный, вычитая операнд из нуля. На практике команда применяется не только для смены знака, но и для вычитания из константы. Допустим, нам надо вычесть содержимое AX из 300. Очень хочется написать: sub 300, ax, но команда SUB не допускает возможности вычитания из непосредственного значения, потому что приемник должен являться РОН или памятью. Значит, мы могли бы предварительно поместить значение 300 в какой-то РОН, а затем вычесть из него AX, однако более простым вариантом с точки зрения процессора будет такой:

neg ax

add ax, 300

Мы прибавили 300 к отрицательному значению ax, что по законам математики дает такой же результат, что и вычитание ax из 300.

XADD приемник, источник

Еще одна редкая, но иногда очень полезная команда. Она похожа на ADD, только перед тем, как поместить сумму операндов в приемник, производит обмен значениями между операндами (как команда XCHG). Эта команда одним махом выполняет сразу 2 действия, а значит, может помочь сэкономить процессорное время.

Все вышеперечисленные арифметические операции изменяют флаги SF, ZF, PF, OF в соответствии с результатом. Команды MUL (умножение) и DIV (деление) были достаточно подробно описаны в предыдущей статье, поэтому не будем повторяться, и идем дальше.

Преобразование типов

Размеры операндов арифметических команд должны быть одинаковыми. Поэтому, если необходимо произвести арифметическое действие над операндами, имеющими разные размеры, следует сначала преобразовать один из них так, чтобы оба операнда имели одинаковый размер. Для этого в системе команд процессора предусмотрены команды преобразования типов. Они служат для преобразования байтов в слова, слов -- в двойные слова, двойных слов -- в учетверенные (qword). Эти преобразования могут выполняться способом знакового расширения -- увеличение размера операнда с учетом знака (заполнение старших разрядов увеличенного операнда значением старшего бита исходного операнда) или нулевого расширения (заполнение старших разрядов увеличенного операнда нолями).

Команды преобразования со знаковым расширением без операндов:

CBW преобразовывает байт, содержащийся в регистре AL, в слово, помещаемое в регистр AX.

CWD преобразовывает слово, содержащееся в регистре AX, в двойное слово, помещаемое в регистры DX: AX. Старшая часть значения разместится в DX, а младшая -- в AX.

CWDE преобразовывает слово, содержащееся в регистре AX, в двойное слово, помещаемое в регистр EAX.

CDQ преобразовывает двойное слово, содержащееся в EAX, в учетверенное слово, помещаемое в регистры EDX: EAX.

Еще раз напомню, что все перечисленные преобразования по сути своей -- лишь распространение значения старшего (знакового) бита исходного операнда на все биты добавляемой части. Эти команды работают с конкретными регистрами и поэтому не имеют операндов.

MOVSX приемник, источник

Преобразовывает с учетом знакового расширения байт в слово или двойное слово; слово -- в двойное слово. Операнд-источник может быть памятью или РОН, приемник всегда должен быть РОН.

MOVZX приемник, источник

Работает так же, как и MOVSX, только производит расширение без учета знака, то есть заполняет добавляемую часть нулями, а не знаковым старшим битом источника.

Десятичная арифметика

Десятичные числа могут быть представлены в так называемом двоично-десятичном коде (Binary Coded Decimal -- BCD). Этот способ предполагает хранение каждой десятичной цифры в четырех битах. Различают два формата хранения BCD-чисел:

-- упакованный, когда каждый байт (8 бит) может содержать две десятичные цифры (по 4 бита каждая). В таком случае каждый байт содержит десятичное число в диапазоне от 00 до 99.

-- неупакованный, когда каждый байт содержит лишь одну десятичную цифру в младших четырех битах, а старшие четыре бита имеют нулевое значение. Диапазон представления неупакованного BCD-числа в отдельно взятом байте составляет от 0 до 9.

Десятичная арифметика осуществляется методом комбинирования вышеописанных команд двоичной арифметики с командами, специально предназначенными для десятичных операций. Команды десятичной арифметики используются для приведения результата предыдущих двоичных вычислений к упакованному/неупакованному формату BCD-числа или, наоборот, для подготовки введенных данных к двоичным арифметическим операциям.

DAA корректирует результат сложения двух упакованных BCD-чисел в регистре AL. Эта команда должна следовать за командой сложения (ADD или ADC), если в сложении участвовали два упакованных BCD, и результат находится в AL. Если откорректированный результат превысит 99, то будет установлен флаг CF, а в AL останутся лишь две младшие цифры. Пример:

mov al, 49h

mov bl, 52h

add al, bl

daa

В данном примере наглядно показано, что упакованные BCD можно записывать просто как шестнадцатеричные числа (буковка h в конце числа означает HEX), только без использования символов ABCDEF. Когда будет произведено сложение 49h и 52h, результат будет неверным. Точнее, он будет верным, но лишь применительно к шестнадцатеричным числам, потому что процессор, складывая эти числа, считает их не упакованными десятичными, а стандартными двоичными или шестнадцатеричными, если хотите. Однако команда DAA все расставляет по своим местам, и в результате в AL получается 01 (две младшие цифры от 101), а установленный флаг CF позволяет определить, что единичка для следующего третьего разряда «в уме».

DAS работает аналогично DAA, только используется для корректировки результата вычитания. У команды также отсутствует операнд, потому что действие производится над регистром AL. Флаг CF (если установлен) указывает на то, что вычитаемое оказалось больше уменьшаемого, и необходимо это дело обработать, например, уменьшением третьего разряда на единицу.

AAA предназначена для корректировки результата сложения НЕ упакованных BCD-чисел размером в байт, то есть одноразрядных. Операнды отсутствуют, действие производится над регистром AL. Если результат превышает 9, то в AL помещается лишь младший разряд, а AH увеличивается на единицу, и устанавливается флаг CF.

AAS аналогична AAA за исключением того, что применяется для корректировки вычитания неупакованных одноразрядных BCD-чисел. При необходимости заема устанавливается флаг CF, а содержимое AH уменьшается на единицу.

AAM корректирует результат умножения двух неупакованных BCD, находящийся в AL. Данная команда просто делит содержимое AL на 10 и помещает частное в AH, а остаток -- в AL. Таким образом, в AX помещается двухразрядное неупакованное BCD. Стандартная версия команды не имеет операндов, однако существует расширенная версия AAM, в которой в роли операнда выступает непосредственное значение -- база, на которую будет производиться деление. Так что, выполнив, к примеру, команду AAM 5, вы поделите нацело содержимое AL, получив частное в AH, а остаток -- в AL. Очень удобный способ для деления нацело небольших (до 8 бит) чисел.

AAD подготавливает к делению неупакованное BCD-число, находящееся в AX. Команда просто добавляет к AL содержимое AH, умноженное на 10, потом AH обнуляется. Несмотря на свое основное назначение, команда отлично справляется не только с подготовкой к делению, но и с простым преобразованием неупакованного двузначного BCD в двоичный эквивалент. Также команда может оказать неоценимую помощь в преобразовании символьного кода цифр сразу в двоичный код. Например, в таблице ASCII цифра «1» имеет код 31h, цифра «2» -- 32h и т. д. Значит, для того, чтобы из символьного кода получить неупакованное BCD, нам достаточно обнулить старший шестнадцатеричный разряд (старшие 4 бита) ASCII-кода. Поместим число 15 в символьном виде в AX и переведем его в двоичное значение:

mov AX, 3135h

and AX, 0F0Fh; обнуляем левые половинки байтов

aad

После выполнения этих команд в AX у нас будет 0Fh, то есть число 15 в нормальном двоичном (шестнадцатеричном) виде, привычном для процессора. В расширенном варианте команды AAD есть возможность указать в качестве операнда непосредственное значение, на которое будет умножаться AH, прежде чем прибавиться к AL. Это дает еще один вариант использования команды: быстрое умножение небольших чисел.

2. Блок схема

3. Код программы

; Разработчик: Киселёв Роман Сергеевич

. stack 100h

. data

fname db 255,255 dup (?); буфер для строки, хранящей имя файла

; строки сообщений, которые будут выводиться на экран

msg db 'Enter File Name: $'

Emsg1 db 0ah, 0dh,'Open File Error ', 0ah, 0dh,'$'

Emsg2 db 0ah, 0dh,'File Is Not BMP', 0ah, 0dh,'$'

Emsg3 db 0ah, 0dh,'File Is Not Monochrome BMP', 0ah, 0dh,'$'

Emsg4 db 0ah, 0dh,'Read File Error', 0ah, 0dh,'$'

Emsg5 db 0ah, 0dh,'Image Size must be less than 320×200', 0ah, 0dh,'$'

; структура BITMAPFILEHEADER

bfType dw ?; информация о типе файла

bfSize dd ?; размер самого файла в байтах

bfReserved1 dw 0; нули (зарезервировано)

bfReserved2 dw 0; тоже нули

bfOffBits dd ?; смещение относительно начала файла и битовый массив растра

HeaderSize equ $-bfType; константа, хранящая размер заголовка

; Структура BITMAPINFOHEADER

biSize dd ?; размер структуры BITMAPINFOHEADER в байтах

biWidth dd ?; ширина изображения (в пикселях)

biHeight dd ?; высота изображения в пикселях

biPlanes dw ?; количество плоскостей

biBitCount dw ?; кол-во бит на пиксель

biCompression dd ?; тип сжатия

biSizeImage dd ?; размер изображения в байтах

biXPelsPerMeter dd ?; горизонтальное разрешение

biYPelsPerMeter dd ?; вертикльное разрешение

biClrUsed dd ?; текущее число цветов графического движка

biClrImportant dd ?; кол-во важных цветов

InfoHeaderSize equ $-biSize; константа для хранения размера структуры BITMAPINFOHEADER

; структуры, необходимые для считывания палитры

; RGBQUAD для 1 цвета

c1B db ?

c1G db ?

c1R db ?

c1Q db ?

; RGBQUAD для 2 цвета

c2B db ?

c2G db ?

c2R db ?

c2Q db ?

PalleteSize equ $-c1B; константа для хранения размеров палитры

BlockSize equ 4000; размер блока при чтении растра

Rastr db BlockSize dup (?); массив битов растра

CurX dw ?; используется при рисовании — текущее зн-ие колонки

CurY dw ?; текущее значение строки

IgnoredBits db 0; число битов, которые не входят в изображение, но присутствуют в каждой строке для выравнивания ее по параграфу

OldScreenMode db?; переменная для хранения предыдущего видеорежима

Descriptor dw?; Дескриптор открытого файла

I_Option db 0; Байт, хранящий информацию о том, включена ли опция/I

; Если выключена, то I_Option=0, иначе — любое значение, отличное от нуля (если конкретно — 1)

. code

; Макрос для вывода строки на экран

print macro str_addr

mov ah, 09

lea dx, str_addr

int 21h

endm

; Макрос для вывода сообщения об ошибке и перехода в начало программы

PrintError macro str_addr

print str_addr

jmp Begin

endm

; Макрос для чтения данных из файла

ReadFile macro buf_addr, StrucSize

mov ah, 3fh; ф-ия для чтения из файла

mov bx, Descriptor; дескриптор файла

lea dx, buf_addr; адрес на начала буфера

mov cx, StrucSize; размер структуры

int 21h

endm

start:

mov ax, @data; инициализация сегмента данных

mov ds, ax

call CmdAnalyze; Вызов анализатора командной строки. в dh будет записано после вызова кол-во символов в имени файла.

mov ax, 0A000h; инициализация es. es будет указывать на сегмент видеобуфера

mov es, ax

test dh, dh; если строка параметров (имя файла) _

jnz OpenFile; _ не пуста, то сразу переходим к открытия файла

; если она пуста то _

jmp Begin; переходим на ввод имени файла с клавиатуры

; сообщения об ошибках

FileNotFound:; если файл не найден

PrintError EMsg1

Read1Error:; если ошибка чтения из файла

call CloseFile

PrintError EMsg4

FileNotBMP:; если файл не является файлом BMP

call CloseFile

PrintError EMsg2

FileNotMonoChrome:; вывод сообщения об ошибке в случае, если BMP не является монохромным

call CloseFile

PrintError EMsg3

ExitNow:; преждевременное завершение программы

mov ax, 4C00h

int 21h

Begin:; на эту метку будем возвращаться при ошибках. Фактически, при неверном формате указанного файла, будем возвращаться сюда и снова запрашивать имя файла

; вывод на экран приглашения для ввода имени файла

print msg

; ввод имени файла с клавиатуры

mov ah, 0ah; 0ah — ф-ия для буферизированного ввода строки с клавиатуры

mov dx, offset fname

int 21h

; формирование строки имени файла для последующего открытия функцией сервиса DOS

mov bx, dx; по адресу в bx лежит размер буфера fname для ввода

inc bx; по адресу в bx — количество байт в строке

mov al,[bx]; в al — кол-во записанных байт в fname

add bl, al; теперь bx указвает на последний записанный байт в строке

inc bl; bx указывает на след. после последнего байт

mov [bx], 0; и помещаем туда ноль-байт

test al, al; если ввели пустую строку _

jz ExitNow; _ то выходим из программы

OpenFile:

; Откроем файл

lea dx, fname

mov ah, 3dh; ф-ия для открытия файла

xor al, al; режим — чтение

inc dx

inc dx; теперь dx указывает на начало строки fname

int 21h; открыли файл

jc FileNotFound; если ошибка — выдаем сообщение — снова вводим имя файла

mov Descriptor, ax

; Читаем BITMAPFILEHEADER

ReadFile bfType, HeaderSize

jc Read1Error

; Проверка сигнатуры формата BMP

mov ax, word ptr bfType; в ax помещаем первые 2 байта структуры заголовка. Они должны быть BM

cmp ax,'MB'; поскольку слова в памяти хранятся в обратном порядке — то сравниваем именно с MB

jne FileNotBMP; Если первые 2 байта не BM, то сообщаем о том, что файл не BMP и снова идем на ввод имени файла

; Чтение структуры BITMAPINFOHEADER из файла

ReadFile biSize, InfoHeaderSize

; Проверка: является ли BMP монохромным ?

mov dx,[biBitCount]; в dx — количество бит на 1 пиксель в картинке

cmp dx, 1; сравниваем кол-во бит на пиксель с единицей. Единица — говорит о том, что битмап монохромный

jne FileNotMonoChrome; если изображение не монохромное, то говорим об этом и снова запрашиваем имя файла

; Выполним обработку опции командной строки /I

mov dl, I_Option

test dl, dl; если опция включена _

jnz SkipSizeCheck; _ то пропускаем проверку размеров изображения

mov dx, word ptr [biWidth]; в dx — ширина изображения

cmp dx, 320; если ширина изображения _

ja IncorrectSize; больше чем 320, то сообщаем о неверном размере

mov dx, word ptr [biHeight]; в dx — высота изображения

cmp dx, 200; если больше чем 200, то _

ja IncorrectSize; _ то сообщаем о неверном размере

SkipSizeCheck:

; считываем палитру

; для монохромных изображений — 2 цвета, 2 структуры RGBQuad. С

ReadFile c1B, PalleteSize

jc ReadFileError

; перемешаемся в файле на bfOffBits, т. е. на начала массива растра

mov ah, 42h; ф-ия для перемещения по файлу

mov bx, Descriptor; восстанавливаем дескриптор файла из стека

xor al, al; устанавливаем режим смещения — относительно начала файла

mov dx, word ptr bfOffBits; смещение указывается как (CX * 65 536) + DX. Вобщем-то DX всегда будет равен 3E для монохромных изображение, а CX=0

mov cx, word ptr bfOffBits+1

int 21h; перемещаемся

; установка графического режима

call SetDisplay

; инициализация начальных параметров рисования

mov cx, word ptr [biHeight]; в cx высота изображения в пикселях

mov CurY, cx; поскольку строки изображения в BMP расположены наоборот, то начинаем рисовать снизу вверх!

mov CurX, 0; начиная с первого правого пикселя изображения

call CalculatePar; вызываем ф-ию для вычисления кол-ва битов, не принадлежащих изображению, но добавленных для выравнивания

; теперь IgnoredBits равно числу игнорируемых битов при рисовании

; будем читать из файла растр блоками по BlockSize байт и записывать в Rastr

; цикл для чтения из файла блоков растра

ReadBlock:

ReadFile Rastr, BlockSize; читаем блок растра из файла

jc ReadFileError; если ошибка доступа, то сообщаем

cmp ax, 0; если достигнут конец файла _

je Skip; _ то выходим из цикла

call DrawBlock; обработка блока

cmp ax, cx; если это был последний блок, то _

jb Skip; _ выходим из файла

jmp ReadBlock; читаем следующий блок

Skip:

call CloseFile; закрываем файл

mov ah, 00; ф-ия для ожидания нажатия клавиши

int 16h; ждем пока юзер не нажмет любую клавишу, а затем выходим

; восстановим предыдущий видеорежим

xor ah, ah; ah=0 — ф-ия для установки графического режима

mov al, OldScreenMode; установка предыдущего режима

int 10h; устанавливаем графический режим с помощью прерывания BIOS

mov ax, 4C00h

int 21h; выход из программы

IncorrectSize:; у изображения слишком большой размер (больше чем 320×200)

call CLoseFile

PrintError EMsg5

ReadFileError:; вывод сообщения об ошибке, в случае возникновения ошибки чтения

PrintError EMsg4

jmp Begin

; Функция для закрытия файла

; ВХОД: sp должен указывать на дескриптор файла

CloseFile:

; закрытие файла

mov ah, 3eh; 3eh — ф-ия DOS для закрытия файла

mov bx, Descriptor

int 21h; закрыть)

ret

; ф-ия для отрисовки одного пикселя

; Вход: al= индекс палитры

DrawPixel proc

push dx; сохраним dx в стек, чтоб не испортить

; если CurX указывает на бит, который не входит в изображение, то пропускаем рисование

mov cx, word ptr [biWidth]; в cx — ширина изображения

cmp cx, CurX; если текущий рисуемый пиксель выходит за границы изображения _

jb SkipDraw; _ то пропускаем рисование

cmp CurX, 319

jg SkipDraw

cmp CurY, 199

jg SkipDraw

; рисуем точку

; mov cx, CurX; номер колонки — текущий (CurX)

; mov dx, CurY; номер строки — текущий (CurY)

; mov ah, 0ch; 0ch — ф-ия для рисования точки в видеосервисе BIOS

; mov bh, 0; номер видеостраницы = 0

; int 10h; рисуем точку

; рисуем точку

push ax; сохраним ax чтоб не испортить

mov ax, 320; найдем расположение нужного байта в памяти видеобуфера

mul CurY; умножим на номер строки

add ax, CurX; прибавим номер колонки

mov di, ax; es: di указывает на байт, соответствующий данному байту

pop ax; восстановим значение индекса палитры

mov byte ptr es: [di], al; копируем индекс палитры данного пикселя в видеобуфер

SkipDraw:

; проверка достижения конца строки изображения

inc CurX; переходим на следующий пиксель

mov cx, word ptr [biWidth]; в cx — ширина изображения

add cl, IgnoredBits; прибавляем к ней кол-во битов, которые не должны отрисовываться

cmp CurX, cx; если текущий номер пикселя не равен последнему _

jne SkipEqu; _ то пропускаем переход к следующей строке

NextStr:

; переход на след. строку

mov CurX, 0; теперь номер 1ого пикселя — нулевой

dec CurY; CurY — указывает следующую строку

SkipEqu:

pop dx; восстановим dx

ret

DrawPixel endp

; ф-ия для установки видеорежима и цветов палитры.

SetDisplay proc

mov ah, 0Fh; ф-ия BIOS для получения номера текущего видеорежима

int 10h

mov OldScreenMode, al; сохранить

xor ah, ah; ah=0 — ф-ия для установки графического режима

mov al, 13h; al=13h установка режима 320×200 c 256 цветами

int 10h; устанавливаем графический режим с помощью прерывания BIOS

mov dx, 3c8h; порт видео палитры (3C8h)

mov al, 0; установить индекс палитры

out dx, al

; Установить цвет фона в черный

; (0,0,0) — черный

mov dx, 3c9h; порт 3C9h

mov al, 0; красный

out dx, al

mov al, 0; зеленый

out dx, al

mov al, 0; синий

out dx, al

mov dx, 3c8h; порт видео палитры (3C8h)

mov al, 1; установить индекс палитры 1

out dx, al

; сделаем белый цвет (63, 63, 63)

mov dx, 3c9h; порт записи 3C9h

mov al, 63; красный

out dx, al

mov al, 63; зеленый

out dx, al

mov al, 63; синий

out dx, al

ret

SetDisplay endp

; процедура для обработки блока растра

; вход: в регистре ax — размер данных в блоке

; по адресу Rastr располагается битовый массив растра

DrawBlock proc

; сохраним значения регистров в стеке, чтоб ничего не попортить

push ax

push bx

push cx

push dx

lea si, Rastr; si указывает на нулевой байт в массиве растра

mov cx, ax; теперь в cx кол-во байтов растра в данном блоке

; циклическая обработка каждого байта растра в блоке

BCycle:

push cx; сохраняем количество итераций в стеке

mov dl,[si]; копируем текущий байт в регистр dl

call DrawByte; вызываем ф-ию отрисовки байта растра

inc si; переходим к следующему байту

pop cx; восстанавливаем значение счетчика итераций

loop BCycle; выполняем цикл, пока не обработаем все байты

; восстановим регистры

pop dx

pop cx

pop bx

pop ax

ret

DrawBlock endp

; процедура для отрисовки пикселей, записанных в одном байте

; вход: dl — байт для отрисовки

DrawByte proc

mov bl, 1 000 0000b; в bl — маска для выборки первого слева бита

mov cx, 8; количество итераций = количеству бит в байте

; будем сдвигать маску вправо и вычленять значения битов с помощью логического И

ShrCycle:

push cx; сохраним значение счетчика итераций

test dl, bl; логическое И между нашим байтом и маской. результат — изменение флагов

jnz White; если бит=1, то цвет пикселя белый

mov al, 0; если нет, то черный

jmp SkipColor; пропускаем установку цвета в белый

White:

mov al, 1; al=1. 1 — индекс палитры для белого цвета (см. SetDisplay)

SkipColor:

call DrawPixel; рисуем данный пиксель на экране

shr bl, 1; сдвигаем маску вправо

pop cx; восстановить счетчик цикла

loop ShrCycle; выполняем алгоритм для каждого бита

ret

DrawByte endp

; ф-ия для вычисления количества игнорируемых битов в строке растра. (они добавляются для выравнивания строки по параграф)

; вход: —

; выход: IgnoredBits

CalculatePar proc

; сохраним на всякий случай значения регистров в стек

push ax

push cx

push dx

push bx

mov ax, word ptr [biWidth]; в ax — ширина изображения

mov bl, 32; будем делить на 32. Откуда 32? (biWidth/8)/4 == biWidth/32. Ненулевой остаток от деления говорит о необходимости выравнивания

xor cx, cx; cx=0 — здесь будет храниться число добавляемых бит для выравнивания

; будем выполнять деление, смотреть остаток

CalcCycle:

div bl; делим на 32 ширину изображения

cmp ah, 0; если остаток равен нулю, то _

je ExitCycle; _ выходим из цикла

inc cx; если остаток ненулевой, то добавляем 1 бит для выравнивания и снова делим

mov ax, word ptr [biWidth]; в ax — первоначальная ширина изображения

add ax, cx; теперь в ax — расширенный дополнительными битами размер строки изображения

jmp CalcCycle; снова делим, пока не дополним строку до кратности 4

ExitCycle:

mov IgnoredBits, cl; сохраним количество битов, дополняющих строку до параграфа

; восстановим все регистры

pop bx

pop dx

pop cx

pop ax

ret

CalculatePar endp

; Процедура для парсинга строки параметров

; Вход: es ссылается на префиксный сегмент (PSP)

; Выход: dh содержит кол-во символов в имени файла, считываемого (имени) из параметров командной строки

; Также влияет на глобальную переменную I_Option, устанавливает ее в 0 или 1, в зависимости от того, установлена ли опция /I

CmdAnalyze proc

mov bx, 80h; в bx — смещение строки параметров программы относительно префиксного сегмента

xor dx, dx; dx=0

mov dl, es: [bx]; в dl — кол-во считанных символов из командной строки

inc bx

test dl, dl; если параметров нет, то _

jz SkipParsing; _ парсить нечего

lea di, fname; в di смещение на строку, которая будет хранить имя файла

xor cx, cx; cx=0

mov cl, dl; в счет цикла (cx) помещаем кол-во символов в строке параметров

add di, 2; сделано для совместимости формата строки при вводе имени файла через параметры и при вводе с клавиатуры

CopySymbols:

mov al, es: [bx]; в al пересылаем текущий символ из строки параметров в PSP

cmp al,''; если полученный символ — пробел, _

je NextSymbol; _ то игнорируем его и переходим к следующему

cmp al,'/'; если символ — косая черта, что обозначает использование опции, _

je UseOption; _ то переходим на метку обработки опции

mov [di], al; если обычный символ — просто переписываем его в fname

inc di; на следующую позицию в fname !

inc dh; увеличиваем счетчик записанных символов

jmp NextSymbol; переход к следующему символу

UseOption:

dec cx; поскольку будем анализировать след. символ, то уменьшим счетчик

jz EndCopy; если косая черта была последней в строке, то завершаем работу со строкой, опция — игнорируется

inc bx; переходим к след. символу

mov al, es: [bx]; пересылаем его в al

cmp al,'I'; если это не ключ нашей опции игнорирования, а нечто иное _

jne NextSymbol; _ то просто переходим к след. символу

mov I_Option, 1; если /I, то включаем опцию

NextSymbol:

inc bx; Переход к след. символу

loop CopySymbols

EndCopy:

mov [di], 0; строку завершаем нулем, чтоб DOS мог нормально ее считать при открытии файла

SkipParsing:

ret

CmdAnalyze endp

end start

4. Результат работы программы

Рисунок 1. при запуске программы необходимо ввести директорию файла *. bmp

Рисунок 2. После ввода правильной директории файла программа рисует изображение

Заключение

Ассемблер является символическим аналогом машинного языка. По этой причине программа, написанная на ассемблере, должна отражать все особенности архитектуры микропроцессора: организацию памяти, способы адресации операндов, правила использования регистров и т. д. Из-за необходимости учета подобных особенностей ассемблер уникален для каждого типа микропроцессоров.

В данной курсовой работе рассмотрены основные этапы программирования на ассемблере, реализован алгоритм выполнения поставленной задачи.

ассемблер файл машинный программа

Список используемой литературы

1. В. Н. Пильщиков. Программирование на языке ассемблера IBM PC. — М.: Диалог — МИФИ. 1997

2. Ярмиш Р., Ярмиш Дж. -- Основы программирования на языке Ассемблера

3. А. Крупник — Изучаем Ассемблер. 2004

4. П. И. Рудаков, К. Г. Финогенов — Язык ассемблера: уроки программирования. 2001

5. Виктор Юров — Assembler. Практикум. 2004

ПоказатьСвернуть
Заполнить форму текущей работой