Гильоши

Гильоши — це характерні візерунки на гроші і цінні папери. Вони красиві, і поєднують в собі одночасно помітну складність з внутрішньої простотою — коли здається, що ти ось-ось вловиш принцип, але він щоразу вислизає від тебе. Можливо, саме це і є визначення краси.

Оскільки гильоши використовувалися як засіб захисту цінних паперів від підробки, все, що пов'язано з їх використанням, було засекречено, і інформацію доводиться збирати по крихтах.

Сама назва походить від прізвища французького інженера Гилльо (Guillot), про який не залишилося ніяких відомостей. Не виключено, що це взагалі чисто міфічна фігура.

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

Приблизно в середині XIX століття комусь прийшла в голову ідея пристосувати такий верстат для генерації складних візерунків на друкарською платівці. Швидше за все, це були американці, оскільки на їхніх грошах гильоши з'явилися раніше всього — на випуску 1862 року, і дуже швидко стали одним з головних елементів дизайну.

image

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

На російських грошах гильоши вперше були надруковані у випуску 1892 року.
image

Якщо розглядати самі візерунки на гроші різних країн, то можна помітити, що, незважаючи на загальний принцип, вони досить сильно різняться. Звідси можна зробити висновок, що кожен розробляв відповідні верстати самостійно. Помітна і кореляція — чим розвиненіша країна, тим її гильоши зроблені краще. Для прикладу достатньо подивитися на румунські гроші часів Чаушеску, де для гильошировочной машини, схоже, використовувалися деталі від трактора.

image

Створення гильошей було складною справою — настільки складним, що цим займалися окремі люди, що мали особливу професію гильошировщика. За деякими відомостями можна зрозуміти, що гильошировщики малювали гильош за ескізом художника — це видно з приводиться приклад начерку і готової банкноти в 1 червінець 1926 року. Не здивуюся, якщо вони це робили шляхом механічної підгонки деталей, а то і взагалі перебирали машину заново.




Зараз гильоши з моди вийшли, оскільки вони можуть протистояти тільки малювання грошей руками — спробуй чорнилом викреслити всі ці дрібні криві. При використанні оптичного копіювання вони марні — сканера все одно, що читати: візерунки або портрети. Так що на сучасних банкнотах гильошей зазвичай вже немає або вони скромно стирчать де-небудь у кутку як данина традиції.

Обчислити алгоритм для малювання гильошей вкрай складно — хоча на вигляд вони здаються досить простими. У багатьох джерелах пишуть, що це всього лише ускладнена версія спірометру з декількома колесами, але я в цьому дуже сумніваюся. Многоколесный спірограф нескладно повторити на комп'ютері, але виходять візерунки по-перше, анітрохи гильоши не нагадують, а по-друге, їх неможливо підігнати до заздалегідь заданих контурах.

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

Насправді принцип побудови візерунків досить простий, але кілька странноват. Вони вважаються за точками, причому простих алгебраїчних операцій над трьома кривими (внутрішнім, зовнішнім контуром і синусоїда, стелящейся між ними) недостатньо — потрібно ще вирішувати рівняння, щоб визначити перетин лінії та кривої.

Власне, в кожній точці ми ставимо крапку синусоїди, але кожен раз різною — як би модульованої поточним станом обмежують контурів.

Як саме необхідні обчислення виконувалися на механічних верстатах, вище мого розуміння.

Отже, принцип малювання. Ми почнемо з горизонтального гильоша, тому що він простіше. Увага на екран.



Зеленим кольором виділені контури, що обмежують наш гильош. Будемо вважати їх нижнім і верхнім і розрахуємо значення точки гильоша в абсциссе t.

Насамперед знайдемо точки PB і PT — точки контурів при t. Заодно вирахуємо в цьому місці вектори напрями кривих.

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

Вирахуємо в цій точці вектор напрямку як середній між векторами в точках PB і PT. Знайдемо для нього перпендикуляр.

