Навеяно топиком Для начала строим эллипс инкрементируя [math]Х[/math] от [math]–b[/math] до 0, а для получения [math]Y[/math] используем уравнение эллипса [math]\left (\frac{X}{A}\right )^{2}+\left (\frac{Y}{B}\right )^{2}=1[/math] откуда [math]Y=B\cdot\sqrt{1–\left (\frac{X}{A}\right )^{2}}[/math], для скорости и используя симметрию выводим на экран через Set4Pixels сразу по четыре точки, все лишнее из цикла убрано, и так как умножение по-любому быстрее деления заменяем деление на [math]A^{2}[/math] умножением на [math]\frac{1}{A^{2}}[/math]. При построении смотрим не получился ли разрыв между точками и если [math]Y(X) – Y(X-1) > 1[/math], тогда строим линию с координатами [math](X-1, Y(X-1))[/math] и [math](X-1, Y(X))[/math]. На получившийся эллипс зеленого цвета, накладываем эллипс красного цвета построенный по алгоритму Брезенхейма. В результате наложения видно, что они на 1 пиксель не совпадают, то ли режим округления в FPU задан неверно, то ли хз? Полез в gdi32.dll откуда вызывается функция Ellips, оказывается из gdi32.Ellips через sysenter практически сразу уходим в ядро. Код (ASM): ; masm dos com # .model tiny a equ 99 b equ 63 aa equ a*a bb equ b*b aa2 equ 2*aa bb2 equ 2*bb aa4 equ 4*aa bb4 equ 4*bb color1 equ 0Ah color2 equ 0Ch .code .386 org 100h start: mov ax,13h int 10h push 0A000h pop es finit mov bp,x1 imul di,y1,320 call Set4Pixels1 mov ecx,b fld1 fld st ;st(0)=st(1)=1 fidiv b2 ;st(0)=-1/b² fstp b2 ;b2=-1/b² @@: inc x1 mov bx,y1 fild x1 fld st fmulp st(1),st; st(0)=x*x fmul b2 ; st(0)=-x²/b² st(1)=1 умножение быстрее деления fadd st,st(1) ; st(0)=1-x²/b² fsqrt ; st(0)=√(1-x²/b²) fimul a1 ; st(0)=a*√(1-x²/b²) fistp y1 sub bx,y1 ;удаляем разрывы между точками cmp bx,-b jb l4 mov bp,x1 dec bp l5: mov di,y1 add di,bx imul di,320 call Set4Pixels1 inc bx jnz l5 l4: mov bp,x1 imul di,y1,320 call Set4Pixels1 loop @b mov ah,0 int 16h ;--------------Алгоритм Брезенхейма для рисования эллипса-------------------- mov eax,x ;d=aa2*((x-1)*(x))+aa+bb2*(1-aa) dec eax imul x imul eax,aa2 add eax,aa add d,eax mov eax,-aa inc eax imul eax,bb2 add d,eax jmp l1 ;while(aa*x > bb*y) l0: call Set4Pixels2 cmp d,0 ;if (d>=0) jl @f dec x ;x-- imul esi,x,aa4 sub d,esi ; d-=aa4*(x); @@: mov esi,y lea esi,[esi*2+3] imul esi,bb2 add d,esi ;d+=bb2*(3+y*2) inc y ;y++ l1: imul esi,x,aa imul edi,y,bb cmp esi,edi jg l0 mov eax,y ;d=bb2*(y+1)*y+aa2*(x*(x-2)+1)+(1-aa2)*bb inc eax imul y imul eax,bb2 add d,eax mov eax,-aa2 inc eax imul eax,bb add d,eax mov eax,x sub eax,2 imul eax,x inc eax imul eax,aa2 add d,eax jmp l2 l3: call Set4Pixels2 cmp d,0 ;if (d<=0) jg @f inc y ;y++ imul eax,y,bb4 add d,eax ;d+=bb4*y @@: dec x ;x-- mov eax,x lea eax,[eax*2-3];d+=aa2*(3-x*2) imul eax,-aa2 add d,eax l2: cmp x,-1 jnz l3 mov ah,0 int 16h mov ax,3 int 10h retn Set4Pixels1 proc mov byte ptr es:[bp+32160][di],color1; X Y neg di mov byte ptr es:[bp+32160][di],color1; X -Y neg bp mov byte ptr es:[bp+32160][di],color1;-X -Y neg di mov byte ptr es:[bp+32160][di],color1;-X Y retn Set4Pixels1 endp Set4Pixels2 proc mov bp,word ptr x imul di,word ptr y,320 mov byte ptr es:[bp+32160][di],color2; X Y neg di mov byte ptr es:[bp+32160][di],color2; X -Y neg bp mov byte ptr es:[bp+32160][di],color2;-X -Y neg di mov byte ptr es:[bp+32160][di],color2;-X Y retn Set4Pixels2 endp x1 dw -b y1 dw 0 x dd b y dd 0 b2 dd -b*b a1 dd a d dd ? end start
Mikl___ На GDI не обращай внимания, там с округлением вообще не заморачивались и эллипс иногда не вполне симметричный получается.
Black_mirror Я убил на поиск подходящей реализации алгоритма Брезенхейма три дня, а оказалось либо эллипс по Брезенхейму рассчитывается неправильно, либо с округлением в FPU что-то не то...
Проблему решил выставлением режима "округление к нулю", аналогично работает режим "округление к −∞" заодно немного упростил реализацию алгоритма Брезенхейма Код (ASM): ; masm dos com # .model tiny a equ 63 b equ 99 aa equ a*a bb equ b*b aa2 equ 2*aa bb2 equ 2*bb aa4 equ 4*aa bb4 equ 4*bb color1 equ 0Ah color2 equ 0Ch .code .386 org 100h start: mov ax,13h int 10h push 0A000h pop es finit push eax fstcw [esp] or word ptr [esp],0C00h; режим округления к нулю fldcw [esp] pop eax mov bp,x1 xor di,di call Set4Pixels1 mov ecx,b fld1 fld st ;st(0)=st(1)=1 fidiv b2 ;st(0)=-1/b² fstp b2 ;b2=-1/b² @@: inc x1 mov bx,y1 ;в bx старое значение Y fild x1 fld st fmulp st(1),st; st(0)=x*x fmul b2 ; st(0)=-x²/b² st(1)=1 умножение быстрее деления fadd st,st(1) ; st(0)=1-x²/b² fsqrt ; st(0)=√(1-x²/b²) fimul a1 ; st(0)=a*√(1-x²/b²) fistp y1 ;удаляем разрывы между точками sub bx,y1;смотрим разницу между старым значением Y и новым значением Y jns l3 neg bp ;в bp старое значение X l2: add di,320 ;рисуем линию с координатами (Хстар, Yстар) и (Хстар, Yнов) call Set4Pixels1 inc bx ;пока разница не станет нулевой js l2 l3: mov bp,x1 call Set4Pixels1 loop @b mov ah,0 int 16h ;--------------Алгоритм Брезенхейма для рисования эллипса-------------------- l0: call Set4Pixels2 cmp d,0 ;if (d>=0) jl @f dec x ;x-- imul esi,x,aa4 sub d,esi ; d-=aa4*x; @@: mov esi,y lea esi,[esi*2+3] imul esi,bb2 add d,esi ;d+=bb2*(3+y*2) inc y ;y++ imul esi,x,aa imul edi,y,bb cmp esi,edi ;while(aa*x > bb*y) jg l0;Остановимся, если Х перестала быть основной осью (дуга ;прошла точку наклона касательной в 45 градусов) ;------------------------- mov eax,y ;d=bb2*(y+1)*y+aa2*(x-1)²+(1-aa2)*bb inc eax imul y imul eax,bb2 mov d,eax mov eax,-aa2 inc eax imul eax,bb add d,eax mov eax,x dec eax imul eax imul eax,aa2 add d,eax ;-------------------------------- l1: call Set4Pixels2 cmp d,0 ;if (d<=0) jg @f inc y ;y++ imul eax,y,bb4 add d,eax ;d+=bb4*y @@: mov eax,x lea eax,[eax*2-5];d+=aa2*(5-x*2) imul eax,-aa2 add d,eax dec x ;x-- jns l1;Остановимся, так как порошли через ось ординат mov ah,0 int 16h mov ax,3 int 10h retn ;--процедуры вывода точки на экран Set4Pixels1 proc mov byte ptr es:[bp+32160][di],color1; X Y neg di mov byte ptr es:[bp+32160][di],color1; X -Y neg bp mov byte ptr es:[bp+32160][di],color1;-X -Y neg di mov byte ptr es:[bp+32160][di],color1;-X Y retn Set4Pixels1 endp Set4Pixels2 proc mov bp,word ptr x imul di,word ptr y,320 mov byte ptr es:[bp+32160][di],color2; X Y neg di mov byte ptr es:[bp+32160][di],color2; X -Y neg bp mov byte ptr es:[bp+32160][di],color2;-X -Y neg di mov byte ptr es:[bp+32160][di],color2;-X Y retn Set4Pixels2 endp ;--данные----------------------- x1 dw -b y1 dw 0 x dd b y dd 0 b2 dd -b*b a1 dd a d dd -aa2*b+aa+bb2 end start
немного упростил реализацию алгоритма Брезенхейма 1) уравнение эллипса x²/b²+y²/a²=1 или x²a²+y²b²-a²b²=0 чтобы найти "точку перелома" берем производную d(x²a²+y²b²-a²b²)/dx=0 2a²x+2b²y*(dy/dx)=0 отсюда dy/dx=-2a²x/(2b²y)=-a²x/(b²y) ; в точке, где дуга прошла точку наклона касательной в 45 градусов dy/dx=-1 и существует равенство b²x=a²y 2) параметры рисования для начальной точки (0,А) точка из предыдущей итерации (x,y) сравнивается с точкой (x+1,y-0.5) разница равна a²(x+1)²+b²(y-0.5)²-a²b²-a²x²-b²y²+a²b²=a²x²+2a²x+a²+b²y²-b²y+0.25b²-a²x²-b²y²=2a²x+a²-b²y+b²/4 ; в точке x=0 y=a d=2a²*0+a²-b²a+bb/4=a²-b²a+b²/4 3) параметры рисования для начальной точки (B,0) точка из предыдущей итерации (x,y) сравнивается с точкой (x-0.5,y+1) разница равна a²(x-0.5)²+b²(y+1)²-a²b²-a²x²-b²y²+a²b²=a²x²-a²x+a²/4+b²y²+2b²y+b²-a²x²-b²y²=-a²x+a²/4+2b²y+b² ; в точке x=b y=0 d=-a²b+a²/4+b² Код (ASM): ; masm dos com # .model tiny a equ 63 b equ 99 aa equ a*a bb equ b*b aa2 equ 2*aa bb2 equ 2*bb color equ 0Ch .code .386 org 100h start: mov ax,13h int 10h push 0A000h pop es mov esi,(bb/4)-bb*a+aa xor ebp,ebp;x=0 mov edi,a*320;y=a xor eax,eax;deltaX=0 ;рисуем верхнюю и нижнюю точки mov byte ptr es:[di+32160],color; 0 Y ;избавляемся от двух команд neg edi так как di равно a*320 то di-a*640=-a*320 mov byte ptr es:[di+32160-a*640],color; 0 -Y ;увеличиваем ошибку накопления до значения следующей точки по оси X l0: add esi,eax ;d+=deltaX ;рисуем четыре симметричные точки mov byte ptr es:[bp+32161][di],color ; X Y ;избавляемся от команды inc ebp увеличив смещение по Х за счет смещения ;центра эллипса neg edi mov byte ptr es:[bp+32161][di],color ; X -Y not ebp; увеличиваем значение в ebp за счет того что not(X)=-(X+1) mov byte ptr es:[bp+32160][di],color ;-X -Y neg edi mov byte ptr es:[bp+32160][di],color ;-X Y neg ebp ;если ошибка накопления прошла 0, затем если координата Y прошла ;более чем полпути к следующему пикселю, то самое время перейти ;по оси Y на 1 пиксель и установить следующую ошибку накопления test esi,esi ;if (d>=0) js @f sub edi,320 ;y-- ;уменьшаем deltaY в соответствие с координатой Y sub deltaY,bb2 ;deltaY=2b²y ;уменьшаем ошибку накопления до значения следующей точки по оси Y sub esi,deltaY ;d-=2b²y ;увеличиваем deltaX в соответствие с координатой Х @@: add eax,aa2 ;deltaX=2a²*x cmp eax,deltaY ;while(a²x > b²y) jl l0;Остановимся, если Х перестала быть основной ;осью (дуга прошла точку наклона касательной в 45 градусов) ;------------------------- mov ebp,b ;x=b ;рисуем правую и левую точки mov byte ptr es:[bp+32160],color ; X 0 mov byte ptr es:[bp+32160-2*b],color ;-X 0 xor edi,edi ;y=0 xor eax,eax ;deltaY=0 mov esi,(aa/4)-aa*b+bb ;-------------------------------- ;увеличиваем ошибку накопления до значения следующей точки по оси Y l1: add esi,eax ;d+=deltaY ;увеличиваем deltaY в соответствие с координатой Y add eax,bb2 ;deltaY=2b²*y add edi,320 ;y++ ;рисуем четыре симметричные точки mov byte ptr es:[bp+32160][di],color; Х Y neg edi mov byte ptr es:[bp+32160][di],color; X -Y neg ebp mov byte ptr es:[bp+32160][di],color;-X -Y neg edi mov byte ptr es:[bp+32160][di],color;-X Y neg ebp ;если ошибка накопления прошла 0, затем если координата Х прошла ;более чем полпути к следующему пикселю, то самое время перейти ;по оси Х на 1 пиксель и установить следующую ошибку накопления test esi,esi ;if (d<=0) js @f ;уменьшаем deltaX в соответствие с координатой X sub deltaX,aa2 ;deltaX=2a²*x sub esi,deltaX ;d-=deltaX dec ebp ;x-- ;увеличиваем координату Y @@: cmp eax,deltaX ;while(b²y > a²x) jl l1;Остановимся, если Y перестала быть основной ;осью (дуга прошла точку наклона касательной в 45 градусов) ;--------------------------------------- mov ah,0 ;ждем нажатие на любую клавишу int 16h mov ax,3 ;восстанавливаем текстовый режим int 10h retn ;выходим из программы ;-------------------------------------------------- deltaX dd aa2*b;2x*a² deltaY dd bb2*a;2y*b² end start
Отделим мух от котлет алгоритм Брезенхейма на каждом шаге вычисляет положение точки, затем происходит вывод этой точки на экран и вычисление координат продолжается. Разделим создание эллипса на два этапа. Вычисление координат и рисование эллипса. Кроме того, если присмотреться, точки эллипса образуют набор вертикальных и горизонтальных линий разной длины смещенных относительно друг друга на один пиксель. Вычисляем длину каждой линии, которую потом поместим в массив ArrayX для горизонтальных и ArrayY для вертикальных линий, значения из массивов из-за симметрии будем читать или от начала к концу или от конца к началу. При этом явно задаются координаты только первой точки (в этом примере x=0 y=a) Код (ASM): ; masm dos com # .model tiny screen_width equ 320 screen_height equ 200 a equ 63 b equ 99 aa equ a*a bb equ b*b aa2 equ 2*aa bb2 equ 2*bb color equ 0Ch .code .386 org 100h start: mov ax,13h int 10h push 0A000h pop es ;--------------Алгоритм Брезенхейма для рисования эллипса-------------------- mov ebx,bb2*a ;deltaY=2b²a mov esi,(bb/4)-bb*a+aa mov edi,offset ArrayX;адрес массива для длин отрезков xor eax,eax ;deltaX=0 l0: add esi,eax ;d+=deltaX inc byte ptr [edi];вычисляем длину отрезка test esi,esi ;if (d ≥ 0) js @f inc edi ;переходим к следующей ячейке inc countX ;увеличмваем счетчик отрезков sub ebx,bb2 ;deltaY=2yb² sub esi,ebx ;d-=2yb² @@: add eax,aa2 ;deltaX=2xa² cmp eax,ebx ;while(a²x > b²y) jl l0 ;Остановимся, если Х перестала быть основной ;осью (дуга прошла точку наклона касательной в 45 градусов) mov ebx,aa2*b ;deltaX=2xa² mov edi,offset ArrayY;адрес массива для длин отрезков xor eax,eax ;deltaY=0 mov esi,(aa/4)-aa*b+bb l1: add esi,eax ;d+=deltaY add eax,bb2 ;deltaY=2yb² inc byte ptr [edi];вычисляем длину отрезка test esi,esi ;if (d ≤ 0) js @f inc edi ;переходим к следующей ячейке inc countY ;увеличмваем счетчик отрезков sub ebx,aa2 ;deltaX=2xa² sub esi,ebx ;d-=deltaX @@: cmp eax,ebx ;while(yb² > xa²) jl l1 ;заполняем первый квадрант--------------------- mov edi,a*screen_width+screen_width*(screen_height/2)+(screen_width/2);x=0 y=a mov al,color ;цвет эллипса stosb ;рисую точку (0,-a) mov ebx,offset ArrayX ;двигаюсь вправо и вверх call subrutineX ;читаю ArrayX от начала в конец add edi,screen_width-1 dec delta;-(screen_width+1);двигаюсь вправо и вверх mov esi,offset ArrayY-1 add esi,countY call subrutineY ;читаю ArrayY с конца и до начала ;заполняем второй квадрант------------------------ std dec edi stosb sub edi,(screen_width-1) neg delta1;delta1=-1 ;двигаюсь влево mov delta,-(screen_width-1) ;и вверх inc esi call subrutineY ;читаю ArrayY от начала в конец add edi,(screen_width+1) dec delta;-screen_width ;двигаюсь влево и вверх dec ebx call subrutineX ;читаю ArrayX с конца и до начала ;заполняем третий квадрант------------------------- add edi,screen_width stosb neg delta;screen_width ;двигаюсь влево и вниз inc ebx call subrutineX ;читаю ArrayX от начала в конец sub edi,(screen_width-1) inc delta;screen_width+1 ;двигаюсь влево и вниз dec esi call subrutineY ;читаю ArrayY с конца и до начала ;заполняем четвертый квадрант--------------------- cld inc edi stosb add edi,(screen_width-1) neg delta1;,1 ;двигаюсь влево mov delta,(screen_width-1) ;и вниз inc esi call subrutineY ;читаю ArrayY от начала в конец sub edi,(screen_width+1) inc delta;screen_width ;двигаюсь вправо и вверх dec ebx call subrutineX ;читаю ArrayX с конца и до начала ;----------------------------------------------------------- mov ah,0 ;ждем нажатие на любую клавишу int 16h mov ax,3 ;восстанавливаем текстовый режим int 10h retn ;выходим из программы ;-------------------------------------------------- subrutineX proc neg delta2 mov ecx,countX @@: push ecx mov cl,[ebx] rep stosb ;двигаемся по горизонтали add edi,delta ;смещаемся по вертикали на 1 пиксель вверх или вниз add ebx,delta2 ;двигаемся по массиву вперед или назад pop ecx loop @b retn subrutineX endp subrutineY proc neg delta2 mov ecx,countY l2: push ecx mov cl,[esi] @@: stosb ;смещаемся по горизонтали на 1 пиксель add edi,delta ;двигаемся по вертикали вверх или вниз loop @b add edi,delta1 ;edi = edi ± 1 add esi,delta2 ;двигаемся по массиву вперед или назад pop ecx loop l2 retn subrutineY endp ;------------------------------ delta dd -screen_width delta1 dd 1 delta2 dd -1 countX dd 0 countY dd 0 ArrayX db a/2 dup(0) ArrayY db a/2 dup(0) end start
Выше эллипс выводился без использования битовой маски в 256-цветном 13h графическом режиме. Приведенная программа выводит эллипсы в следующих графических режимах номер режимаразрешение в пикселяхколичество цветов0Dh 320x200 16 цветов0Eh640x20016 цветов0Fh640x350 монохромный10h640x35016 цветов11h640x480монохромный12h640x48016 цветов
Переделано из Абраш М. "Таинства программирования графики" К.: ЕвроСИБ, 1996 Код (ASM): ; masm dos com # .model tiny .code .386 org 100h SCREEN_WIDTH_IN_BYTES equ 80;число байтов в строке в режиме 12h GC_INDEX equ 3CEh SET_RESET_INDEX equ 0 ;индекс регистра установки/сброса в GC SET_RESET_ENABLE_INDEX equ 1; индекс регистра разрешения установки/сброса в GC BIT_MASK_INDEX equ 8; регистр битовой маски ;рисуем эллипс заданных радиусов по осям X и Y и заданным цветом, используя ;метод с целыми числами и без извлечения корней, генерируя дугу для одного ;октанта в буфер, рисуем четыре симметричных дуги из этого буфера. Затем делаем ;то же самое для другой основной оси. start: mov ax,12h int 10h ;640x480x16 push 0A000h pop es ;es=0A000h @@: cmp YRadius,240 ja exit push Color1 push YRadius push XRadius push 480/2 push 640/2 call DrawEllips dec Color1 dec XRadius add YRadius,2 jmp @b exit: mov ah,0 int 16h ;ждем пока не надавят на клаву mov ax,3 ;восстанавливаем текстовый режим int 10h retn ;выходим из программы ;---------------------------------------- ;рисуем эллипс радиусом А по оси Х, радиусом В по оси Y и цветом Color, центр ;в точке (x, y). Радиусы должны быть положительными DrawEllips proc PixListPtr equ word ptr [bp-22h] BB2 equ dword ptr [bp-20h] AA2 equ dword ptr [bp-1Ch] BB equ dword ptr [bp-18h] AA equ dword ptr [bp-14h] YAdjust equ dword ptr [bp-10h] XAdjust equ dword ptr [bp-0Ch] Threshold equ dword ptr [bp-8] WorkingY equ word ptr [bp-4] WorkingX equ word ptr [bp-2] X equ word ptr [bp+4] Y equ word ptr [bp+6] A equ word ptr [bp+8] B equ word ptr [bp+0Ah] Color equ word ptr [bp+0Ch] push bp mov bp,sp sub sp,22h push si push di mov si,X mov di,Y movzx eax,B mul eax mov BB,eax ; BB = B*B shl eax,1 mov BB2,eax ; BB2 = B*B*2 ;установим цвет с помощью установки/сброса mov dx,GC_INDEX mov ax,0F00h or SET_RESET_ENABLE_INDEX out dx,ax ;outpw(GC_INDEX, (0x0F00) | SET_RESET_ENABLE_INDEX) ;разрешим установку/сброс для всех плоскостей mov ah,Color mov al,0 out dx, ax ;outpw(GC_INDEX, (Color << 8) | SET_RESET_INDEX) ;установим цвет установки/сброса. Оставим индексный регистр GC указывающим на ;регистр битовой маски mov al,BIT_MASK_INDEX out dx,al ;outp(GC_INDEX, BIT_MASK_INDEX) ;рисуем четыре симмитричные дуги, для которых продвижение по оси Х идет быстрее, ;то есть для которых Х - основная ось. Установим параметры рисования для первой ;точки (0, В). Вычислим все точки вдоль дуги размером 1/8 эллипса и разместим ;эту информацию в PixList mov PixListPtr,offset PixList;PixListPtr = PixList mov WorkingX,0 ;WorkingX = 0 mov XAdjust,0 ;XAdjust = 0 movzx eax,A mul eax mov AA,eax ; AA = A*A shl eax,1 mov AA2,eax ; AA = A*A*2 movzx edx,B imul eax,edx mov YAdjust,eax ;YAdjust = AA * 2 * B sar eax,1 ;eax = AA * B mov ebx,AA shr ebx,2 ;ebx = AA/4 sub ebx,eax mov Threshold,ebx ;Threshold = AA/4 - AA * B ;инкрементируем ошибку накопления до значения для следующей точки по оси Х b1: mov eax,XAdjust add eax,BB add Threshold,eax ;Threshold += XAdjust + BB ;если ошибка накопления прошла 0, значит, координатаа Y прошла более чем полпути ;к следующему пикселю и самое время перейти по оси Y и остановить новую ошибку ;накопления cmp Threshold,0 ;if (Threshold >= 0) mov bx,PixListPtr jl b2 mov eax,AA2 sub YAdjust,eax ;YAdjust -= AA * 2 mov eax,YAdjust sub Threshold,eax ;Threshold -= YAdjust ;перейдем по обеим осям mov byte ptr [bx],1 ;*PixListPtr++ = 1 jmp short b3 ;перейдем только вдоль оси Х b2: mov byte ptr [bx],0 ;else *PixListPtr++ = 0 b3: inc PixListPtr ;инкрементируем координату Х mov eax,BB2 add XAdjust,eax ;XAdjust += BB * 2 inc WorkingX ;WorkingX++ ;остановимся, если Х больше не основная ось (дуга прошла точку наклона ;касательной в 45) mov eax,XAdjust cmp eax,YAdjust ;if(XAdjust >= YAdjust ) break jl b1 ;----------------------------------------------------------------- ;рисуем каждый из четырех симметричных октантов, для которых Х основная ось. ;Дуги через одну выравниваем, чтобы не произошло наложения push offset PixList push 0 push SCREEN_WIDTH_IN_BYTES push WorkingX mov ax,di;Y sub ax,B push ax;Y-B push si;X call DrawHOctant;(X, Y-B, WorkingX, SCREEN_WIDTH_IN_BYTES,0,PixList) push offset PixList+1 push 1 push SCREEN_WIDTH_IN_BYTES mov ax,WorkingX dec ax push ax;WorkingX-1 movzx ax,PixList add ax,di;Y sub ax,B push ax;Y-B+(*PixList) mov dx,si inc dx push dx;X+1 call DrawHOctant;(X+1, Y-B+(*PixList), WorkingX-1, SCREEN_WIDTH_IN_BYTES, 1, PixList+1) push offset PixList push 0 push -SCREEN_WIDTH_IN_BYTES push WorkingX mov ax,di add ax,B push ax push si call DrawHOctant;(X, Y+B, WorkingX, -SCREEN_WIDTH_IN_BYTES,0,PixList) push offset PixList+1 push 1 push -SCREEN_WIDTH_IN_BYTES mov ax,WorkingX dec ax push ax movzx ax,PixList mov dx,di add dx,B sub dx,ax push dx mov ax,si inc ax push ax call DrawHOctant;(X+1, Y+B-(*PixList), WorkingX-1, -SCREEN_WIDTH_IN_BYTES, 1, PixList+1) ;----------------------------------------------------------- ;рисуем четыре симметричные дуги, для которых продвижение по основной оси Х идет ;быстрее, то есть для которых Y - основная ось. Установим параметры рисования ;для начальной точки (А, 0). Вычислим все точки вдоль дуги 1/8 эллипса и ;разместим эту информацию в PixList mov PixListPtr,offset PixList; PixListPtr = PixList mov WorkingY,0; WorkingY = 0 mov YAdjust,0; YAdjust = 0 mov ebx,BB mov eax,ebx movsx edx,A imul ebx,edx ;ebx = BB * A shr eax,2 ;eax = BB / 4 sub eax,ebx ;eax = BB / 4 - BB * A mov Threshold,eax ;Threshold = BB / 4 - BB * A shl ebx,1 ;ebx = BB * 2 * A mov XAdjust,ebx ;XAdjust = BB * 2 * A b4: mov eax,YAdjust add eax,AA add Threshold,eax ;Threshold += YAdjust + AA cmp Threshold,0 mov bx,PixListPtr jl b5 mov eax,BB2 sub XAdjust,eax ;XAdjust -= BB * 2 mov eax,XAdjust sub Threshold,eax ;Threshold -= XAdjust mov byte ptr [bx],1 ;*PixListPtr++ = 1 jmp b6 b5: mov byte ptr [bx],0 ;*PixListPtr++ = 0 b6: inc PixListPtr mov eax,AA2 add YAdjust,eax ;YAdjust += AA * 2; inc WorkingY ;WorkingY++ mov eax,YAdjust cmp eax,XAdjust ;if(YAdjust >= XAdjust ) break; jl b4 ;------------------------------------------------ push offset PixList push 1 push -SCREEN_WIDTH_IN_BYTES push WorkingY push di mov ax,si sub ax,A push ax call DrawVOctant; DrawVOctant(X-A, Y, WorkingY, -SCREEN_WIDTH_IN_BYTES,1,PixList); push offset PixList+1 push 1 push SCREEN_WIDTH_IN_BYTES mov ax,WorkingY dec ax push ax mov ax,di inc ax push ax movzx ax, PixList add ax,si sub ax,A push ax call DrawVOctant; DrawVOctant(X-A+(*PixList), Y+1, WorkingY-1, SCREEN_WIDTH_IN_BYTES,1,PixList+1); push offset PixList push 0 push -SCREEN_WIDTH_IN_BYTES push WorkingY push di mov ax,si add ax,A push ax call DrawVOctant; DrawVOctant(X+A, Y, WorkingY, -SCREEN_WIDTH_IN_BYTES,0,PixList); push offset PixList+1 push 0 push SCREEN_WIDTH_IN_BYTES dec WorkingY push WorkingY inc di push di movzx ax,PixList add si,A sub si,ax push si call DrawVOctant; DrawVOctant(X+A-(*PixList), Y+1, WorkingY-1, SCREEN_WIDTH_IN_BYTES,0,PixList+1); mov dx,GC_INDEX+1 mov al,0FFh out dx,al ; outp(GC_INDEX+1,0xFF) dec dx mov ax,SET_RESET_ENABLE_INDEX out dx,ax ; outpw(GC_INDEX, SET_RESET_ENABLE_INDEX) pop di pop si leave retn 10 DrawEllips endp
Код (ASM): ; Рисуем дугу в октанте, в котором Х - основная ось. (x,y) - это первый пиксел ;дуги. HorizontalMoveDirection выбирает направление движения вдоль дуги по ;горизонтали - влево или вправо (0=влево, 1=вправо). RowOffset содержит смещение ;в байтах от данной линии сканирования до следующей и проверяет, отрисовывается ;дуга вверх или вниз, DrawLength - это горизонтальная длина в пикселах дуги, а ;DrawList - это список, содержащий 0 для каждой точки, если следующая точка ;вертикально выровнена, и 1, если следующая точка на 1 пиксел по диагонали ;смещена влево или вправо DrawHOctant proc X equ word ptr [esp+6] Y equ word ptr [esp+8] DrawLength equ word ptr [esp+10] RowOffset equ word ptr [esp+12] HorizontalMoveDirection equ word ptr [esp+14] DrawList equ word ptr [esp+16] push si push di imul dx,Y,SCREEN_WIDTH_IN_BYTES mov di,X mov cx,di sar di,3 add di,dx ;di = ScreenPtr = (Y * SCREEN_WIDTH_IN_BYTES)+(X/8)) and cx,7 mov ax,80h sar ax,cl mov cl,al ;cl = BitMask = 0x80 >> (X & 7) mov si,DrawLength ;while (DrawLenght--) b1: mov dx,GC_INDEX+1 mov al,cl ;al = BitMask out dx,al ;outp(GC_INDEX + 1, BitMask) or byte ptr es:[di],0FEh;*ScreenPtr |= 0xFE mov bx,DrawList inc DrawList cmp byte ptr [bx],0 ;if(*DrawList++) jz b2 add di,RowOffset ;ScreenPtr += RowOffset b2: cmp HorizontalMoveDirection,1 jnz b3 shr cl,1 ;BitMask = BitMask >> 1 jnz b4 ;BitMask = 0? mov cl,80h ;BitMask = 80h inc di ;ScreenPtr++ jmp b4 b3: shl cl,1 ;BitMask = BitMask << 1 jnz b4 ;BitMask = 0? mov cl,1 ;BitMask = 1 dec di ;ScreenPtr-- b4: dec si jnz b1 pop di pop si retn 12 DrawHOctant endp ; Рисуем дугу в октанте, в котором Y - основная ось. (x,y) - это первый пиксел ;дуги. HorizontalMoveDirection выбирает направление движения вдоль дуги по ;горизонтали - влево или вправо (0=влево, 1=вправо). RowOffset содержит смещение ;в байтах от данной линии сканирования до следующей и проверяет, отрисовывается ;дуга вверх или вниз, DrawLength - это вертикальная длина в пикселах дуги, а ;DrawList - это список, содержащий 0 для каждой точки, если следующая точка ;вертикально выровнена, и 1, если следующая точка на 1 пиксел по диагонали ;смещена влево или вправо DrawVOctant proc X equ word ptr [esp+6] Y equ word ptr [esp+8] DrawLength equ word ptr [esp+10] RowOffset equ word ptr [esp+12] HorizontalMoveDirection equ word ptr [esp+14] DrawList equ word ptr [esp+16] push si push di ;позиционируемся на байт, в котором находится первый пиксель imul dx,Y,SCREEN_WIDTH_IN_BYTES mov di,X mov cx,di ;cx = X sar di,3 add di,dx ;di = Y * SCREEN_WIDTH_IN_BYTES + (X/8)) ;установим начальную битовую маску and cx,7 mov ax,80h shr ax,cl mov cl,al ;cl = BitMask = 0x80 >> (X & 7) ;рисуем все точки в DrawList mov si,DrawLength ;while (DrawLenght--) test si,si jz b4 b1: mov dx,GC_INDEX+1 ;установим битовую маску пиксела mov al,cl ;al = BitMask out dx,al ;outp(GC_INDEX + 1, BitMask) ;рисуем пиксел. OR выполним, чтобы принудительно считать/записать для загрузки ;карманов. Записываемые данные значения не имеют, потому что установка/сброс ;разрешена для всех плоскостей or byte ptr es:[di],0FEh;*ScreenPtr |= 0xFE ;перейдем на следующий пиксель в соответствии с DrawList mov bx,DrawList inc DrawList cmp byte ptr [bx],0 ;if(*DrawList++) jz b3 ;шагнем по горизонтали, чтобы создать диагональное перемещение. Сдвинем битовую ;маску, перемещаясь на один байт по горизонтали, если битовая маска вырождается cmp HorizontalMoveDirection,1 jnz b2 shr cl,1 ;BitMask = BitMask >> 1 jnz b3 ;BitMask = 0? mov cl,80h ;BitMask = 80h inc di ;ScreenPtr++ jmp b3 b2: shl cl,1 ;BitMask = BitMask << 1 jnz b3 ;BitMask = 0? mov cl,1 ;BitMask = 1 dec di ;ScreenPtr-- b3: add di,RowOffset ;ScreenPtr += RowOffset dec si jnz b1 b4: pop di pop si retn 12 DrawVOctant endp Color1 dw 7 XRadius dw 319 YRadius dw 1 ;максимальная длина вдоль основной оси равна 1/2 ширины экрана PixList db SCREEN_WIDTH_IN_BYTES*8/2 dup(0) end start рисование эллипса с помощью 3 режима записи VGA, что позволяет для рисования в графических режимах 0Dh, 0Eh, 10h, 11h, 12h избежать использования тормозов в виде команд OUT при установке пиксела Код (ASM): ; masm dos com # .model tiny,stdcall SCREEN_WIDTH_IN_BYTES equ 80 ;число байтов в строке в режиме 12h GC_INDEX equ 3CEh;порт графического контроллера (GC) SET_RESET_INDEX equ 0 ;индекс регистра установки/сброса в GC SET_RESET_ENABLE_INDEX equ 1 ;индекс регистра разрешения установки/сброса в GC BIT_MASK_INDEX equ 8 ;регистр битовой маски GC_MODE_INDEX equ 5 ;индекс регистра графического режима в GC COLOR_DONT_CARE equ 7 ;индекс регистра безразличия цвета в GC BIT_MASK_INDEX equ 8 ;регистр битовой маски .code .386 org 100h start: push 0A000h pop es ;es := 0A000h mov ax,12h ;установим графический режим 640x480x16 int 10h ;--------------------------------------------- @@: push Color1 push YRadius push XRadius push 480/2 ;y-координата центра push 640/2 ;x-координата центра call DrawEllips ;в цикле выводим 120 эллипсов с разными радиусами dec Color1 ;и разными цветами dec XRadius add YRadius,2 cmp YRadius,240 jb @b mov ah,0 ;ждем нажатия на клаву int 16h mov ax,3 ;восстанавливаем текстовый режим int 10h ret ;выходим из программы
Код (ASM): ;рисуем эллипс заданных радиусов и заданным цветом по осям Х и Y, используя ;быстрый метод с целыми числами без извлечения корней, генерируя координаты ;дуги октанта в буфер PixelList и затем рисуем восемь дуг, используя ;вычисленные координаты ;-------------------------------------------------- DrawEllips proc X,Y,A,B,Color:word local OriginalGCMode:byte,Threshold:dword,AA:dword,BB:dword local XAdjust:dword,YAdjust:dword,DrawLength:word mov di,X ;установим цвет с помощью установки/сброса mov dx,GC_INDEX mov ax,0F00h or SET_RESET_ENABLE_INDEX ;AH=значение для регистра разрешения установки/сброса (все битовые плоскости out dx,ax ;разрешены) AL=номер регистра установки/сброса ;разрешим установку/сброс для всех плоскостей mov ah,byte ptr Color ;AH=значение пиксела mov al,SET_RESET_INDEX ;AL=номер регистра установки/сброса в GC out dx,ax ;установим цвет установки/сброса mov al,GC_MODE_INDEX out dx,al inc dx in al,dx mov OriginalGCMode,al or al,0Bh out dx,al dec dx mov ax,COLOR_DONT_CARE out dx,ax mov ax,0FF00h or BIT_MASK_INDEX out dx,ax ;готовимся к расчету координат дуги, где основная координата по оси X movzx eax,B mul eax mov BB,eax ;BB = B * B push eax ;MinorSquared = BB movzx eax,A mul eax mov AA,eax ;AA = A * A push eax ;MajorSquared = AA movzx edx,B mul edx ;eax = B*AA mov edx,AA shr edx,2 sub edx,eax push edx ;Threshold = AA/4 - B*AA add eax,eax push eax ;MinorAdjust = 2*B*AA call GenerateEOctant ;рисуем четыре симметричные дуги вдоль оси Х ;рисуем 1-ую дугу push SCREEN_WIDTH_IN_BYTES mov ax,Y sub ax,B ;Y-B call DrawHLOctant ;рисуем 2-ую дугу push SCREEN_WIDTH_IN_BYTES mov ax,Y sub ax,B ;Y-B call DrawHROctant ;рисуем 3-ью дугу push -SCREEN_WIDTH_IN_BYTES mov ax,Y add ax,B ;Y+B call DrawHLOctant ;рисуем 4-ую дугу push -SCREEN_WIDTH_IN_BYTES mov ax,Y add ax,B ;Y+B call DrawHROctant ;готовимся к расчету координат дуги, где основная координата по оси Y push AA ;MinorSquared = AA mov eax,BB push eax ;MajorSquared = BB movzx edx,A mul edx ;eax = A*BB mov edx,BB shr edx,2 sub edx,eax push edx ;Threshold = BB/4 - A*BB shl eax,1 push eax ;MinorAdjust = 2*A*BB call GenerateEOctant ;рисуем четыре симметричные дуги вдоль оси Y ;рисуем 5-ую дугу push -SCREEN_WIDTH_IN_BYTES push Y mov ax,di sub ax,A ;X-A call DrawVROctant ;рисуем 6-ую дугу push SCREEN_WIDTH_IN_BYTES push Y ;Y mov ax,di sub ax,A ;X-A call DrawVROctant ;рисуем 7-ую дугу push -SCREEN_WIDTH_IN_BYTES push Y mov ax,di add ax,A ;X+A call DrawVLOctant ;рисуем 8-ую дугу push SCREEN_WIDTH_IN_BYTES push Y ;Y mov ax,di add ax,A ;X+A call DrawVLOctant ;восстанавливаем первоначальный режим записи mov ah,OriginalGCMode mov al,GC_MODE_INDEX mov dx,GC_INDEX out dx,ax ;восстанавливаем нормальное состояние регистра безразличия цвета mov ax,0F00h or COLOR_DONT_CARE out dx,ax ;включим разрешение установки/сброса mov ax,SET_RESET_ENABLE_INDEX out dx,ax ret DrawEllips endp ;------------------------------------------------ ;генрируем октант заданного радиуса, помещая резудьтаты в PixelList, где 0 ;означает, что пикселы рисуются только вдоль основной оси, а 1, что рисуем ;пиксел по диагонали. Функция возвращает количество точек в дуге. ;---------------------------------------------- GenerateEOctant proc ;параметры MinorSquared equ dword ptr [esp+22] MajorSquared equ dword ptr [esp+18] Threshold equ dword ptr [esp+14] MinorAdjust equ dword ptr [esp+10] ;адрес возврата из процедуры [esp+6] ;локальные переменные MajorSquaredDouble equ dword ptr [esp+4] MinorSquaredDouble equ dword ptr [esp] add sp,-8 ;место под локальные переменные xor si,si ;установим счетчик в ноль mov eax,MajorSquared shl eax,1 mov MajorSquaredDouble,eax mov eax,MinorSquared shl eax,1 mov MinorSquaredDouble,eax ;установим MajorAdjust=0 xor ecx,ecx ;ecx - MajorAdjust mov ebx,Threshold ;ebx - ошибка накопления GenLoop: add ebx,ecx;инкрементируем ошибку накопления на MajorAdjust+MinorAxis^2 add ebx,MinorSquared ;если ошибка накопления прошла ноль, то координата по неосновной оси прошла ;более чем полпути к следующему пикселю и самое время сделать шаг вдоль ;неосновной оси и переустановить ошибку накопления mov PixelList[si],0;положим, что не будем двигаться вдоль неосновной оси js MoveMajor ;координата вдоль неосновной оси изменилась. ;Подправим координату по неосновной оси. mov eax,MajorSquaredDouble sub MinorAdjust,eax ;подправим ошибку накопления при шаге вдоль неосновной оси sub ebx,MinorAdjust mov PixelList[si],1 MoveMajor: inc si ;посчитаем эту точку ;подправим координату вдоль основной оси для новой точки add ecx,MinorSquaredDouble ;остановимся, если основная ось поменялась (дуга прошла точку наклона ;касательной в 45 градусов) cmp ecx,MinorAdjust jb GenLoop Done: ;вернем число пикселей через SI add sp,8 ;удаляем локальные переменные retn 10h GenerateEOctant endp ;-------------------------------------------------------------------------- ;Рисуем дугу в октанте, в котором Y - основная ось. (x,y) - это первый пиксел ;дуги. Функция выбирает направление движения вдоль дуги по горизонтали - влево ;или вправо (L=влево, R=вправо). RowOffset содержит смещение в байтах от данной ;линии сканирования до следующей и проверяет, отрисовывается дуга вверх или ;вниз, в AX - X, в SI - вертикальная длина в пикселах дуги, PixelList - список, ;содержащий 0 для каждой точки, если следующая точка вертикально выровнена, ;и 1, если следующая точка на 1 пиксел по диагонали смещена влево или вправо