Поради по графіку в Delphi, Delphi, Програмування, статті

Небесна мережа

Зміст

  1. Фрактальні множини.

  2. Перетягування вікна за робочу область.

  3. Вогненні літери (ефект вогню).

  4. “Зіркові” вікна.

  5. Кілька графічних ефектів.

Фрактальні безлічі

Ви напевно часто бачили досить хитромудрі картини, на яких незрозуміло що зображено, але все одно незвичайність їх форм зачаровує і приковує увагу. Як правило, це хитромудрі форми не піддаються здавалося б якомусь математичному опису. Ви приміром бачили візерунки на склі після морозу або наприклад хитромудрі плями, залишені на аркуші чорнильною ручкою, так ось щось подібне цілком можна записати у вигляді деякого алгоритму, а отже доступно порозумітися з комп’ютером. Подібні множини називають фрактальними. Вобще, як мені відомо фрактали з’явилися не так вже давно, але відразу завоювали свою важливу нішу. Фрактали не схожі на звичні нам фігури, відомі з геометрії, і будуються вони за певними алгоритмами, а ці алгоритми з допомогою комп’ютера можна зобразити на екрані. Вобще, якщо все злегка спростити, то фрактали – це якесь перетворення багаторазово застосоване до вихідної фігурі.

Тут хочу зупинитися на фрактальних множинах Мандельброта і Жюліа. Зображення цих множин не мають жодних чітко окреслених меж. Особливістю фракталів є те, що навіть маленька частина зображення в кінцевому підсумку представлет загальне ціле, особливо добре цей ефект можна поспостерігати на прикладі безлічі Жюліа. До речі на основі цієї властивості фракталів засновано фрактальної стиснення даних, але цю тему розберемо як-небудь пізніше (коли накопичитися досить потрібного матеріалу).

Отже приступимо до самого головного, заради чого ми тут і зібралися. Як же будуються ці удівітелние безлічі?

Все зводиться до обчислення однієї єдиної формули.

Zi+1=Zi2+C

Тут Z і C – комплексні числа. Як видно, формули по суті являє собою звичайну рекурсію (або щось подібне багаторазово застосованого перетворення). Знаючи правила роботи з комплексними числами дану формулу можна спростити і привести до наступного увазі.

xi+1=xi2-yi2+a

yi+1=2*xi*yi+b

Побудова множини Мандельброта зводиться до наступного. Для кожної точки (a, b) проводиться серія обчислень по вищенаведеним формулам, причому x0 і y0 приймаються рівними нулю, тобто точка у формулі виступає як константи. На кожному кроці вичіляется величина r = sqrt (x2 + y2). Значенням r, як не важко помітити, є відстань точки з координатами (x, y) від початку координат (r = sqrt [ (X-0) 2 + (y-0) 2]). Вихідна точка (a, b) вважається належить безлічі Мандельброта, якщо вона ніколи не віддаляється від початку координат на якесь критичне число. Для відображення можна підрахувати швидкість віддалення від центру, якщо наприклад точка пішла за критичне відстань, і залежно від неї забарвити вихідну точку до відповідних колір. Повне зображення безлічі Мандельброта можна отримати на площині від -2 до +1 по осі x і від -1.5 до 1.5 по осі y. Також відомо, що для отримання прімелімой точності достатньо 100 ітеарцій (по теорії їх повинно бути нескінченно багато). Нижче представлений лістинг функції реалізує виконання ітерацій і визначення приналежності точки безлічі Мандельброта, точніше на виході ми отримуємо колір для соответствующе точки. В якості критичного числа взято число 2. Щоб не обчислювати корінь, ми порівнюємо квадрат відстані (r2) з квадратом критичного числа, тобто порівнюємо (x2 + y2) і 4.

function MandelBrot(a,b: real): TColor;
var x,y,xy: real;
x2,y2: real;
r:real;
k: integer;
begin
r:=0;
x:=0; y:=0;
k:=100;
while (k>0)and(r<4) do begin
x2:=x*x;
y2:=y*y;
xy:=x*y;
x:=x2-y2+a;
y:=2*xy+b;
r:=x2+y2;
dec(k)
end;
k:=round((k/100)*255);
result:=RGB(k,k,k);
end;

Безліч Жюліа виходить якщо зафіксувати у формулі значення комплексної константи (a + ib), яка буде однакова для всіх точок, а початкові значення x0 і y0 приймати рівними значенням координатам обчислюється точки. Лістинг для безлічі Жюліа наведено нижче.

function Julia(x0,y0: real): TColor;
var a,b,x,y,x2,y2,xy: real;
r:real;
speed,k: integer;
begin
r:=1;
a:=-0.55; b:=-0.55;
x:=x0; y:=y0;
k:=100;
while (k>0)and(r<4) do begin
x2:=x*x;
y2:=y*y;
xy:=x*y;
x:=x2-y2+a;
y:=2*xy+b;
r:=x2+y2;
dec(k)
end;
k:=round((k/100)*255);
result:=RGB(k,k,k);
end;

Нижче наведено лістинг функції відображає дані безлічі.

procedure TForm1.BitBtn2Click(Sender: TObject);
var x_min,y_min,x_max,y_max,hx,hy,x,y: real;
i,j,n: integer;
color: TColor;
begin

x_min:=-1.5; x_max:=2;
y_min:=-1.5; y_max:=1.5;
n:=300;
y:=y_min;
hx:=(x_max-x_min)/n;
hy:=(y_max-y_min)/n;
for j:=0 to n do begin
x:=x_min;
for i:=0 to n do begin
if rbM.Checked then color:=MandelBrot(x,y);
if rbJ.Checked then color:=Julia(x,y);
imPict.Picture.Bitmap.Canvas.Pixels[i,j]:=color;
x:=x+hx;
end;
y:=y+hy;
end;
end;

При розгляді теми велику допомогу надала стаття А.Колеснікова “Візуалізація фрактальних структур” в “Комп’ютерних вістях”.

