Міністерство освіти і науки, молоді та спорту України
Національний університет “Львівська політехніка”
Кафедра “Автоматизовані системи управління”
/
Розрахункова робота
на тему: "Хвилі та їх створення"
з курсу «Основи комп’ютерної графіки»
Варіант теми завдання розрахункової роботи № 60
Малюємо хвилі:
Для початку нам потрібний фон.
Це може бути фотографія неба, або ваш персональний малюнок.
/
Тепер малюємо лінію горизонту і море (вийшло трохи кривувато, однак давайте зішлемось на вітер).
/
Використовуючи круглий твердий пензлик (round hard-edge brush) з opacity у 70-85% намітимо хвилі.
/
Зверніть увагу на стрілки, по них видно всі основні лінії напрямку.
/
За допомогою більш темного кольору додаємо тіні найближчим хвилям, враховуючи напрям основних ліній.
Старайтесь, щоб усі тіньові лінії були рівні та нікуди не «втікали» , в іншому випадку ви ризикуєте втратити форму хвилі.
/
Використовуючи той самий пензлик, що й раніше, але з більш світлим відтінком підсилимо гребінь хвилі кружечками.
Зробимо ближчі до нас кружечки більш яскравими, а ті,що далі від нас нехай будуть темнішими – це надасть глибину.
Також на цьому етапі позначаємо на вводі світні плями.Ось наприклад як на цьому фото:
/
/
Тепер , коли потрібно деталізувати я вирішив трохи зекономити час, і саме тому взяв пензлик, який ставить одразу кілька точок.
За допомогою нього я добавив безліч дрібних бризгів на гребені хвилі.
/
Продовжуючи деталізацію, вирішив дадати світлим пензликом розводи з піною під гребенем.
Доречі, вони мають звичку розташовуватись сіткою.
/
Залишилось ще проробити все ще раз тоненьким пензликом – детальки, детальки, детальки!
/
Хвилі та алгоритм їх створення
Побудуємо грубу модель поверхні води. У вузлах горизонтальної решітки з квадратними осередками знаходяться точки, які можуть рухатись лише вертикально. Кожна точка з’єднана з вісьмома своїми сусідніми пружними пружинами. Тоді точка буде рухатись за таким законом:
z(t+|t) ~= z(t) + v(t)*|t + a(t)*|t^2/2, де
z(t) - висота точки у момент часу t;
|t - достатньо малий пpоміжок часу;
v(t) ~= (z(t)-z(t-|t))/|t - швидкість точки у момент часу t;
a(t) = f(t)/m = (f_1(t)+f_2(t)+...+f_8(t))/m;
f(t) - сума сил,
діючих на точку у веpтикальному напpямку;
m - маса точки;
f_i(t) - сила, діюча на точку зі стоpони i-ого сусіда;
f_i(t) ~= k*(z_i(t)-z(t)).
Таким чином,
z(t+|t) ~= 2*z(t) - z(t-|t) +
(z_1(t)+z_2(t)+...+z_8(t)-8*z(t)) * k*|t^2/2m.
Покладемо останній коефіцієнт, що дорівнює 1/4. Тоді фоpмула пpийме вигляду
z(t+|t) = (z_1(t)+z_2(t)+...+z_8(t))/4 - z(t-|t).
Таким чином,зберігаючи карту висот для даного і попереднього моментів часу, можна збудувати карту висот для наступного моменту часу. Зауважимо,що при розрахунках карту для наступного моменту часу можна будувати на місці карти для попереднього моменту.
Як накласти зображення на карту висот? Для кожної точки екрану треба знайти, який піксель картинки потрібно в ній показувати. Або, те ж саме, зміщення зображуваного пікселя відносно піксеня, який був би зображений в цій точці, якби поверхність була рівна. Можна показати, що зміщення вздовж осі OX тим більше , чим більший кут між поверхнею картинки в даній точці та віссю OX. Для спрощення замінимо кути їх тангенсами, а залежність зробимо лінійною :
|x = (z(x,y)-z(x-1,y))*n,
|y = (z(x,y)-z(x,y-1))*n, де
z(x,y) - висота в точці (x,y),
n - деякий коефіцієнт, візьмемо n=1/4.
Таким чином, там, де повинен був зображатись піксель
з кооpдинатами (x,y), ми малюємо піксель з кооpдинатами
(x+(z(x,y)-z(x-1,y))/4,y+(z(x,y)-z(x,y-1))/4). }
{**********************************************************}
{$A+,B-,D-,E-,F-,G+,I-,L-,N+,O-,P-,Q-,R-,S-,T-,V-,X+,Y-}
{$M 16384,0,655360}
Uses CRT;
Type
{ заголовок *.BMP-файлу }
BMPFileHeader = record
bfType : Array[1..2] of Char;
bfSize : LongInt;
bfReserved : LongInt;
bfOffBits : LongInt;
biSize : LongInt;
biWidth : LongInt;
biHeight : LongInt;
biPlanes : Word;
biBitCount : Word;
biCompression : LongInt;
biSizeImage : LongInt;
biXPelsPerMeter : LongInt;
biYPelsPerMeter : LongInt;
biClrUsed : LongInt;
biClrImportant : LongInt;
{ палітpа у випадку 256-кольорового *.BMP-файлу }
bmiColors : Array [0..255] of record
rgbBlue : Byte;
rgbGreen : Byte;
rgbRed : Byte;
rgbReserved : Byte;
end;
end;
{**********************************************************}
Type tScreen = Array[0..199,0..319] of Byte;
Var Screen : tScreen absolute $a000:$0000;
pScreen,
{ каpта висот для даного моменту часу }
buf1,
{ каpта висот для наступного та попереднього моментів часу }
buf2,
{ використовується для обміну двох попередніх вказівників }
buf3,
picture, { тут зберігається каpтинка }
{ тут зберігається кадp, готовий до виведення на екpан }
total : ^tScreen;
BMP : File;
Header : BMPFileHeader;
x,y,i : Integer;
BEGIN
{ виділяємо динамічну пам’ять }
New(buf1); FillChar(Buf1^,SizeOf(tScreen),0);
New(buf2); FillChar(Buf2^,SizeOf(tScreen),0);
New(picture);
New(total);
pScreen:=@Screen;
{ читаемо каpтинку з 256-кольорового *.BMP файлу
з pазміpом зобpаження 320x200
та без використання компpесії }
Assign(BMP,ParamStr(1));
ReSet(BMP,1);
BlockRead(BMP,Header,SizeOf(Header),i);
BlockRead(BMP,total^,SizeOf(tScreen),i);
Close(BMP);
{ у файлі рядки зберігались у зворотньому поpядку,
їх треба пеpеставити }
For y:=0 to 199 do
picture^[y]:=total^[199-y];
{ пеpеходимо в гpафічний pежим 13h та змінюємо палітpу }
asm
mov ax, $13
int $10
end;
Port[$3c8]:=0;
For i:=0 to 255 do
With Header.bmiColors[i] do
begin
Port[$3c9]:=rgbRed shr 2;
Port[$3c9]:=rgbGreen shr 2;
Port[$3c9]:=rgbBlue shr 2;
end;
{ краплі падають, поки не натиснута клавіша ESC }
Repeat
x:=1+Random(197); { у випадкове місце каpти висоти }
y:=1+Random(317);
Buf1^[x,y]:=255; { кидаємо краплю }
Buf1^[x+1,y]:=255;
Buf1^[x,y+1]:=255;
Buf1^[x+1,y+1]:=255;
{ будуємо каpту висот для наступного моменту часу }
asm
push ds
les di, Buf2
lds si, Buf1
{ межі екpану не чіпаємо, так як там у точок немає }
add si, 321
mov cx, 320*198-2 { всіх восьми сусідів }
xor ah, ah
xor bh, bh
@@loop:
mov al, [ds:si-321] { ax := ( buf1^[y-1,x-1] }
mov bl, [ds:si-320]
add ax, bx { + buf1^[y-1,x] + }
mov bl, [ds:si-319]
add ax, bx { + buf1^[y-1,x+1] + }
mov bl, [ds:si-1]
add ax, bx { + buf1^[y,x-1] + }
mov bl, [ds:si+1]
add ax, bx { + buf1^[y,x+1] + }
mov bl, [ds:si+319]
add ax, bx { + buf1^[y+1,x-1] + }
mov bl, [ds:si+320]
add ax, bx { + buf1^[y+1,x] + }
mov bl, [ds:si+321]
add ax, bx { + buf1^[y+1,x+1] ) }
shr ax, 2 { / 4 }
mov bl, [es:si]
sub ax, bx { - buf2^[y,x] }
jg @@1 { pезультат не повинен бути менше нуля }
xor ax, ax
@@1:
{ невелике "затухання" необхідне, щоб уся каpта }
mov bl, al
{ висот не заповнилась значеннями FFh }
shr bl, 6
sub al, bl
mov [es:si], al
inc si
loop @@loop
pop ds
end;
{ накладаємо зобpаження на каpту висот }
asm
{ нам буде потрібний сегментний pегістp SS }
cli
{ зберігаємось }
push ds
push bp
mov bp, ss
les di, total
mov ss, word ptr picture+2
lds si, buf1
{ пеpший рядок каpтинки пеpеписуємо без змін }
mov cx, 320
@@loop1:
mov al, [ss:di]
stosb
loop @@loop1;
{ обpобляємо внутpішні рядки }
mov cx, 320*198
xor bh, bh
@@loop2:
xor ah, ah
mov al, [ds:di] { ax := buf1^[y,x] }
mov dx, ax
mov bl, [ds:di-1]
sub ax, bx { - buf1^[y,x-1] }
sar ax, 2 { / 4 (вирахували |x) }
mov bl, [ds:di-320]
sub dx, bx { dx := buf1^[y,x] - buf1^[y-1,x] }
sar dx, 2 { / 4 (вирахували |y) }
mov si, dx
sal dx, 2
add dx, si
sal dx, 6 { dx := dx * 320 }
mov si, di
add si, ax
add si, dx
mov al, [ss:si] { al := picture^[y+|y,x+|x] }
mov [es:di], al { total^[y,x] := al }
inc di
loop @@loop2
{ останній рядок каpтинки пеpеписуємо без змін }
mov cx, 320
@@loop3:
mov al, [ss:di]
stosb
loop @@loop3;
{ відновлюваємося }
mov ss, bp
pop bp
pop ds
sti
end;
{ копіюємо готовий кадp на екpан }
asm
push ds
les di, pScreen
lds si, total
mov cx, 320*200/4
db $66; rep movsw { rep movsd }
pop ds
end;
Buf3:=Buf1;
Buf1:=Buf2; { дана каpта висот стає попередньою, }
Buf2:=Buf3; { а наступна - даною }
{ поки у поpту клавіатуpи не з’явиться код клавіші ESC }
Until Port[$60]=1;
{ повертаємось у текстовий pежим }
asm
mov ax, $03
int $10
end;
{ звільняємо пам’ять }
Dispose(Picture);
Dispose(buf2);
Dispose(buf1);END.
Хвильовий алгоритм
Хвильовий алгоритм - алгоритм, що дозволяє знайти мінімальний шлях в графі з ребрами одиничної довжини. Заснований на алгоритмі пошуку в ширину. Застосовується для знаходження найкоротшого шляху в графі, в загальному випадку знаходить лише його довжину.
Суть алгоритму
На двовимірній карті (матриці), що складається з «прохідних» і «непрохідних» комірок, позначена комірка старту і комірка фінішу. Мета алгоритму - прокласти найкоротший шлях від комірки старту до комірки фінішу, якщо це, звичайно, можливо. Від старту у всі напрями поширюється хвиля, причому кожна пройдена хвилею комірка позначається як «пройдена». Хвиля, у свою чергу, не може проходити через комірки помічені як «пройдені» або «непрохідні». Хвиля рухається, поки не досягне точки фінішу або поки не залишиться непройдених комірок. Якщо хвиля пройшла всі доступні комірки, але так і не досягла точки фінішу, значить, шлях від старту до фінішу прокласти неможливо. Після досягнення хвилею фінішу, прокладається шлях в зворотному напрямі (від фінішу до старту) і зберігається в масиві.
Різновиди
Заповнення по матриці в 4 напрямках, він же алгоритм Лі - точніший, але менш швидкий метод.
i+1
i+1 i i+1
i+1
Заповнення по матриці в 8 напрямків - більш швидкий, але менш точний метод. Шлях по діагоналі приблизно в 1.4 рази довший, ніж по горизонталі й вертикалі, саме тому комірки, розташовані по діагоналі, мають коефіцієнт +3, а по горизонталі й вертикалі - коефіцієнт +2.
і+3 і+2 i+3
i+2 i i+2
і+3 i+2 і+3
Існує також алгоритм А* (A-star) - направлений хвильовий алгоритм.
Практичне застосування
Хвильовий алгоритм - один з основних при автоматизованому трасуванні (розводці) друкованих плат. Він має досить велику кількість різноманітних модифікацій, що дозволяють покращити знайдений розв'язок з точки зору затрат пам'яті та швидкодії. Також одне з характерних застосувань хвильового алгоритму - пошук найкоротшої відстані на карті в стратегічних комп'ютерних іграх.
Опис алгоритму
Алгоритм складається з кількох етапів, серед яких основними можна виділити наступні: підготовчий етап, етап поширення хвилі та етап побудови шляху.
На підготовчому етапі проводиться аналіз комірок, що присутні на карті, визначаються зайняті комірки. Решта комірок позначаются як вільні, їм присвоюється вага "0". В лічильник кроків К записується 1.
Етап поширення хвилі полягає у знаходженні точки фінішу шляхом перебору сусідніх комірок та присвоєння їм відповідних ваг. В першу чергу перевіряються всі комірки, суміжні з початковою. Якщо комірка має вагу "0", їй присвоюється значення з лічильника кроків. Комірки з іншою вагою (заповнені на попередніх кроках), а також комірки, відмічені як зайняті, залишаються без змін. Якщо одна з комірок є точкою фінішу, алгоритм переходить на наступний етап – побудову шляху. В іншому випадку лічильник кроків збільшується на одиницю і описаний для початкової точки алгоритм повторюється для всіх точок з вагою К-1. Якщо кінцева точка не знайдена, і для всіх точок з вагою К-1 в околі не лишилось вільних комірок, то вважається, що шлях не існує.
На етапі побудови шляху (згортання хвилі) починаємо рух з кінцевої точки. Для цієї точки обирається комірка з її околу (вага цієї комірки буде К). Тоді для цієї комірки знову шукаєтся суміжна з вагою К-1. Таким чином продовжується доти, поки не знайдено комірку з вагою 1, в околі якої знаходиться початкова точка (вага початкової точки 0). Таким чином маємо побудований шлях, який також відносить до множини шляхів мінімальної довжини.
Приклад реалізації
Також однією з задач, з якими легко справляється хвильовий алгоритм є пошук виходу з лабіринту. Нижче наведено приклад реалізації алгоритму пошуку виходу з лабіринту на мові С++.
#include "conio.h" // Для функції getch()
#include <string.h>
struct screen_point{
unsigned char chr;
unsigned char attr; // Це все що потрібно для виводу
}; // на екран.
typedef struct screen_point screen_line[80];
screen_line * scr;
char movecost[10][10]={
{0,0,0,0,0,0,0,0,0,0},
{0,1,6,6,6,6,6,1,1,0},
{0,1,0,0,0,0,6,0,0,0},
{0,1,0,1,1,1,1,1,1,0},
{0,1,0,1,1,0,0,0,1,0}, // Це і є лабіринт
{0,1,0,1,0,0,1,0,1,0}, // 0 - стіна
{0,1,0,1,0,1,1,0,1,0}, // будь-яке інше число -
{0,1,0,0,0,0,0,0,1,0}, // ступінь прохідності
{0,1,8,1,1,1,1,1,1,0}, // 1 - найкраща прохідність
{0,0,0,0,0,0,0,0,0,0}
};
unsigned char fillmap[10][10]; // Розмір рівний розміру лабіринту!!!
// якщо шлях може бути довшим за
// 255 варто замінити byte->word
struct{
signed char x,y; // Координати в лабіринті
}buf[256]; // Чим більший лабіринт, тим більшим повинен
// бути цей масив
unsigned char bufp,bufe; // Індекси в buf
int sx,sy,tx,ty; // Початкові та кінцеві координати шляху
/*
ЦЯ ЧАСТИНА ЗАЙМАЄТЬСЯ ВИВОДОМ НА ЕКРАН І
НЕ МАЄ ЖОДНОГО ВІДНОШЕННЯ ДО АЛГОРИТМУ
*/
void clrscr(){ // Очистити екран
int i;
for(i=0;i<80*25;i++)((short*)scr)[i]=0x0720;
}
// Надрукувати рядок str в координатах (x,y) кольором attr
void writestr(int x,int y,char str[],char attr){
int i;
for(i=0;str[i]!=0;i++,x++){scr[y][x].chr=str[i];scr[y][x].attr=attr;}
}
// Малюємо початковий малюнок лабіринту
void draw_maze(){
int i,j;
for(j=0;j<10;j++)for(i=0;i<10;i++){
scr[j][i*2 ].attr=16*(7-movecost[j][i])+7+8*((i+j)&1);
scr[j][i*2+1].attr=16*(7-movecost[j][i])+7+8*((i+j)&1);
}
scr[sy][sx*2].chr='[';scr[sy][sx*2+1].chr=']';
scr[ty][tx*2].chr='<';scr[ty][tx*2+1].chr='>';
scr[1][40].attr=16*(7-1);writestr(45,1,"Пусте місце",7);
scr[3][40].attr=16*(7-0);writestr(45,3,"Стіна",7);
scr[5][40].attr=16*(7-6);writestr(45,5,"Болото",7);
writestr(40,7,"[] Початкова точка",7);
writestr(40,9,"<> Ціль шляху",7);
}
/*
РЕАЛІЗАЦІЯ САМОГО АЛГОРИТМУ
*/
/* Ця функція перевіряє чи є запропонований шлях в точку фінішу більш коротким,
ніж знайдені раніше, і якщо так, то запам'ятовує точку в buf. */
void push(int x,int y,int n){
if(fillmap[y][x]<=n)return; // Якщо новий шлях не коротший, його відкидаємо
fillmap[y][x]=n; // Запам'ятовуємо нову довжину шляху
buf[bufe].x=x;
buf[bufe].y=y; // Запам'ятовуємо точку
bufe++;
scr[y][x*2 ].chr=n/10+48;
// Промальовка та очікування натиснення клавіші
scr[y][x*2+1].chr=(n%10)+48;
getch(); //
}
/* Тут береться чергова точка з buf і повертається 1,
якщо buf пустий, повертається 0 */
int pop(int *x,int *y){
if(bufp==bufe)return 0;
*x=buf[bufp].x;
*y=buf[bufp].y;
bufp++;
return 1;
}
void fill(int sx,int sy,int tx,int ty){
int x,y,n,t;
// Спочатку fillmap заповнюється max значенням
_fmemset(fillmap,0xFF,sizeof(fillmap));
bufp=bufe=0; // Обнуляємо буфери
push(sx,sy,0);
while(pop(&x,&y)){ // Цикл виконуєтьс, доки є точки в буфері
if((x==tx)&&(y==ty)){
writestr(0,20,"Знайдено шлях довжиною ",15);
scr[20][19].chr=n/10+48;
scr[20][20].chr=(n%10)+48;
break;
}
// n рівне довжині шляху до будь-якої сусідньої комірки
n=fillmap[y][x]+movecost[y][x];
// Перебір 4-х сусідніх комірок
if(movecost[y+1][x ])push(x ,y+1,n);
if(movecost[y-1][x ])push(x ,y-1,n);
if(movecost[y ][x+1])push(x+1,y ,n);
if(movecost[y ][x-1])push(x-1,y ,n);
}
// Якщо перебрано всю карту і не знайдено жодного шляху
if(fillmap[ty][tx]==0xFF){
writestr(0,20,"Шляху не існує!!!",15);
return;
} else
writestr(0,20,"Заливку завершено. Пройдемо по шляху!!!",15);
x=tx;y=ty;n=0xFF; // Ми почали заливку з (sx,sy), отже
// по шляху доведеться йти з (tx,ty), у зворотньому напрямі
while((x!=sx)||(y!=sy)){ // Доки не прийшли в (sx,sy)
scr[y][x*2].attr=2*16;scr[y][x*2+1].attr=2*16; // Малювання
// Шукається сусідня комірка,
if(fillmap[y+1][x ]<n){tx=x ;ty=y+1;t=fillmap[y+1][x ];} // що містить
if(fillmap[y-1][x ]<n){tx=x ;ty=y-1;t=fillmap[y-1][x ];} // мінімальне значення
if(fillmap[y ][x+1]<n){tx=x+1;ty=y ;t=fillmap[y ][x+1];}
if(fillmap[y ][x-1]<n){tx=x-1;ty=y ;t=fillmap[y ][x-1];}
x=tx;y=ty;n=t; // Переходимо на знайдену комірку
getch(); // Очікуємо натиснення клавіші
}
}
void main(){
int i;
sx=1;sy=1; // Задання початкової точки
tx=3;ty=3; // Задання цілі шляху
scr=(screen_line*)0xB8000;
clrscr();
draw_maze();
getch();
fill(sx,sy,tx,ty); // Виклик функції пошуку шляху
}
Рекурсивний (хвильовий) алгоритм стиснення зображень
Англійська назва рекурсивного стиснення – wavelet. Цей алгоритм орієнтований на стиснення кольрових і чорно-білих зображень з плавними переходами. Ідеальний для картинок типу рентгенівських фотографій. Коефіцієнт стиснення варіюється в межах 5-100. При великих коефіцієнтах стиснення на різких границях, особливо діагональних, можливі спотворення [6].Основна ідея алгоритму полягає в тому, що зберігаються різниці між середніми значеннями сусідніх блоків зображення, які звичайно приймають значення близькі до нуля.Рекурсивне стиснення базується на пірамідальному S-перетворенні, яке може використовуватись для стиснення фотореалістичних зображень як майже без втрат, так і з втратами. Стиснення виконується за два кроки: перший – S - перетворення початкового зображення; другий - перетворені дані кодуються одним з статистичних методів. Обидві операції зворотні, що дозволяє відновити початкове зображення. Однак для отримання великих коефіцієнтів стиснення необхідно зниження точності представлення компонент зображення, отриманих в результаті виконання S-перетворення, але так щоб спотворення не були візуально помітні.Структура кодування приведена на рис. 3.10. Вхідні відеодані обробляються фільтрами S-перетворення, які формують компоненти зображення. Адаптивний квантувач призначений для квантування значень відліків компонент з урахуванням особливостей зорового сприйняття. Процес виконання квантування зв'язаний з втратами інформації, однак ці втрати повинні бути непомітними, тобто візуальна якість відновленого зображення не повинне відрізнятися від вихідного.Власне стиснення може бути виконано на основі одного з методів статистичного кодування (наприклад, кодування Хаффмена, LZW-кодування, арифметичного кодування). Основна схема пірамідального S-перетворення приведена на рис. 3.11, а зворотного перетворення на рис. 3.12. Відліки низько- (фільтр F1) і високочастотної (фільтр F2) складової одержують відповідно до наступних виразів:/. (3.13)Де n= 0, 1... (N/2 – 1), C [2*n], C [2*n + 1] – відліки вхідного цифрового сигналу, а L[n], H[n] – низькочастотні і високочастотні коефіцієнти S-перетворення (рис. 3.11), L[n] – відліки на виході низькочастотного фільтра, H[n] – відліки на виході високочастотного фільтра. Ділення на два може бути виконано зсувом вправо на один біт, що дає результат з округленням до меншого.До кожного вихідного компонента фільтрів знову застосовуються дані формули, утворюючи в такий спосіб піраміду. Ясно, що чим більше кількість компонентів, тим більше очікуваний коефіцієнт стиску.Перевагою даного представлення вихідного зображення є те, що дисперсія H[n] менше ніж для C [n]. Інверсне S-перетворення обчислюється в такий спосіб:/. (3.14)Рис. 3.11-3.12Алгоритм для двовимірних даних реалізується аналогічно. Для квадрата з розмірами 2х2 і значеннями елементів a2i,2j, a2i+1, 2j, a2i, 2j+1, a2i+1, 2j+1/,отримаємо/ (3.15)Використовуючи ці формули, наприклад, для зображення з розмірами 512х512 пікселів після першого перетворення одержимо 4 матриці розміром 256х256 елементів:
Исходное
B1
B2
Изображение
B3
B4
У першій, як легко догадатися, буде зберігатися зменшена копія зображення. В другій — усереднені різниці пар значень пікселів по горизонталі. У третій — усереднені різниці пар значень пикселів по вертикалі. У четвертій — усереднені різниці значень пикселів по діагоналі. За аналогією з двовимірним випадком, ми можемо повторити наше перетворення й одержати замість першої матриці 4 матриці розміром 128х128. Повторивши наше перетворення втретє, ми одержимо в підсумку: 4 матриці 64х64, 3 матриці 128х128 і 3 матриці 256х256. На практиці, при записі у файл, значення / звичайно не враховують, відразу одержуючи виграш приблизно на третину розміру файлу.Можливі варіанти розкладу зображення на компоненти приведені на рис. 3.13 – 3.20. До достоїнств цього алгоритму можна віднести те, що він дуже легко дозволяє реалізувати можливість поступового “прояву” зображення при передачі зображення по мережі. Крім того,