Порахуємо значення самої звичайної синусоїди в точці t (в припущенні, що вона починається в точці 0,0 і рухається вздовж осі ординат вправо).

Тепер исказим цю синусоїду наступним чином: перенесемо поточне значення аргументу (t,0) у точку Mid, її віссю X зробимо «середній вектор», а віссю Y, відповідно, перпендикуляр до нього. Тоді її поточна точка опиниться в точці RP.

Залишилося тільки змодулювати амплітуду цієї синусоїди. Порахуємо відстань Mid--IP, тобто відстань від середньої точки до першої точки перетину перпендикуляра з контуром. Який контур брати, верхній або нижній, ми визначимо за значенням вихідної синусоїди — вона знаходиться вище осі ординат або нижче. На рисунку зображено випадок, коли значення еталонної синусоїди більше нуля і ми використовуємо верхній контур.

Тепер масштабується Mid--RP, вважаючи відстань Mid--IP одиницею, і відкладемо це значення вздовж нової осі ординат, тобто по перпендикуляру. Ми отримаємо точку GP — вона і буде шуканої точкою гильоша.

Перейдемо до програмування. Ось програма малювання горизонтального гильоша мовою Asymptote:

import graph;
import wave;

size(1000,1000);
xaxis(ticks=Ticks);
yaxis(ticks=Ticks);

defaultpen(2);

var zero = (0,0);

typedef pair pairf(real x);

///////////////////////////////////////////

// Одиничний вектор, перпендикулярний до вектора (0,0)--v
pair orthogonal(pair v)
{
return unit((-v.y,v.x));
}

// Точка і напрям кривої в точці з координатами x,path(x)
pair[] pt_and_dir(path p, real x)
{
var t = times(p,x)[0];
return new pair[]{point(p,t), dir(p,t)};
}

// Функція, що розраховує точку гильоша в точці з абсцисою t
pairf between(path top, path bottom, real topk=0.5, real phase, real omega)
{
return new pair(real t)
{
// Точка і напрямок верхнього і нижнього контуру
var pdt = pt_and_dir(top,t);
var pdb = pt_and_dir(bottom,t);

// "Середня" точка як суміш з верхньої та нижньої
var mid = topk*pdt[0] + (1-topk)*pdb[0];
// Виведемо для наочності середню точку
draw(mid,gray);
// Вектор, перпендикулярний суміші напрямків верхньої і нижньої точки
var ort = orthogonal(topk*pdt[1] + (1-topk)*pdb[1]);

// Точка на звичайній синусоїді, яку ми зараз будемо модулювати
var f = sin(phase+omega*t);

// Вектор, у бік якого модуляція відхилить точку
var rp = rotate(degrees(atan2(ort.y,ort.x)-pi/2),zero) * (0,f);

// Розраховуємо точку перетину перпендикулярного вектора з одним з контурів
// Для цього дізнаємося всі точки перетину і беремо з них найближчу
var ipath = rp.y >= 0 ? top : bottom;
var inter = intersections(ipath,mid,mid+ort);
var interp = sequence(new pair(int i) {return point(ipath,inter[i]); }, inter.length);
interp = sort(interp, new bool(a pair, pair b) {return abs(mid.x-a.x) < abs(mid.x-b.x); });
var ip = interp[0];

// Дізнаємося відстань від "середньої точки" до точки перетину перпендикулярного вектора з контуром
var r = sqrt((mid.x-ip.x)*(mid.x-ip.x)+(mid.y-ip.y)*(mid.y-ip.y));

// Модулируем синусоїду
return mid+r*rp;
};
}

// Малюємо гильош кілька разів зі зсувом
void repeat(int n, path top, path bottom, real topk, real freq)
{
var step = 2pi/n;
for (var i: sequence(0,n))
{
draw(graph(between(top,bottom, topk, i*step, freq), 0,8, 500));
}
} 

// Верхній контур - просто щось типу синусоїди
path top = shift(0,0.3)*(4*make_wave(1.4, (+1,+0.7),(+1,-0.7) ));