Перетягування вікна за робочу область

Для вирішення даної проблеми в можна піти в двох напрямках один не дуже витончений і злегка корявий, другий більш витончений. Розглянемо обидва.

Перший спосіб.

Перший спосіб полягає в обробки подій натискання клавіші миші на формі, переміщення миші і відпуску клавіші. Попередньо слід оголосити наступні змінні.

Hit: boolean;
_x,_y: integer;

Мінлива Hit на початку роботи повинні мати значення False. Це зручно зробити в обробнику події форми OnCreate.

procedure TForm1.FormCreate(Sender: TObject);
begin
Hit:=false;
end;

Далі слід написати обробник для події OnMouseDown (натиснута клавіша миші).

procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Hit:=true;
_x:=X; _y:=Y;
end;

Тут ми мутем присвоювання перменная Hit значення true вказуємо, що було вироблено натискання кнопки миші (поки кнопка буде утримуватися в натиснутому стані значення змінної Hit дорівнюватиме true). Також тут ми запам’ятовуємо поточні координати миші. Далі слід написати обробник для події OnMouseMove (переміщення курсора миші)

procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if Hit then begin
left:=left+(x-_x);
top:=top+(y-_y);
end;
end;

Спочатку йде перевірка натиснута клавіша миші, якщо натиснута, то переміщаємо наше вікно. І залишилося написати обробник для події OnMouseUp.

procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Hit:=false;
end;

Таким чином ми вказуємо що кнопка було відпущено, і тепер при переміщенні миші не слід перетягувати вікно.
А тепер про недоліки це методу. По перше, перетягування в тому вигляді, в якому наведено вище буде здійснюватися з допомогою обох кнопок миші (зазвичай це прийнято робити тільки лівою кнопкою). Цей недолік легко обходитися, слід лише додатково перевіряти параметр Button: TMouseButton. Другий недолік просто так не оминути. Якщо стоїть режим, коли при перетягуванні вікна саме вікно не перетягується, а перетягується його контур і лише при отжатии клавіші вікно малюється на новому місці, то даний метод буде робити вікно “білої вороною “перетягувати вікно, тому що воно буде постійно перемальовувати при перетягуванні.

Другий спосіб
Другий спосіб полягає в обробці повідомлення WM_NCHITTEST. Повідомлення надсилається вікна, і параметрами його є координати курсора миші. Повідомлення обробляється і возврашает одне зі значень, з яких нас будуть інтерісовать тільки два: HTCLIENT і HTCAPTION. Після обробки соообщенія по замовчуванням возврщается результат над якою областю вікна була натиснута клавіша миші. HTCLIENT означає що клавіша була натиснута над клієнтської областю, а HTCAPTION-над заголовком вікна. Як відомо Windows забезпечує перетягування за заголовок вікна, отже нам треба перехопити на зворотному шляху повідомлення, і якщо клавіша була натиснута над клієнтської областю, то підмінити повертається значення, вказуючи що ніби-то натискання було над заголовком.
В Delphi це робиться наступним чином. Спочатку оголошуємо обробник повідомлення, це робиться в оголошенні класу вікна.

procedure WMNCHITTEST (var Msg: TWMNCHITTEST); message WM_NCHITTEST;

Після цього описуємо відповідний обробник.

procedure TForm1.WMNCHITTEST (var Msg: TWMNCHITTEST);
begin
inherited;
if Msg.Result=HTCLIENT then
Msg.Result:=HTCAPTION;
end;

Спочатку ми викликаємо обробку по замовчуванням, а потім якщо треба переобпределяем повертається значення.
Даний метод не має тими недоліками, які були вказані прімінітельно до першого методу. До речі таким чином можна перетягувати вікна і не мають заголовка, правда

Вогненні літери (ефект вогню)

Старовинні люди підкорив вогонь, дізнався його секрети і поставив на службу людству. Так давайте і ми спробуємо підкорити цю стихію.
Для початку розберемо принцип створення ефект вогню. У комп’ютерній графіку при роботі із зображеннями дуже часто використовуються всілякі фільтри. Давайте розглянемо принцип побудови подібних фільтрів. Нехай є матриця (таблиця) розміром 3х3 (або 5х5), елементами якої є небудь числа (коефіцієнти). Наприклад ось така


1

2

7

4

9

6

3

8

5

Далі береться якесь зображення, і до нього застосовується цей фільтр такий спосіб. Береться частину зображення розміром 3х3 (5х5) навколо поточної точки (x, y)

(x-1,y-1) (x,y-1) (x+1,y-1)
(x-1,y) (x,y) (x+1,y)
(x-1,y+1) (x,y+1) (x+1,y+1)

Тепер накладаємо наш фільтр на цю частину зображення і чинимо наступним чином, множимо колір точки на відповідний коефіцієнт з фільтру, так чинимо для кожної точки, а потім складаємо ці твори. Наприкінці зазвичай ділять отриману суму на кількість доданків. Для нашого прикладу отримаємо наступне.

( (x-1,y-1)*1 + (x,y-1)*2 + (x+1,y-1)*7 + (x-1,y)*4 + (x,y)*9 + (x+1,y)*6 +
(x-1,y+1)*3 + (x,y+1)*8 + (x+1,y+1)*5)) /9

Тут під координатами (X, y) розуміється колір відповідної точки. Отримане значення є новим колір точки (x, y) (точка навколо якої було взято зображення). І так ми шукаємо колір для кожної точки зображення.
Існує дуже цікавий фільтр


0

0

0

1

1

1

1

1

1

