Если кому-то надо будет нарисовать юзербар, то он откроет GIMP (или любой другой графический редактор) и нарисует в нем несколько слоев со штрихами/градиентами и отблесками. А что если надо создать сразу много юзербаров? К примеру, генерировать новую картинку при смене трека на интернет-радио? В этой статье я поделюсь небольшой методикой как это можно сделать. Сразу скажу, что вся графика у нас будет процедурной и руками не нужно ничего рисовать.
Обычно юзербар состоит из: фоновой картинки, фоновой штриховки, блеска и надписи. С надписью понятно — это будет название трека, который на данный момент находится в эфире. А остальным займемся прямо сейчас.
1. Поиск фона
Можно открыть Google Images, ввести в качестве запроса волшебное слово Nature и насладиться замечательными картинами природы. Но картин слишком много, какую выбрать? Да и какую картинку не выбери, она все равно быстро надоест. Как вариант, можно использовать несколько картинок. К примеру, я пошел в магазин с картинками и затарился там фотосетами. Осталось нарезать. Отрезать кусочек картинки легко, но какой именно? Вдруг там будет синее небо, а разве нам нужен задник из синего цвета? Что же отрезать? Мне известно 2 варианта:
- Выделить края (к примеру, пройтись по картинке матрицей свертки) и найдя наиболее «контрастный» регион вырезать его. Можно искать какие-то конкретные формы, скажем горизонтальные линии. Можно очень долго играть с фильтрами изображений и подбирать их последовательности, подбирая такие параметры, которые будут давать самую красивую часть картинки. Способ гибкий, но я в свое время наигрался с фильтрацией изображений и нырять в это больше не хотелось.
- Сжать картинку в JPEG с низким качеством, посмотреть сколько байт занимает результат. Если картинка весит немного, то скорее всего ничего интересного там нет, если картинка весит больше — значит она более детальная. Гибкости нет, зато реализовывается в 1 строчку. Этот способ я и буду использовать.
В ходе экспериментов была подобрана граница, за которой картинку не стоит считать за подходящую, равно как и обнаружено, что часть картинок имеет контур/рамочку/копирайт, которым на юзербарах делать нечего, а следовательно проще отрезать по 10% с каждой стороны, чем руками просматривать и удалять брак. Это попило у меня много времени, но в итоге появилось:
use GD;
use utf8;
opendir(mo,".");
$maxWidth=352; # ширина картинки. Кто догадается, почему такая?
$maxHeight=32; # высота картинки. Тоже не стандарт, почему?
$border=.1; # Отрезаем по 10%
$uid=0;
@files=sort map{"".$_}grep{/jpe?g|png|gif/i}readdir(mo);
$res=GD::Image->new($maxWidth,$maxHeight,1);
$tmp=GD::Image->new($maxWidth,2000,1);
$count=0;
foreach(@files){
print $_." (".($count++)."/".@files.")n";
$img=GD::Image->new($_);
next if ! defined $img;
($imgWidth,$imgHeight)=$img->getBounds;
$ox=int($imgWidth*$border);
$oy=int($imgHeight*$border);
next if $imgWidth-$ox*2<$maxWidth || $imgHeight-$oy*2<$maxHeight;
$aspect=$imgHeight/$imgWidth;
$newHeight=$maxWidth*$aspect;
next if $newHeight>2000;
$tmp->copyResampled($img,0,0,$ox,$oy,$maxWidth,$newHeight,$imgWidth-$ox*2,$imgHeight-$oy*2);
for($q=0;$q<=$newHeight-$maxHeight;$q+=$maxHeight){
$res->copy($tmp,0,0,0,$q,$maxWidth,$maxHeight);
if(length($res->jpeg(45))>=3000){ ##### Вот тут вся магия!
open(dd,">res/".$uid.".png");
print dd $res->png(9);
close(dd);
$uid++;
print "saved!n";
}
}
}
В итоге имеем примерно вот такую нарезочку: rghost.net/40299752
2. Делаем штриховку
Обычно в юзербарах штриховка под углом 45 градусов. Но как ее сделать? Рисовать самому в графическом редакторе — сложно, по крайней мере для меня. Я создал SVG-файл и отрендерив получил примерно такое:
Выглядит еще терпимо, но что-то мне тут не нравилось. Хотелось гибкости. А править SVG мне быстро надоело, равно как и запускать инкскейп. Согласитесь, выглядит не очень. Остается только вариант написать утильку и сгенерировать что надо. Для рисования черных и белых полосочек можно использовать формулу:
color=sin(x/3.14*step)
Но штриховка должна быть под углом, поэтому надо сделать трансформацию.
for($w=0;$w<30;$w++){
for($q=0;$q<350;$q++){
$rad=($angle/180*3.14159265); # Переводим градусы в радианы
$aa=cos($rad); # а-элемент матрицы трансформации
$cc=sin($rad); # с-элемент матрицы трансформации
$rx=$q*$aa+$w*$cc; # Смещение с учетом трансформации
$l=sin($rx/3.14*$step); # Получаем яркость
$l=$l*127+128; # нормализуем
$res->setPixel($q,$w,($a<<24) | ($l<<16) | ($l<<8) | $l);
}
}
# Сохраняем
open(dd,">shade.png");
print dd $res->png(9);
close(dd);
Нет, это не результат, это небольшие косяки, которые вылезли при написании, но выглядит красиво. А вот примерный результат, который должен быть в итоге:
Угол поворота и ширину полосочек я доверил выбрать священному рандому.
3. Отблеск
Как вы уже наверное поняли, я не умею рисовать вообще, даже полосочки не смог сделать. А как рисовать отблеск? Отблеск — это попытка изобразить объем, словно на наш глянцевый юзербар сверху падает свет. Если объем, то будем использовать 3D-рендерер, пусть сам рисует. А юзербар можно будет «выпучить», представив его в виде цилиндра. Для этого я использовал Persistence of Vision Raytracer, хотя подойдет что угодно, что позволяет себя скриптовать и имеет хороший результат рендеринга.
#include "rand.inc"
#include "colors.inc"
background {color Black}
camera {location <0,0,-1.2> look_at <0,0,0>}
cylinder {<-10,0,0>,<10,0,0>,0.5 open texture{pigment{color White}}}
light_source{
<0,1,-1>
color White
fade_power 3
fade_distance 1
}
light_source{
<0,-100,-1>
color White
fade_power 3
fade_distance 0
}
Как видите, я создал цилиндр и 2 источника света. Этого достаточно для классического эффекта «отблеска», который я отчаянно пытался нарисовать, следуя пошаговым инструкциям многочисленных туториалов. А почему только 2 источника света? Почему не добавить других? В ходе экспериментов я видел что-то вроде:
Пришел к такому:
Вот такой вариант напоминает элементы интерфейса одной операционной системы:
4. Шрифты
Обычно используется шрифт Visitor, как оригинал, так и его русифицированный аналог. Но проблема в том, что на нашей радиостанции много японской музыки, как отрендерить иероглифы? Если же изначально использовать японский шрифт, то на русский/английский становиться страшно смотреть. Надо заметить, это попило больше всего крови у меня.
Как вариант, можно взять FreeType, отрендерить китайский, японский и прочие шрифты, а затем взять Визитор и часть отрендеренных глифов заменить на имеющиеся в нем. Будет эдакая сборная солянка из шрифтов. Для начала напишем растеризатор всех символов:
void render(char *filename,int size, int isAlias){
fprintf(stderr,"Rendering file %s, font size: %d, is %sn",filename,size,isAlias?"anti-aliased":"mono");
FT_Init_FreeType (&library);
if(FT_New_Face (library, filename, 0, &face)==0){
FT_Set_Char_Size (face, 0, size*64, 0, DPI);
unsigned char *gl=malloc(CHARSIZE);
int ch;
int linepos=(LINEHEIGHT-size)/2+size-1;
for (ch=0;ch<MAXCHARS;ch++){
int ind = FT_Get_Char_Index (face, ch);
if(ind == 0){
continue;
}
FT_Load_Glyph (face, ind, 0);
FT_Render_Glyph (face->glyph, FT_RENDER_MODE_NORMAL);
bits = face->glyph->bitmap;
if(bits.width==0 || bits.rows==0){
continue;
}
int oy=linepos-(face->glyph->metrics.horiBearingY>>6);
memset (gl,0,CHARSIZE);
int q,w;
for (w=0;w<bits.rows;w++){
for (q=0;q<bits.width;q++){
int v=bits.buffer[bits.pitch * w+q];
int y=w+oy;
if(y>=0 && y<HEIGHT && q<WIDTH){
if(isAlias==0){
v=v>128?255:0;
}
*(gl+y*WIDTH+q)=v;
}
}
}
memcpy(font+ch*CHARSIZE,gl,CHARSIZE);
}
free(gl);
}
FT_Done_FreeType(library);
}
Здесь все просто: получаем имя файла со шрифтом, открываем его, берем первый фейс и перебираем MAXCHARS символов в нем. Далее, скармливаем ему все имеющиеся шрифты:
char *mainfonts="/usr/local/lib/X11/fonts/TrueType";
struct dirent *ent;
DIR *dir=opendir (mainfonts);
while((ent=readdir(dir))!=NULL){
sprintf(filename,"%s/%s",mainfonts,ent->d_name);
render (filename,15,1);
}
closedir(dir);
render("みかちゃん.ttf",15,1);
render("FZFangSong.ttf",15,1);
render("FZKaiTi.ttf",15,1);
render("LiberationMono-Regular.ttf",10,0);
render("LucidaTypewriterRegular.ttf",10,1);
render("micro/visitor1.ttf",10,0);
render("micro/Visitor_Rus.ttf",10,0);
Здесь мы все шрифты рендерим в 15 пунктов, последними же идет визитор, причем в 10 пунктов и без антиалиасинга.
На выходе получаем почти все имеющиеся глифы у нас:
Да, про кернинг надо забыть. Да, часть символов будет обрезана, другую часть символов будет не видно. Надо сказать, эта штука попила не мало моей крови, это лучшее что я смог родить.
5. Соединяем все вместе
backpic=loadPng(fileBackgound);
hoverpic=loadPng(fileGlossy);
shade=loadPng(fileShade);
lumaToAlpha(hoverpic);
gdImageCopy(res,backpic,0,0,0,0,350,19); // берем фон
gdImageCopy(res,shade,1,1,0,0,350,19); // добавляем штриховку
drawText(res,text,10,0); // рисуем текст с окантовкой
gdImageCopy(res,hoverpic,1,1,0,0,350,19); // блеск
gdImageRectangle(res,0,0,349,18,0); // добавляем рамочку
// сохраняем в файл
FILE *resfile=fopen(fileOut,"w");
gdImagePng(res,resfile);
fclose(resfile);
Надо сказать, что если блеск слишком яркий, то возможно имеет смысл добавлять текст уже после него. Получаем что-то вроде (текст под блеском и блеск под текстом):
Да, иероглифы выглядят не очень красиво, но это лучше отображения квадратиков/вопросиков.
6. Интеграция в Icecast
Можно править как административный интефейс, но я же правил непосредственно плагины. Посмотрите, какая функция назначена на plugin->set_tag? Ну и очевидная правка:
if (strcmp (tag, "title") == 0)
{
free (source_mp3->url_title);
source_mp3->url_title = value;
userbars_render(value);
}
Все, в userbars_render() рендерим юзербарчик и сохраняем его в директорию вебсервера. Все. Теперь на нашем радио есть модные юзербарки!
7. Послесловие
Все описанное выше — это только возможный вариант. Он не претендует ни на что, да и сами юзербары я начал делать в качестве прокрастинации, ибо от основного проекта у меня жуткая депрессия и печаль, а вот такие шалости позволяют увидеть, что я не хуже всех и хоть как-то вернуться в русло быдлокодинга. Вообще, изначально планировалось сделать анимированные юзербарки с плавающими огнями и прыгающими буквами, но у меня началась прокрастинация внутри прокрастинации, поэтому я привожу только «базовую» часть, а анимации/кернинг/оптимизации я предлагаю сделать вам самим. Делись своими экспериментами, одному человеку все равно все не придумать!
Автор: iFrolov