// Нижній - пряма
path bottom = (0,0)--(8,0);

draw(top, blue);
draw(bottom, blue);

// укладаємо між ними 9 "синусоїд" з частотою 11, проходять точно посередині між верхнім і нижнім контуром
repeat(9, top, bottom, 0.5, 11);


А ось результат її роботи. Витончено.



З круговими гильошами — як раз тими, що зазвичай фігурують на грошах — справа ускладнюється. Принцип залишається тим же, але…

1. Замість декартових координат доводиться працювати в полярних
2. Криві стають примхливими і починають себе погано вести. При більш-менш складних контурах в деяких точках виникає ситуація, коли перпендикуляр зовсім не перетинається з потрібним контуром. Очевидно, в таких випадках і потрібне багаторічний досвід гильошировщика.

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

import graph;
import wave;

size(1000,1000);

xaxis(ticks=Ticks);
yaxis(ticks=Ticks);

defaultpen(5);

var zero = (0,0);

typedef pair pairf(real x);
typedef pair[] pairaf(real t);

///////////////////////////////////////////

// Визначення вектора перпендикулярного
pair orthogonal(pair v)
{
return unit((-v.y,v.x));
}

// Переобчислення полярних координат в декартовые
pair cart(a real, real r)
{
return (r*cos(a), r*sin(a));
}

// Нормалізований кут по вектору-направленню
real atan2p(pair v)
{
var a = atan2(v.y,v.x);
return a<0 ? a+2pi : a;
}