Даний фільтр як би витягає вгору все зображення. Саме на основі цього фільтра і будується ефект вогню. Тобто ми малюємо червону лінію (джерело вогню) і застосовуємо до неї цей фільтр який витягує її вгору. Якщо більше нічого не робити, то зображення витягнеться і ми отримаємо просто статичну картину. Щоб цього не сталося, до зображенню вогню щоразу додається деяка кількість випадково розміщених чорних точок, які вносять різноманітність у зображення і в сукупності з застосуванням фільтра дає потрібний нам ефект. Якщо точок буде занадто багато, то гонь буде погано “Розпалюватися”, і буде ноборот занадто високо підніматися, якщо їх буде занадто мало, тому кіль випадкових точок слід підбирати в кожному конкретному випадку самому. Ось і вся теорія, а тепер перейдемо до практики.
При програмуванні використовувався набір компонентів DGC (див. Розділ “Вчимося” -> “Delphi Game Creator”), а точніше нам буде потрібно тільки компонент TDGCScreen. Опустіть його на форму і встановіть властивість DisplayMode в значення dm320x200x8 (екран розміром 320×200 256 кольорів). Щоб була можливість вийти з програми (вихід по натисненню Esc) напишемо обробник для форми події
OnKeyPress

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
if Key = #27 then
close;
end;

Тепер нам слід перевизначити палітру. Буде її перевизначати наступним чином, від чорного поступово до червоного кольору, саме ці два кольори будуть використані при створенні вогню.

procedure TForm1.SetPallete;
var NewPalette: T256PaletteEntry;
i: integer;
PE: TPaletteEntry;
begin
for i:=0 to 255 do begin
PE.peRed:=i;
PE.peGreen:=0;
PE.peBlue:=0;
PE.peFlags:=0;
NewPalette[i]:=PE;
end;
/ / Встановити нову палітру
DGCScreen1.SetPalette(NewPalette);
end;

Палітра являє собою масив з 256 елементів (від 0 до 255). Елементи масиву є об’єктами типу TPaletteEntry і визначають Кокретно колір в форматі RGB (частки червоного зеленого і синього). І коли ми вказуємо небудь колір ми вказуємо його індекс у палітрі а від туди вже вибирається Кокретно колірне подання. Церна колір це частки всіх кольорів дорівнює 0, а червоний коли частка червоного дорівнює 255 а інших 0. Таким чином ми в циклі заповнюємо нашу нову палітру, а потім встановлюємо її за допомогою методу SetPalette (див. вище).
Тепер вчинимо наступним чином, намалюємо яку або фігуру на екрані (лінії, текст і т.д.) червоним кольором, а потім скопіюємо весь екран в заздалегідь приготований буфер. Тип буфера оголосимо наступним чином

type
FlameArray=array [1..320,1..200] of byte;
……………………….
Flame,Flame2: FlameArray;

Тепер Flame2 містить початкову картинку, а Falme містить поточний зміст екрану (але про це пізніше).
А тепер напишемо текст на екрані (адреса сайту і e-mail адресу) і запам’ятаємо його в буфері.