// Відстань між двома точками
real distance(a pair, pair b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

// Крапка і вектор напрямку на шляху p при полярному куту a
pair[] pt_and_dir(path p, a real)
{
var ii = intersections(p,--zero cart(a,100));
if (ii.length==0)
{
write(p);
write(a);
}
var t = ii[0];
return new pair[]{point(p,t[0]), dir(p,t[0])};
}

// Обчислення точок гильоша, якщо є функція midpoint, що розраховує координати і напрям в середній точці
pairf between(path top, path bottom, pairaf midpoint, real phase, real omega)
{
return new pair(real t)
{
var b = midpoint(t);
var mid = b[0];
draw(mid,green);
var mid_dir = b[1];
var f = sin(phase+omega*t);

var angle = (degrees(atan2p(mid_dir))+180) % 360;

var rp = rotate(angle,zero) * (0,f);

//На який контур дивиться вектор нашої точки синусоїди - на верхній або нижній?
var ipath = distance(mid+rp,zero) > distance(mid,zero) ? top : bottom;

// Знайдемо точки перетину перпендикуляра з контуром
var inter = intersections(ipath,mid,mid+rp);

// Ось тут-то і засідка. Перетинів з контуром може взагалі не бути. Виводимо тоді лінію протилежного кольору, 
// щоб це було відразу помітно
if (inter.length==0)
{
draw(mid--mid+rp,magenta,Arrow);
return mid;
}

// Відсортуємо точки перетину і знайдемо саму ближню
var interp = sequence(new pair(int i) {return point(ipath,inter[i]); }, inter.length);
interp = sort(interp, new bool(a pair, pair b) {return distance(mid,a) < distance(mid,b); });
var ip = interp[0];

var r = distance(mid,ip);
return mid + r*rp;
};
}

// Обчислення точок гильоша, якщо задано середній шлях
pairf between(path top, path bottom, path base, real phase, real omega)
{
pairaf mf = new pair[](real t) { return pt_and_dir(base, t); };
return between(top, bottom, mf, phase, omega);
}

// Обчислення точок гильоша, якщо даний коефіцієнт змішування верхнього і нижнього контурів
pairf between(path top, path bottom, real topk=0.5, real phase, real omega)
{
pairaf midpoint = new pair[](real t) 
{
var pdt = pt_and_dir(top,t);
var pdb = pt_and_dir(bottom,t);
var mid = topk*pdt[0] + (1-topk)*pdb[0];
var mid_dir = topk*pdt[1] + (1-topk)*pdb[1];
return new pair[]{mid, mid_dir}; 
};
return between(top, bottom, midpoint, phase, omega);
}

// Малювання гильоша з обчисленням середньої лінії (як у програмі з горизонтальним гільошем)
void repeat(int n, path top, path bottom, real topk, real freq)
{
var step = 2pi/n;
for (var i: sequence(0,n-1))
{
draw(graph(between(top,bottom, topk, i*step, freq), 0, 2pi, 500));
}
}

// Варіант - середня лінія не обчислюється, а задається окремим шляхом
void repeat(int n, path top, path bottom, path base, real freq)
{
var step = 2pi/n;
for (var i: sequence(0,n-1))
{
draw(graph(between(top,bottom,base, i*step, freq), 0, 2pi, 500));
}
} 

// Контури - зовнішній і внутрішній. Щоб не возитися з обчисленням їх координат, я просто обвів їх у векторному редакторі,
// зберіг як SVG витяг з тексту опис шляху
path top = (458.43,237.715)..controls (468.922,264.461) and (481.563,290.133)..(466.797,322.145)
..controls (438.688,358.184) and (392.762,345.094)..(362.438,351.945)
..controls (354.488,353.742) and (350.508,354.398)..(342.07,358.234)
..controls (338.023,360.074) and (333.797,358.609)..(329.125,358.598)
..controls (324.457,358.582) and (319.348,360.02)..(315.824,363.094)
..controls (306.16,371.52) and (294.707,387.746)..(278.176,400.949)
..controls (261.645,414.152) and (250.875,417.965)..(236.914,417.996)
..controls (222.957,418.023) and (213.074,414.152)..(196.543,400.949)
..controls (180.012,387.746) and (168.559,371.52)..(158.895,363.094)
..controls (155.371,360.02) and (150.262,358.582)..(145.594,358.598)
..controls (140.922,358.609) and (136.695,360.074)..(132.648,358.234)
..controls (124.211,354.398) and (120.23,353.742)..(112.281,351.945)
..controls (81.957,345.094) and (36.0313,358.184)..(7.92188,322.145)
..controls (-6.84375,290.133) and (5.79688,264.461)..(16.2891,237.715)
..controls (19.1992,230.289) and (11.7227,218.836)..(11.7227,209.773)
..controls (11.7227,200.711) and (19.1992,188.906)..(16.2891,181.48)
..controls (5.79688,154.734) and (-6.84375,129.063)..(7.92188,97.0508)
..controls (36.0313,61.0078) and (81.957,74.0977)..(112.281,67.2461)
..controls (120.23,65.4531) and (124.211,64.7969)..(132.648,60.9609)
..controls (136.695,59.1211) and (140.922,60.582)..(145.594,60.5977)
..controls (150.262,60.6094) and (155.371,59.1719)..(158.895,56.1016)
..controls (168.559,47.6719) and (180.012,31.4492)..(196.543,18.2461)
..controls (213.074,5.03906) and (222.957,1.16797)..(236.914,1.19922)
..controls (250.875,1.22656) and (261.645,5.03906)..(278.176,18.2461)
..controls (294.707,31.4492) and (306.16,47.6719)..(315.824,56.1016)
..controls (319.348,59.1719) and (324.457,60.6094)..(329.125,60.5977)
..controls (333.797,60.582) and (338.023,59.1211)..(342.07,60.9609)
..controls (350.508,64.7969) and (354.488,65.4531)..(362.438,67.2461)
..controls (392.762,74.0977) and (438.688,61.0078)..(466.797,97.0508)
..controls (481.563,129.063) and (468.922,154.734)..(458.43,181.48)
..controls (455.52,188.906) and (462.996,200.238)..(463.055,209.359)
..controls (463.113,218.48) and (455.52,230.289)..(458.43,237.715)
--cycle;

path bottom = (435.121,232.246)..controls (424.465,250.848) and (436.73,269.879)..(418,294.242)
..controls (399.266,318.602) and (368.48,321.363)..(355.004,331.102)
..controls (341.531,340.84) and (349.316,349.461)..(338.301,353.699)
..controls (327.289,357.934) and (311.055,338.211)..(297.848,342.762)
..controls (277.797,349.668) and (257.313,362.477)..(235.926,362.551)
..controls (214.543,362.629) and (196.012,350.156)..(175.961,343.25)
..controls (162.75,338.699) and (146.52,358.422)..(135.504,354.188)
..controls (124.492,349.949) and (132.277,341.328)..(118.805,331.59)
..controls (105.328,321.852) and (74.5391,319.09)..(55.8086,294.73)
..controls (37.0742,270.367) and (49.3438,251.336)..(38.6875,232.734)
..controls (33.918,224.414) and (17.0078,213.824)..(17.0078,209.363)
..controls (17.0078,204.902) and (33.9258,194.051)..(38.6953,185.727)
..controls (49.3555,167.129) and (37.082,148.094)..(55.8242,123.734)
..controls (74.5625,99.375) and (105.359,96.6094)..(118.84,86.875)
..controls (132.32,77.1367) and (124.531,68.5117)..(135.547,64.2773)
..controls (146.566,60.043) and (162.805,79.7617)..(176.016,75.2148)
..controls (196.078,68.3086) and (214.613,55.832)..(236.008,55.9102)
..controls (257.398,55.9883) and (277.891,68.7969)..(297.953,75.7031)
..controls (311.164,80.2539) and (327.402,60.5313)..(338.418,64.7656)
..controls (349.438,69.0039) and (341.648,77.625)..(355.129,87.3633)
..controls (368.609,97.0977) and (399.406,99.8633)..(418.145,124.223)
..controls (436.887,148.586) and (424.613,167.617)..(435.273,186.219)
..controls (440.043,194.539) and (456.961,205.215)..(456.926,209.535)
..controls (456.887,213.859) and (439.891,223.926)..(435.121,232.246)
--cycle;

// Підганяємо під масштаб нашого малюнка
top = scale(1/10)*top;
bottom = scale(1/10)*bottom;

// І зміщуємо контури з центру координат в лівий верхній квадрант
var min = min(top);
var max = max(top);
top=shift(-(max.x-min.x)/2, -(max.y-min.y)/2)*top;

min = min(bottom);
max = max(bottom);
bottom=shift(-(max.x-min.x)/2-min.x -(max.y-min.y)/2-min.y)*bottom;

draw(top, blue);
draw(bottom, blue);

// Пускаємо 4 синусоїди
repeat(4, top, bottom, 0.5, 18);

Результат — щось схоже на розетку з купона в 25 білоруських рублів 1992 року. Можна зробити ще схожою, якщо самому акуратно намалювати середню лінію (див. другий малюнок).







Деякі лінії на нашому малюнку вийшли з щербинами — це через брак точності обчислень. В якійсь мірі їх можна виправити, поставивши в операторі draw розрахунок не 500, а 10000 точок.

Що можна ще придумати з гильошами?

По-перше, якщо уважно подивитися на білоруську версію, то помітно, що тамтешні візерунки ще змінюють свою частоту — вони стають то густіше, то рідше. Як враховувати частоту в моїй програмі — я так і не придумав.

По-друге, гильоши прямо-таки напрошуються бути збережені у вигляді кривих Безьє — вони дуже добре ними апроксимуються. Але для цього треба розуміти, як вони себе ведуть — де у них вершини, де точки перегину. Обчислювати це з набору точок як-то нерозумно, а як підійти до питання математично, наприклад, порахувавши похідну, — незрозуміло.

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

P. S. Малюнки у статті отмасштабированы, щоб не псувати загальний вигляд сторінки. Ви можете відкрити їх окремо і розглянути з кращою роздільною здатністю.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.