Procedure TForm1.GetReady;
var i,j: integer;
begin
with DGCScreen1.Front.Canvas do begin
Brush.Color:=0;
Font.Color:=255;
Font.Size:=30;
TextOut(10,40,’http://www.chat.ru’);
TextOut(100,96,’/~shival’);
Font.Size:=20;
TextOut(40,150,’sia@itt.net.ru’);
/ / Копіюємо в буфер
for i:=1 to 315 do
for j:=1 to 195 do
begin
if Pixels[i,j]<>0 then begin
Flame2[i,j]:=Pixels[i,j];
end;
end;
release;
end;
end;

Тепер напишемо процедуру виводить знімок вогню на екран.

const
RPoint = 6000;

type
ScreenArray=array [1..64000] of byte;
…………………………………………………

procedure TForm1.DrawFlame;
var i,j: integer;
P: Pointer;
begin
/ / Розставляємо випадкові точки чорного кольору
for i:=1 to RPoint do
Flame[1+random(315),1+random(195)]:=0;

/ / Копіюємо первісний малюнок
for i:=1 to 315 do
for j:=1 to 195 do
begin
if Flame2[i,j]<>0 then
Flame[i,j]:=Flame2[i,j];
end;

Filter; / / застосовуємо фільтр

/ / Виводимо вийшли на екран
with DGCScreen1.Front do
begin
P:=GetPointer;
for i:=1 to 315 do
for j:=1 to 195 do
ScreenArray(P^)[i+widthbytes*(j-1)]:=Flame[i,j];
ReleasePointer;
end;
end;

А тепер кілька пояснень до наведеного лістингу. Спочатку ми на екрані (а точніше в буфері, з якого потім виведемо на екран) розставляємо випадковим чином RPoint чорних крапок. Потім ми копіюємо в поточний буфер первісну картинку з буфера в якому ми її запам’ятали, причому ми копіюємо тільки саму картинку, тобто кольори відмінні від чорного, щоб не затерти отриманий до цього часу вогонь. Якщо постійно НЕ встановлювати початкове зображення, то вогонь миттю з’їсть його і перед вами знову буде чорний екран. Далі ми застосовуємо фільтр, фільтр працює з поточним буфером Flame. І тепер вийшло зображення виводимо на екран (з буфера Flame). Щоб висновок відбувався швидше ми отримуємо за допомогою функції GetPointer покажчик на область пам’яті в якій зберігається зображення екрану, і пишемо все собержімое буфера Flame прямо в пам’ять, минаючи всі інстанції. Коли ви Викликали функцію GetPointer Windows блокується, в цей час ви вільно пишете в пам’ять а потім викликом методу ReleasePointer відновлюєте нормальне функціонування. Використовуючи отриманий покажчик ми обертаністю до потрібної ділянки пам’яті і пишемо туди потрібне значення з буфера Flame.
Тепер залишилося написати фільтр.

procedure TForm1.Filter;
var i,j: integer;
res : integer;
begin
for i:=1 to 315 do
for j:=1 to 195 do begin
res:=round((Flame[i-1,j]+
Flame[i,j]+
Flame[i+1,j]+
Flame[i+1,j+1]+
Flame[i,j+1]+
Flame[i-1,j+1])/6);
if res<10 then Flame[i,j]:=0 else
Flame[i,j]:=res;
end;
end;

Фільтр працює так, як було розказано раніше, тільки всі кольори індекс менше ніж 10 ми замінюємо на звичайний чорний, ось і все. Тепер напишемо обробник події OnInitialize компонента TDGCScreen.

procedure TForm1.DGCScreen1Initialize(Sender: TObject);
var i: integer;
begin
Randomize;
SetPallete; / / встановити палітру
GetReady; / / вивести первісне зображення
DrawFlame;
end;

Тепер щоб вогонь горів, а не стіл на місці опустимо на форму компонент TTimer і встановимо його властивість Enabled в TRUE а Interval привласнимо значення 50. І напишемо в його обробнику події OnTimer наступний код.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
DrawFlame;
end;

Тобто ми просто оновлюємо екран з определнной проміжком часу, а тим самим ми дали вогню горіти. От і все. Якщо хочете можете поекспериментувати з фільтром. Наприклад застосуйте такий фільтр ..


0

0

1

0

1

1

1

1

1

При застосуванні такого фільтра вогонь буде подніаться не вгору а трохи навскіс, як ніби дме вітер. Можете поексперементувати, приминаючи також фільтр розміром 5х5.


0

0

0

0

0

0

0

0

0

0

1

1

1

1

1

1

1

1

1

1

1

1

1

1

1

При застосуванні цього фільтра полум’я буде “пожарче”. Спробуйте косою вогонь з фільтром 5×5 і т.д. (Тільки не хабивайте міняти дільник у фільтрі). Поексперементіруйте з палітрою, спробуйте в ній зробити три кольори і т.д.
Якщо у Вас щось не вийшло, надішліть мені листа з проханням надіслати повний вихідний текст проекту, який прекрасно працює, за допомогою нього можливо Вам простіше буде розібратися.

“Зіркові” вікна

Ви хочете, щоб ваш додаток було відмічено споживачем? Чималу роль в цьому відіграє оформлення. Оформлення це не тільки зручний інтерфейс і красиві картинки. Як правило запам’ятовуються додатки, які виділяються із загальної маси чимось особливим. Пропоную Вам створити форми нестандартної форми. Якщо Вас цікавить створення вікон у вигляді зірки або ще більш чудернацької форми, то розташовуйтесь зручніше і читайте цю статтю.
Почнемо ми з розгляду роботи з регіонами в Windows. Регіон в Windows являє собою деяку замкнену фігуру. Наприклад коло чи зірка, чи прямокутник і т.д. Давайте розглянемо приклади побудови таких регіонів. Перша функція для побудови регіонів:

function CreateEllipticRgn (Left,Top,Right,Bottom: integer) : HRGN

Ця функція створює область представляє собою еліпс вписання в прямокутник, координати якого задаються за допомогою параметрів Left, Top, Right, Bottom. Функція повертає дескриптор області, за допомогою якого можна буде надалі з нею працювати.

function CreateEllipticRgnIndirect( Rect: TRect) : HRGN;

Ця функція за дією аналогічна попередньої, тільки параметри прямокутника задаються структурою TRect.

function CreatePolygonRgn(const Points; nPoints, FillMode: integer):
HRGN;

Створює регіон, форма якого визначається масивом Points, який задається слеующім чином:
Points: array [1..nPoints] of TPoint;
nPoints, як Ви вже зрозуміли, ця кількість точок.
FillMode – определется яка саме область увійде в регіон. Тут як мені здається цей параметр особливого значення не має і як правило встановлюється рівним ALTERNATE.

function CreatePolyPolygonRgn(const pPtStruct; const pIntArray; nCount,
FillMode: integer): HRGN;

Створює регіон, який складається з декількох регіонів.
pPtStruct – це як би масив масивів, тобто масив елементами якого є масиви виду array [1 .. n] of TPoint, кожен такий елемент являє собою окремий регіон.
pIntArray – масив, елементи якого показують кіль вершин у відповідному регіоні
nCount – кількість поставлених регіонів.
FillMode – режим заповнення.

function CreateRectRgn (Left,Top,Right,Bottom: integer) : HRGN;

Створює прямокутний регіон.

function CreateRectRgnIndirect( Rect: TRect) : HRGN;

Теж створює прямокутний регіон, але його положення і розмір задаються через структуру
TRect.

function CreateRoundRectRgn (Left,Top,Right,Bottom, WidthEl,HeightEl:
integer) : HRGN;

Створює прямокутний регіон (розміри Left, Top, Right, Bottom) із закругленими кутами. Параметри WidthEl і HeightEl визначають параметри заокруглення.

Дані регіони можна використовувати для побудови більш складних регіонів, шляхом комбінування.

function CombineRgn( Dest, Source1, Source2: HRGN; mode: integer):
integer;

Функція комбінує два регіони Source1 і Source2 і отримує новий регіон Dest.
Mode – це режим комбінування регіонів, значення параметра наведені в таблиці.

Значення Опис
RGN_AND Результат комбінування – перетин регіонів
RGN_COPY Результат – копія регіону Source1.
RGN_DIFF Результат – частина регіону Source1 яка не
належить регіону Source2.
RGN_OR Результат – об’єднання двох регіонів
RGN_XOR Результат – об’єднання регіонів мінус їх
перетин.

Таким чином ми навчилися створювати регіони практично будь-якої форми. А тепер навчимося змінювати форму вікна. Для цього застосовується наступна функція:

function SetWindowRgn(hWnd: THandle; Rgn: HRGN; RDraw: boolean): integer;

Ця функція для вікна з дескриптором hWnd встановлює нову область відображення, яку визначає створений регіон Rgn. Регіон може бути будь-якої форми. Параметр RDraw встановлений в TRUE указує операційної системі перемалювати вікно після зміни його області відображення. Тобто тепер все що є на форму буде так само працювати як і завжди, але відображатися буде тільки те, що потрапило в зазначений регіон.

Зараз ми розглянемо ще дві корисні функції та після цього побудуємо форму у вигляді зірки, яка добре підійде для вікна About.

function PtInRegion (Rgn: HRGN; X,Y: integer): boolean;

Функція визначає потрапила точка (X, Y) в регіон Rgn.

function FrameRgn(DC:THandle;Rgn:HRGN;Brush:THandle;W,H:integer):boolean;

Функція малює рамку для регіону Rgn. DC- дескриптор щільна, на якому малюємо рамку
Brush – кисть якої малюємо,
W, H – параметри рамки (ширина і висота).

Ну а тепер побудуємо ось таку форму.

Як бачите, форма має форму зірки і перетягується тільки за заголовок, який позначений на формі синім кольором. Для початку в обробнику OnCreate ми створюємо потрібний регіон і присвоюємо його нашій формі, нижче представлений лістинг відповідної процедури:

procedure TForm1.FormCreate(Sender: TObject);
var Star1,Star2: HRGN;
Ver: array [1..11] of TPoint;
_Height,_Width,CapY: integer;
begin
BorderStyle: = bsNone; / / обов’язково встановити, щоб Windows
/ / Не заважала зі своїм відображенням кордону
CapY: = GetSystemMetrics (SM_CYCAPTION) +8; / / отримуємо висоту заголовок
/ / Створюємо регіон (зовнішній)
Ver[1]:=Point(0,3*height div 10);
Ver[2]:=Point(3*width div 10,3*height div 10);
Ver[3]:=Point(5*width div 10,0);
Ver[4]:=Point(7*width div 10,3*height div 10);
Ver[5]:=Point(width,3*height div 10);
Ver[6]:=Point(8*width div 10,5*height div 10);
Ver[7]:=Point(width,height);
Ver[8]:=Point(8*width div 10,9*height div 10);
Ver[9]:=Point(2*width div 10,9*height div 10);
Ver[10]:=Point(0,height);
Ver[11]:=Point(2*width div 10,5*height div 10);
Star1:=CreatePolygonRgn(Ver,11,ALTERNATE);

/ / Створюємо внутрішній регіон (у вигляді трикутника)
Ver[1]:=Point(5*width div 10,6*height div 10);
Ver[2]:=Point(6*width div 10,7*height div 10);
Ver[3]:=Point(4*width div 10,7*height div 10);
Star2:=CreatePolygonRgn(Ver,3,ALTERNATE);
/ / Віднімаємо із зовнішнього регіону внутрішній
CombineRgn(Star1,Star1,Star2,RGN_DIFF);
/ / Встановлюємо вікна новий регіон
SetWindowRgn(Handle,Star1,true);
DeleteObject (Star2); / / видаляємо непотрібний об’єкт
/ / Створюємо регіон для рамки (зовнішній)
_Width:=width-4; _height:=height-4;
Ver[1]:=Point(2,2+3*_height div 10);
Ver[2]:=Point(2+3*_width div 10,2+3*_height div 10);
Ver[3]:=Point(2+5*_width div 10,2);
Ver[4]:=Point(2+7*_width div 10,2+3*_height div 10);
Ver[5]:=Point(2+_width,2+3*_height div 10);
Ver[6]:=Point(2+8*_width div 10,2+5*_height div 10);
Ver[7]:=Point(2+_width,2+_height);
Ver[8]:=Point(2+8*_width div 10,2+9*_height div 10);
Ver[9]:=Point(2+2*_width div 10,2+9*_height div 10);
Ver[10]:=Point(2,2+_height);
Ver[11]:=Point(2+2*_width div 10,2+5*_height div 10);
rBound:=CreatePolygonRgn(Ver,11,ALTERNATE);

/ / Створюємо регіон для рамки (внутрішній)
_width:=width+4; _height:=height+4;
Ver[1]:=Point(5*_width div 10-2,6*_height div 10-2);
Ver[2]:=Point(6*_width div 10-2,7*_height div 10-2);
Ver[3]:=Point(4*_width div 10-2,7*_height div 10-2);
Star2:=CreatePolygonRgn(Ver,3,ALTERNATE);
CombineRgn(rBound,rBound,Star2,RGN_DIFF);
DeleteObject(Star2);

/ / Створюємо регіон для заголовка (з великої трикутника віднімаємо поменше)
/ / Причому підстави трикутників суміщені
Ver[1]:=Point(3*width div 10+4,3*height div 10);
Ver[2]:=Point(5*width div 10,4);
Ver[3]:=Point(7*width div 10-4,3*height div 10);
rTitle:=CreatePolygonRgn(Ver,3,ALTERNATE);

Ver[1]:=Point(3*width div 10+(4+CapY),3*height div 10);
Ver[2]:=Point(5*width div 10,(4+CapY));
Ver[3]:=Point(7*width div 10-(4+CapY),3*height div 10);
Star2:=CreatePolygonRgn(Ver,3,ALTERNATE);
CombineRgn(rTitle,rTitle,Star2,RGN_DIFF);
DeleteObject(Star2);
end;

Прошу не забути попередньо оголосить змінні rTitle і rBound. Це можна зробити в розділі private оголошення класу:

private
rBound,rTitle: HRGN;

Таким чином, ми створили вікно не стандартної форми, тепер навчимося малювати йому рамку і перетягувати за заголовок. Відображення рамки і заголовка проівзодітся в обробнику події
onPaint.

procedure TForm1.FormPaint(Sender: TObject);
begin
/ / Малюємо рамку. Спочатку ми малюємо широку сіру оконтовку
/ / А потім на ній більш вузьку білу, домагаючись цим
/ / Некториє подібності тривимірності.
Canvas.Brush.Color:=clGray;
FrameRgn(Canvas.Handle,
rBound,
Canvas.Brush.Handle,
2,
2);
Canvas.Brush.Color:=clWhite;
FrameRgn(Canvas.Handle,
rBound,
Canvas.Brush.Handle,
1,
1);
/ / А тепер відображаємо заголовок
Canvas.Brush.Color:=clBlue;
PaintRgn(Canvas.Handle,rTitle);
/ / Тут можна намалювати текст на загловке кнопочки і т.д.
end;

Сподіваюся лістинг коментував досить для гарного розуміння, тим більше що функції були описані в початку статті. Тепер розглянемо принцип перетягування вікна за новий заголовок, для цього нам треба обробляти повідомлення WM_NCHITTEST. МИ вже обробляли це повідомлення, коли вчилися перетягувати вікно за клієнтську область а не тільки за заголовок, тепер завдання стоїть трохи інша і з нею справляється слеующій лістинг:

procedure TForm1.WMNCHITTEST(var Msg: TWMNCHITTEST);
begin
inherited; / / викликаємо обробник за умовчанням
WITH Msg DO
/ / Переводимо екранні координати у віконні і працюємо з ними
WITH ScreenToClient(Point(XPos,YPos)) DO
/ / Якщо натиснення було над регіоном заголовка
/ / То дати знати про це Windows, інше вона зробить за вас
IF PtInRegion(rTitle, X, Y) THEN
Result := htCaption;
end;

Також перед запуском програми слід передбачити його закриття, для цього киньте на форму кнопку і пристосуйте її для закриття вікна. Слід пам’ятати, що в результуючому вікні буде відображатися все, що потрапляє в новий регіон, тобто ви можете також майструвати інтерфейс в середовищі Delphi, створювати кнопки, статичний текст і т.д., але в результаті буде відображено тільки те, що потрапить межі нового регіону. Будьте уважні. А тепер можете приступити до створення своїх вікон незвичайної форми.

Кілька графічних ефектів

Думаю все Ви не раз використовували популярну програму PhotoShop. Скажіть, а нехотелі б самим написати програму, яка б робила щось подібне, в сенсі ефектів які виходять при фільтрації зображення. Якщо так, то ця стаття допоможе вам почати ваше нелегка справа, а далі, як кажуть, дерзайте самі. Відразу пропоную всім, хто досить обізнаний у цій області, надіслати принцип створення інших ефектів, буду Вам дуже вдячний. У перших двох ефект використовується принцип фільтрації розказаний в розділі “Вогненні літери (ефект вогню) “, якщо ви її не читали, то раджу для початку переглянути її, хоча б те, що стосується теорії фільтрації.

Звичайне розмиття зображення.
Для розмиття може застосовуватися наступний фільтр:


a

a

a

a

0

a

a

a

a

де а-це якесь ціле число, обчних просто 1. При застосуванні цієї матриці до зображенню слід розділити отриманий результат на величину a * 8 (величина a знаходиться в восьми клітинах). Якщо а дорівнює 1 то, можна просто зрушити результат на 3 розряду вправо (8 – це третій ступінь двійки). Для здійснення розмиття фільтр застосовується послідовно до кожної складової кольору зображення (RGB – червоний, зелений, синій), а потім в результаті отримуємо результуючий колір. Нижче наведено лістинг, який реалізує ефект розмиття.

procedure TForm1.Button1Click(Sender: TObject);
var i,j: integer;
Red,Green,Blue: byte;
W,H: integer;
filter: array [1..3,1..3] of integer;
begin
filter[1,1]:=1; filter[1,2]:=1; filter[1,3]:=1;
filter[2,1]:=1; filter[2,2]:=0; filter[2,3]:=1;
filter[3,1]:=1; filter[3,2]:=1; filter[3,3]:=1;
W:=Image1.Picture.Bitmap.Width-1;
H:=Image1.Picture.Bitmap.Height-1;
for i:=2 to W do
for j:=2 to H do
begin
with Image1.Picture.Bitmap.Canvas do
begin
/ / Вважаємо червону складову
Red:=((GetRValue(Pixels[i-1,j-1])*filter[1,1]+
GetRValue(Pixels[i,j-1])*filter[1,2]+
GetRValue(Pixels[i+1,j-1])*filter[1,3]+
GetRValue(Pixels[i-1,j])*filter[2,1]+
GetRValue(Pixels[i,j])*filter[2,2]+
GetRValue(Pixels[i+1,j])*filter[2,3]+
GetRValue(Pixels[i-1,j+1])*filter[3,1]+
GetRValue(Pixels[i,j+1])*filter[3,2]+
GetRValue(Pixels[i+1,j+1])*filter[3,3]) div 8 );
/ / Вважаємо зелену складову
Green:=((GetGValue(Pixels[i-1,j-1])*filter[1,1]+
GetGValue(Pixels[i,j-1])*filter[1,2]+
GetGValue(Pixels[i+1,j-1])*filter[1,3]+
GetGValue(Pixels[i-1,j])*filter[2,1]+
GetGValue(Pixels[i,j])*filter[2,2]+
GetGValue(Pixels[i+1,j])*filter[2,3]+
GetGValue(Pixels[i-1,j+1])*filter[3,1]+
GetGValue(Pixels[i,j+1])*filter[3,2]+
GetGValue(Pixels[i+1,j+1])*filter[3,3]) div 8 );
/ / Вважаємо синю складову
Blue:=((GetBValue(Pixels[i-1,j-1])*filter[1,1]+
GetBValue(Pixels[i,j-1])*filter[1,2]+
GetBValue(Pixels[i+1,j-1])*filter[1,3]+
GetBValue(Pixels[i-1,j])*filter[2,1]+
GetBValue(Pixels[i,j])*filter[2,2]+
GetBValue(Pixels[i+1,j])*filter[2,3]+
GetBValue(Pixels[i-1,j+1])*filter[3,1]+
GetBValue(Pixels[i,j+1])*filter[3,2]+
GetBValue(Pixels[i+1,j+1])*filter[3,3]) div 8 );
/ / Відображаємо результуючу точку
Image2.Picture.Bitmap.Canvas.Pixels[i,j]:=
RGB(Red,Green,Blue);
end;
end;

Тут Image1 – це об’єкт TImage містить вихідне зображення, а Image2 – результуюче.
А це обробник OnCreate для форми.

procedure TForm1.FormCreate(Sender: TObject);
var Bmp: TBitmap;
begin
Image1.Picture.Bitmap.PixelFormat:=pf24bit;
Bmp:= TBitmap.Create;
Bmp.Width:=Image1.Picture.Bitmap.Width;
Bmp.Height:=Image1.Picture.Bitmap.Height;
Bmp.PixelFormat:=pf24bit;
Image2.Picture.Bitmap:=Bmp;
end;

Для отримання більшої ступеня розмиття слід застосувати ефект до результату і так далі, а також можна використовувати матрицю не 3х3 а 5х5, але при цьому слід враховувати, що якщо суму всіх елементів матриці поділити на наш дільник (у прикладі це 8) повинно вийти 1, інакше можливі непередбачені результати обробки зображення.

Тиснення зображення.
Це дуже цікавий ефект, коли все зображене як би видавлюється з полотна. Розумію що пояснив незрозуміло, але спробуйте написати самі цей ефект і ви не розчаруєтеся. Тут використовується матриця такого вигляду:


а

a

a

a

0

-a

-a

-a

-a

Дільник залишається тим самим (8 якщо а = 1). Тепер якщо суму елементів матриці поділити на наш дільник, то вийти 0. Таким чином можете поекспериментувати над матрицею і дільником, але щоб в результаті подібного обчислення виходив саме 0. І ще є одна особливість, але це буде виділено окремо в лістингу:

procedure TForm1.Button1Click(Sender: TObject);
var i,j: integer;
Red,Green,Blue: byte;
W,H: integer;
filter: array [1..3,1..3] of integer;
begin
filter[1,1]:=1; filter[1,2]:=1; filter[1,3]:=1;
filter[2,1]:=1; filter[2,2]:=0; filter[2,3]:=-1;
filter[3,1]:=-1; filter[3,2]:=-1; filter[3,3]:=-1;
W:=Image1.Picture.Bitmap.Width-1;
H:=Image1.Picture.Bitmap.Height-1;
for i:=2 to W do
for j:=2 to H do
begin
with Image1.Picture.Bitmap.Canvas do
begin
/ / Вважаємо червону складову
Red:=((GetRValue(Pixels[i-1,j-1])*filter[1,1]+
GetRValue(Pixels[i,j-1])*filter[1,2]+
GetRValue(Pixels[i+1,j-1])*filter[1,3]+
GetRValue(Pixels[i-1,j])*filter[2,1]+
GetRValue(Pixels[i,j])*filter[2,2]+
GetRValue(Pixels[i+1,j])*filter[2,3]+
GetRValue(Pixels[i-1,j+1])*filter[3,1]+
GetRValue(Pixels[i,j+1])*filter[3,2]+
GetRValue(Pixels[i+1,j+1])*filter[3,3]) div 8 );
/ / Вважаємо зелену складову
Green:=((GetGValue(Pixels[i-1,j-1])*filter[1,1]+
GetGValue(Pixels[i,j-1])*filter[1,2]+
GetGValue(Pixels[i+1,j-1])*filter[1,3]+
GetGValue(Pixels[i-1,j])*filter[2,1]+
GetGValue(Pixels[i,j])*filter[2,2]+
GetGValue(Pixels[i+1,j])*filter[2,3]+
GetGValue(Pixels[i-1,j+1])*filter[3,1]+
GetGValue(Pixels[i,j+1])*filter[3,2]+
GetGValue(Pixels[i+1,j+1])*filter[3,3]) div 8 );
/ / Вважаємо синю складову
Blue:=((GetBValue(Pixels[i-1,j-1])*filter[1,1]+
GetBValue(Pixels[i,j-1])*filter[1,2]+
GetBValue(Pixels[i+1,j-1])*filter[1,3]+
GetBValue(Pixels[i-1,j])*filter[2,1]+
GetBValue(Pixels[i,j])*filter[2,2]+
GetBValue(Pixels[i+1,j])*filter[2,3]+
GetBValue(Pixels[i-1,j+1])*filter[3,1]+
GetBValue(Pixels[i,j+1])*filter[3,2]+
GetBValue(Pixels[i+1,j+1])*filter[3,3]) div 8 );
/ / Це та фішка про яку було сказано раніше
Red:=Red+128; if Red>255 then Red:=255;
Green:=Green+128; if Green>255 then Green:=255;
Blue:=Blue+128; if Blue>255 then Blue:=255;
/ / Відображаємо результуючу точку
Image2.Picture.Bitmap.Canvas.Pixels[i,j]:=
RGB(Red,Green,Blue);
end;
end;

Як бачите лістинг практічсекі повністю повторює лістинг розмиття за винятком пари моментів. Так що можете трохи видозмінити програму і отримати абсолютно новий ефект.

Розумієте по Гауса
Цей алгоритм був узятий з продукту “Поради щодо Delphi “, який ви можете завантажити за адресою http://www.webinspector.com/delphi. Тому тут я не буду приводити будь-яких коментарів, а лише наведу приклад лістингу, за роз’ясненнями звертайтеся за вказаною адресою.

unit GBlur2;

interface

uses Windows, Graphics;

type
PRGBTriple = ^TRGBTriple;
TRGBTriple = packed record
b: byte; / / легше для використання ніж типу rgbtBlue …
g: byte;
r: byte;
end;
PRow = ^TRow;
TRow = array[0..1000000] of TRGBTriple;
PPRows = ^TPRows;
TPRows = array[0..1000000] of PRow;

const MaxKernelSize = 100;

type

TKernelSize = 1..MaxKernelSize;
TKernel = record
Size: TKernelSize;
Weights: array[-MaxKernelSize..MaxKernelSize] of single;
end;
/ / Ідея полягає в тому, що при використанні TKernel ми ігноруємо
/ / Weights (вага), за винятком Weights в діапазоні-Size .. Size.

procedure GBlur(theBitmap: TBitmap; radius: double);

implementation

uses SysUtils;

procedure MakeGaussianKernel(var K: TKernel; radius: double;
MaxData, DataGranularity: double);
/ / Робимо K (гауссових зерно) із середньоквадратичним відхиленням = radius.
/ / Для поточного додатка ми встановлюємо змінні MaxData = 255,
/ / DataGranularity = 1. Тепер у процедурі встановимо значення
/ / K.Size так, що при використанні K ми будемо ігнорувати Weights (вага)
/ / З найменш можливими значеннями. (Малий розмір нам на користь,
/ / Оскільки час виконання безпосередньо залежить від
/ / Значення K.Size.)
var j: integer;
temp, delta: double;
KernelSize: TKernelSize;
begin
for j:= Low(K.Weights) to High(K.Weights) do begin
temp:= j/radius; K.Weights[j]:= exp(- temp*temp/2);
end;
/ / Робимо так, щоб sum (Weights) = 1:

temp:= 0;
for j:= Low(K.Weights) to High(K.Weights) do
temp:= temp + K.Weights[j];
for j:= Low(K.Weights) to High(K.Weights) do
K.Weights[j]:= K.Weights[j] / temp;

/ / Тепер відкидаємо (або робимо відмітку “Ігнорувати”
/ / Для змінної Size) дані, що мають відносно невелике значення –
/ / Це важливо, в іншому випадку смазаваніе походимо з малим радіусом і
/ / Тій області, яка “захоплюється” великим радіусом …
KernelSize:= MaxKernelSize;
delta:= DataGranularity / (2*MaxData);
temp:= 0;
while (temp < delta) and (KernelSize > 1) do begin
temp:= temp + 2 * K.Weights[KernelSize]; dec(KernelSize);
end;
K.Size:= KernelSize;
/ / Тепер для коректності повертається результату проводимо ту ж
/ / Операцію з K.Size, так, щоб сума всіх даних була дорівнює одиниці:

temp:= 0;
for j:= -K.Size to K.Size do
temp:= temp + K.Weights[j];
for j:= -K.Size to K.Size do
K.Weights[j]:= K.Weights[j] / temp;
end;

function TrimInt(Lower, Upper, theInteger: integer): integer;
begin
if (theInteger <= Upper) and (theInteger >= Lower) then
result:= theInteger else
if theInteger > Upper then
result:= Upper
else result:= Lower;
end;

function TrimReal(Lower, Upper: integer; x: double): integer;
begin
if (x < upper) and (x >= lower) then
result:= trunc(x) else
if x > Upper then
result:= Upper else
result:= Lower;
end;

procedure BlurRow(var theRow: array of TRGBTriple; K: TKernel; P: PRow);
var j, n, LocalRow: integer;
tr, tg, tb: double; / / tempRed та ін
w: double;
begin

for j:= 0 to High(theRow) do
begin
tb:= 0;
tg:= 0;
tr:= 0;
for n:= -K.Size to K.Size do begin
w:= K.Weights[n];
/ / TrimInt задає відступ від краю рядка …
with theRow[TrimInt(0, High(theRow), j – n)] do begin
tb:= tb + w * b;
tg:= tg + w * g;
tr:= tr + w * r;
end;
end;
with P[j] do begin
b:= TrimReal(0, 255, tb);
g:= TrimReal(0, 255, tg);
r:= TrimReal(0, 255, tr);
end;
end;
Move(P[0], theRow[0], (High(theRow) + 1) * Sizeof(TRGBTriple));
end;

procedure GBlur(theBitmap: TBitmap; radius: double);
var Row, Col: integer;
theRows: PPRows;
K: TKernel;
ACol: PRow; P:PRow;
begin
if (theBitmap.HandleType <> bmDIB)
or (theBitmap.PixelFormat <> pf24Bit) then
raise exception.Create
(‘GBlur може працювати тільки з 24-бітними зображеннями ‘);
MakeGaussianKernel(K, radius, 255, 1);
GetMem(theRows, theBitmap.Height * SizeOf(PRow));
GetMem(ACol, theBitmap.Height * SizeOf(TRGBTriple));

/ / Запис позиції даних зображення:
for Row:= 0 to theBitmap.Height – 1 do
theRows[Row]:= theBitmap.Scanline[Row];
/ / Розмиваємо кожну строчку:
P:= AllocMem(theBitmap.Width*SizeOf(TRGBTriple));
for Row:= 0 to theBitmap.Height – 1 do
BlurRow(Slice(theRows[Row]^, theBitmap.Width), K, P);
/ / Тепер розмиваємо кожну колонку
ReAllocMem(P, theBitmap.Height*SizeOf(TRGBTriple));
for Col:= 0 to theBitmap.Width – 1 do
begin
/ / – Зчитуємо першу колонку в TRow:
for Row:= 0 to theBitmap.Height – 1 do
ACol[Row]:= theRows[Row][Col];

BlurRow(Slice(ACol^, theBitmap.Height), K, P);
/ / Тепер поміщаємо оброблений стовпець на своє
/ / Місце в дані зображення:
for Row:= 0 to theBitmap.Height – 1 do
theRows[Row][Col]:= ACol[Row];end;

FreeMem(theRows);
FreeMem(ACol);
ReAllocMem(P, 0);
end;

end.

А використовувати цей модуль можна таким чином

procedure TForm1.Button1Click(Sender: TObject);
var b: TBitmap;
begin
if not openDialog1.Execute then exit;
b:= TBitmap.Create;
b.LoadFromFile(OpenDialog1.Filename);
b.PixelFormat:= pf24Bit;
Canvas.Draw(0, 0, b);
GBlur(b, StrToFloat(Edit1.text));
Canvas.Draw(b.Width, 0, b);
b.Free;
end;

Поексперіментіруйет з другим параметром процедури GBlur, для отримання різних ступенів розмиття.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*