Установка Python в режиме portable под Windows

Установка Python

  1. Скачать инсталлятор в формате MSI отсюда: https://python.org/downloads/
  2. Установить полученный дистрибутив <python.msi> в папку <INSTALLDIR> командой:
    msiexec /a <python.msi> /qn TARGETDIR="<INSTALLDIR>"

    , где /a — команда на установку с админскими правами, /qn — не задавать лишних вопросов, TARGETDIR — конечная папка

Установка Python Package Index

  1. Скачать файл getpip.py: https://bootstrap.pypa.io/get-pip.py
  2. Установить pip командой:
    <INSTALLDIR>\python.exe getpip.py
  3. Теперь любые пакеты можно устанавливать командой:
    <INSTALLDIR>\Scripts\pip.exe install <package name>

В итоге у вас есть обособленный Python интерпретатор любой версии с любыми пакетами в комплекте.

Также у вас есть возможность запускать *.py файлы с помощью установленного дистрибутива по клику мыши. Для этого вам понадобится PyLauncher.

Установка и настройка PyLauncher

  1. Скачать и установить pylauncer отсюда: https://bitbucket.org/vinay.sajip/pylauncher/downloads
  2. Открыть/создать файл %LOCALAPPDATA%\py.ini и добавить строку:
    [commands]
    mypython=<INSTALLDIR>\python.exe
  3. Добавить/поменять первую строку запускаемых *.py файлов на такую:
    #!mypython

    Теперь при запуске этих файлов их будет обрабатывать ваш интерпретатор (<INSTALLDIR>\python.exe)

Как это работает?

Первая строчка в формате #!<program name> используется в *nix подобных системах для автоматического выбора интерпретатора во время запуска и называется она, кстати, «shebang». В Windows системах эта строка игнорируется. PyLauncher просто перехватывает обработку *.py файлов и вызывает правильный Python интерпретатор.

Ускорение работы Python

Я рекомендую отключать создание и обновление *.pyc и *.pyo файлов в процессе работы Python. Аргументы могу привести следующие: оперативной памяти сейчас у всех хватает, процессор достаточно быстрый. Но устройство хранения данных (в народе флешка, жёсткий диск, SSD) по-прежнему слабовато.

Поэтому не выпендривайтесь и запускайте python с параметром -B или добавьте параметр PYTHONDONTWRITEBYTECODE в переменную окружения с любым непустым значением. Например, PYTHONDONTWRITEBYTECODE=Non_empty_string.

Используем FreeType для вывода строки

Продолжаем наши посиделки с библиотекой растеризации шрифтов FreeType. Из предыдущих моих постов вы уже должны знать какими способами можно выводить текст в OpenGL и как получить изображение символа из шрифта с помощью FreeType.

Наша задача вывести строку каким-нибудь шрифтом в прямоугольную картинку. Для этого у нас есть библиотека FreeType и мы умеем получать с помощью неё изображение символа. Изображения символа для составления строки недостаточно, потому что строка состоит из символов разделенных разным расстоянием, при этом каждый символ имеет разную высоту и позицию по вертикали.

По вертикали у строки есть три основных параметра:

  • базовая линия (baseline) — это ноль по вертикали в нашей системе координат. Символы позиционируются по вертикали относительно этой линии
  • верхняя граница (ascender) — это верхняя горизонтальная линия, которую (теоретически) не пересекает ни один символ в строке (в нашей системе координат ее вертикальная позиция имеет положительное значение)
  • нижняя граница (descender) — это нижняя горизонтальная линия, которую (теоретически) не пересекает ни один символ в строке (ее вертикальная позиция имеет отрицательное значение)

Для нашей задачи нам достаточно лишь базовой линии.

Sphinx
автор картинки: Max Naylor

По горизонтали строка состоит из символов. У каждого символа есть своё посадочное место, его размер и расстояние до следующего символа. Также надо учитывать кернинг (это специальное расстояние между двумя символами, чтобы текст смотрелся более гармонично).

Библиотека FreeType даёт нам возможность узнать о глифе практически все. Есть три источника информации:

  • FT_Glyph — содержит advance.x (расстояние до следующего символа)
  • FT_BitmapGlyph — содержит left (расстояние от текущей позиции posX до картинки), top (расстояние от базовой линии до верхней линии глифа), bitmap (содержит изображение глифа и его размер)
  • FT_Bitmap — содержит width (ширина изображения глифа), height (высота изображения глифа, поле называется rows), buffer (буфер с изображением)

Для наглядности нарисовал вот такую симпатичную схему:

ascender,descender,baseline,advance.x,width,height,top,left

Оранжевым цветом обозначено содержимое FT_Bitmap.buffer, серым — пространство, занимаемое символом в строке.

Алгоритм решения нашей задачи состоит из двух основных шагов:

  1. вычислить ширину и высоту строки и место для рисования каждого символа в строке
  2. выделить память для картинки и нарисовать каждый символ строки по ранее вычисленным позициям

Перед практической частью хотел бы ещё рассказать вкратце про систему счисления библиотеки FreeType.

Для нецелых чисел в ней используется формат 26.6, где 26 бит хранят целую часть числа, а 6 бит — все остальное. Для получения целой части числа его необходимо сдвинуть вправо на шесть бит. Для того, чтобы не терять дробную часть числа, сложение и вычитание надо производить как есть и лишь непосредственно перед использованием целой части сдвигать на шесть бит. Еще есть формат 16.16 (целая часть 16 бит и дробная часть 16 бит). Чего только не придумают, лишь бы float не использовать.

Используемые заголовки, вспомогательные функции и структуры (тело функций ниже по тексту):

// Подключаем FreeType
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
 
// Подключаем libpng (http://libpng.sourceforge.net/index.html)
#include <png.h>
 
// Поддержка uint8_t, int32_t и т.д.
#include <stdint.h>
// Поддержка std::string
#include <string>
// Поддержка std::vector
#include <vector>
 
// Получить изображение символа
FT_Glyph getGlyph(FT_Face face, uint32_t charcode);
 
// Получить кернинг между двумя символа
FT_Pos getKerning(FT_Face face, uint32_t leftCharcode, uint32_t rightCharcode);
 
// Сохранить картинку в PNG
void savePNG(uint8_t *image, int32_t width, int32_t height);
 
// Позиция и размер глифа в строке
struct Symbol
{
    // Позиция по горизонтали
    int32_t posX;
    // Позиция по вертикали (от базовой линии)
    int32_t posY;
    // Ширина глифа
    int32_t width;
    // Высота глифа
    int32_t height;
 
    FT_Glyph glyph;
};

Для начала загрузим шрифт из файла arial.ttf:

// Инициализация библиотеки
FT_Library ftLibrary = 0;
FT_Init_FreeType(&ftLibrary);
 
// Загрузка шрифта arial.ttf из текущей папки
FT_Face ftFace = 0;
FT_New_Face(ftLibrary, "arial.ttf", 0, &ftFace);
 
// Установим размер символа для рендеринга
FT_Set_Pixel_Sizes(ftFace, 100, 0);

Затем посчитаем позицию каждого символа и размеры строки:

// Выводимая строка
const std::string exampleString("FreeType it's amazing!");
 
// Набор готовых символов
std::vector<Symbol> symbols;
int32_t left = INT_MAX;
int32_t top = INT_MAX;
int32_t bottom = INT_MIN;
uint32_t prevCharcode = 0;
 
// Позиция текущего символа в формате 26.6
int32_t posX = 0;
 
for (std::size_t i = 0; i < exampleString.size(); ++i)
{
    // Получаем код символа
    const uint32_t charcode = exampleString[i];
 
    // Получаем глиф для этого символа
    FT_Glyph glyph = getGlyph(ftFace, charcode);
 
    if (!glyph)
    {
        // Глифы в шрифте есть не для всех символов
        continue;
    }
 
    if (prevCharcode)
    {
        // Используем кернинг
        posX += getKerning(ftFace, prevCharcode, charcode);
    }
    prevCharcode = charcode;
 
    symbols.push_back(Symbol());
    Symbol &symb = symbols.back();
 
    FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) glyph;
 
    // Вычисляем горизонтальную позицию символа
    symb.posX = (posX >> 6) + bitmapGlyph->left;
 
    // Вычисляем вертикальную позицию символа относительно базовой 
    // линии. Отрицательные значения - сверху, положительные - снизу.
    symb.posY = -bitmapGlyph->top;
 
    // Ширина символа
    symb.width = bitmapGlyph->bitmap.width;
    // Высота символа
    symb.height = bitmapGlyph->bitmap.rows;
 
    // Ссылка на глиф
    symb.glyph = glyph;
 
    // Смещаем позицию текущего символа
    // (glyph->advance имеет формат 16.16, поэтому для приведения 
    // его к формату 26.6 необходимо сдвинуть число на 10 бит враво)
    posX += glyph->advance.x >> 10;
 
    // Вычисляем самую левую позицию
    left = std::min(left, symb.posX);
 
    // Вычисляем самую верхнюю позицию
    top = std::min(top, symb.posY);
 
    // Вычисляем самую нижнюю позицию
    bottom = std::max(bottom, symb.posY + symb.height);
}
 
for (std::size_t i = 0; i < symbols.size(); ++i)
{
    // Смещаем все символы влево, чтобы строка примыкала к левой части
    symbols[i].posX -= left;
}
 
const Symbol &lastSymbol = symbols.back();
 
// Ширина строки (изображения) - это крайняя правая 
// точка последнего символа в строке
const int32_t imageW = lastSymbol.posX + lastSymbol.width;
 
// Высота строки (изображения)
const int32_t imageH = bottom - top;

По полученным размерам заполним нашу картинку:

// Выделяем память для картинки
std::vector<uint8_t> image(imageW * imageH);
 
for (std::size_t i = 0; i < symbols.size(); ++i)
{
    const Symbol &symb = symbols[i];
 
    FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) symb.glyph;
    FT_Bitmap bitmap = bitmapGlyph->bitmap;
 
    for (int32_t srcY = 0; srcY < symb.height; ++srcY)
    {
        // Координата Y в итоговой картинке
        const int32_t dstY = symb.posY + srcY - top;
 
        for (int32_t srcX = 0; srcX < symb.width; ++srcX)
        {
            // Получаем пиксель из изображения символа,
            // (обязательно используйте pitch вместо width)
            const uint8_t c = bitmap.buffer[srcX + srcY * bitmap.pitch];
 
            // Если пиксель полностью прозрачный, то пропускаем его
            if (0 == c)
            {
                continue;
            }
 
            // Приводим множество [0..255] к [0..1] для удобства блендинга
            const float a = c / 255.0f;
 
            // Координата X в итоговой картинке
            const int32_t dstX = symb.posX + srcX;
 
            // Вычислим смещение в итоговой картинке
            uint8_t *dst = image.data() + dstX + dstY * imageW;
 
            // Рисуем этот пиксель в итоговую картинку с блендингом
            dst[0] = uint8_t(a * 255 + (1 - a) * dst[0]);
        }
    }
}

Выведем картинку в какой-нибудь простой формат. Например, PNG:

// Сохраняем изображение в PNG формате с прозрачностью
savePNG(image.data(), imageW, imageH);

В конце программы не забудем освободить всю используемую память:

// Освобождаем памяти для глифов
for (std::size_t i = 0; i < symbols.size(); ++i)
{
    FT_Done_Glyph(symbols[i].glyph);
}
 
// Освобождаем шрифт
FT_Done_Face(ftFace);
ftFace = 0;
 
// Заканчиваем работу с библиотекой
FT_Done_FreeType(ftLibrary);
ftLibrary = 0;

Порадуемся за результат:

freetype

Вспомогательные функции:

FT_Glyph getGlyph(FT_Face face, uint32_t charcode)
{
    // Загрузка глифа в face->glyph с отрисовкой
    FT_Load_Char(face, charcode, FT_LOAD_RENDER);
 
    FT_Glyph glyph = 0;
    // Получаем глиф
    FT_Get_Glyph(face->glyph, &glyph);
    return glyph;
}
 
 
FT_Pos getKerning(FT_Face face, uint32_t leftCharcode, uint32_t rightCharcode)
{
    // Получаем индекс левого символа
    FT_UInt leftIndex = FT_Get_Char_Index(face, leftCharcode);
    // Получаем индекс правого символа
    FT_UInt rightIndex = FT_Get_Char_Index(face, rightCharcode);
 
    // Здесь будет хранится кернинг в формате 26.6
    FT_Vector delta;
    // Получаем кернинг для двух символов
    FT_Get_Kerning(face, leftIndex, rightIndex, FT_KERNING_DEFAULT, &delta);
    return delta.x;
}
 
 
void savePNG(uint8_t *image, int32_t width, int32_t height)
{
    // Файл для сохранения картинки
    FILE *f = fopen("output.png", "wb");
 
    png_structp png_ptr =
        png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
 
    png_infop info_ptr = png_create_info_struct(png_ptr);
 
    png_init_io(png_ptr, f);
 
    // Изображение в формате RGBA по 8 бит на 
    // канал и по четыре канала на пиксель
    png_set_IHDR(
        png_ptr, 
        info_ptr, 
        width, 
        height, 
        8, 
        PNG_COLOR_TYPE_RGBA, 
        PNG_INTERLACE_NONE, 
        PNG_COMPRESSION_TYPE_BASE, 
        PNG_FILTER_TYPE_BASE);
 
    png_write_info(png_ptr, info_ptr);
 
    // Одна строка в формате RGBA, 4 канала
    std::vector<uint8_t> row(width * 4);
 
    // Сохраняем PNG построчно
    for (int32_t y = 0; y < height; ++y)
    {
        // Преобразуем нашу строку из одноканальной в формат RGBA
        for (int32_t x = 0; x < width; ++x)
        {
            // Цвет одинаковый для всех пикселей 0x202020
            row[x * 4 + 0] = 0x20;
            row[x * 4 + 1] = 0x20;
            row[x * 4 + 2] = 0x20;
            // Прозрачность берём из исходных данных
            row[x * 4 + 3] = image[y * width + x];
        }
 
        // Сохраняем строку в PNG
        png_write_row(png_ptr, row.data());
    }
 
    png_write_end(png_ptr, 0);    
 
    // Закончили работу, освобождаем ресурсы
    fclose(f);
    png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
    png_destroy_write_struct(&png_ptr, 0);
}

И весь код целиком под спойлером.

Исходный код C++ (с комментариями)

// Подключаем FreeType
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
 
// Подключаем libpng (http://libpng.sourceforge.net/index.html)
#include <png.h>
 
// Поддержка uint8_t, int32_t и т.д.
#include <stdint.h>
// Поддержка std::string
#include <string>
// Поддержка std::vector
#include <vector>
 
// Получить изображение символа
FT_Glyph getGlyph(FT_Face face, uint32_t charcode);
 
// Получить кернинг между двумя символа
FT_Pos getKerning(FT_Face face, uint32_t leftCharcode, uint32_t rightCharcode);
 
// Сохранить картинку в PNG
void savePNG(uint8_t *image, int32_t width, int32_t height);
 
// Позиция и размер глифа в строке
struct Symbol
{
    // Позиция по горизонтали
    int32_t posX;
    // Позиция по вертикали (от базовой линии)
    int32_t posY;
    // Ширина глифа
    int32_t width;
    // Высота глифа
    int32_t height;
 
    FT_Glyph glyph;
};
 
 
int main()
{
    // Инициализация библиотеки
    FT_Library ftLibrary = 0;
    FT_Init_FreeType(&ftLibrary);
 
    // Загрузка шрифта arial.ttf из текущей папки
    FT_Face ftFace = 0;
    FT_New_Face(ftLibrary, "arial.ttf", 0, &ftFace);
 
    // Установим размер символа для рендеринга
    FT_Set_Pixel_Sizes(ftFace, 100, 0);
 
    // Выводимая строка
    const std::string exampleString("FreeType it's amazing!");
 
    // Набор готовых символов
    std::vector<Symbol> symbols;
    int32_t left = INT_MAX;
    int32_t top = INT_MAX;
    int32_t bottom = INT_MIN;
    uint32_t prevCharcode = 0;
 
    // Позиция текущего символа в формате 26.6
    int32_t posX = 0;
 
    for (std::size_t i = 0; i < exampleString.size(); ++i)
    {
        // Получаем код символа
        const uint32_t charcode = exampleString[i];
 
        // Получаем глиф для этого символа
        FT_Glyph glyph = getGlyph(ftFace, charcode);
 
        if (!glyph)
        {
            // Глифы в шрифте есть не для всех символов
            continue;
        }
 
        if (prevCharcode)
        {
            // Используем кернинг
            posX += getKerning(ftFace, prevCharcode, charcode);
        }
        prevCharcode = charcode;
 
        symbols.push_back(Symbol());
        Symbol &symb = symbols.back();
 
        FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) glyph;
 
        // Вычисляем горизонтальную позицию символа
        symb.posX = (posX >> 6) + bitmapGlyph->left;
 
        // Вычисляем вертикальную позицию символа относительно базовой 
        // линии. Отрицательные значения - сверху, положительные - снизу.
        symb.posY = -bitmapGlyph->top;
 
        // Ширина символа
        symb.width = bitmapGlyph->bitmap.width;
        // Высота символа
        symb.height = bitmapGlyph->bitmap.rows;
 
        // Ссылка на глиф
        symb.glyph = glyph;
 
        // Смещаем позицию текущего символа
        // (glyph->advance имеет формат 16.16, поэтому для приведения 
        // его к формату 26.6 необходимо сдвинуть число на 10 бит враво)
        posX += glyph->advance.x >> 10;
 
        // Вычисляем самую левую позицию
        left = std::min(left, symb.posX);
 
        // Вычисляем самую верхнюю позицию
        top = std::min(top, symb.posY);
 
        // Вычисляем самую нижнюю позицию
        bottom = std::max(bottom, symb.posY + symb.height);
    }
 
    for (std::size_t i = 0; i < symbols.size(); ++i)
    {
        // Смещаем все символы влево, чтобы строка примыкала к левой части
        symbols[i].posX -= left;
    }
 
    const Symbol &lastSymbol = symbols.back();
 
    // Ширина строки (изображения) - это крайняя правая 
    // точка последнего символа в строке
    const int32_t imageW = lastSymbol.posX + lastSymbol.width;
 
    // Высота строки (изображения)
    const int32_t imageH = bottom - top;
 
    // Выделяем память для картинки
    std::vector<uint8_t> image(imageW * imageH);
 
    for (std::size_t i = 0; i < symbols.size(); ++i)
    {
        const Symbol &symb = symbols[i];
 
        FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) symb.glyph;
        FT_Bitmap bitmap = bitmapGlyph->bitmap;
 
        for (int32_t srcY = 0; srcY < symb.height; ++srcY)
        {
            // Координата Y в итоговой картинке
            const int32_t dstY = symb.posY + srcY - top;
 
            for (int32_t srcX = 0; srcX < symb.width; ++srcX)
            {
                // Получаем пиксель из изображения символа,
                // (обязательно используйте pitch вместо width)
                const uint8_t c = bitmap.buffer[srcX + srcY * bitmap.pitch];
 
                // Если пиксель полностью прозрачный, то пропускаем его
                if (0 == c)
                {
                    continue;
                }
 
                // Приводим множество [0..255] к [0..1] для удобства блендинга
                const float a = c / 255.0f;
 
                // Координата X в итоговой картинке
                const int32_t dstX = symb.posX + srcX;
 
                // Вычислим смещение в итоговой картинке
                uint8_t *dst = image.data() + dstX + dstY * imageW;
 
                // Рисуем этот пиксель в итоговую картинку с блендингом
                dst[0] = uint8_t(a * 255 + (1 - a) * dst[0]);
            }
        }
    }
 
    // Сохраняем изображение в PNG формате с прозрачностью
    savePNG(image.data(), imageW, imageH);
 
    // Освобождаем памяти для глифов
    for (std::size_t i = 0; i < symbols.size(); ++i)
    {
        FT_Done_Glyph(symbols[i].glyph);
    }
 
    // Освобождаем шрифт
    FT_Done_Face(ftFace);
    ftFace = 0;
 
    // Заканчиваем работу с библиотекой
    FT_Done_FreeType(ftLibrary);
    ftLibrary = 0;
 
    return 0;
}
 
 
FT_Glyph getGlyph(FT_Face face, uint32_t charcode)
{
    // Загрузка глифа в face->glyph с отрисовкой
    FT_Load_Char(face, charcode, FT_LOAD_RENDER);
 
    FT_Glyph glyph = 0;
    // Получаем глиф
    FT_Get_Glyph(face->glyph, &glyph);
    return glyph;
}
 
 
FT_Pos getKerning(FT_Face face, uint32_t leftCharcode, uint32_t rightCharcode)
{
    // Получаем индекс левого символа
    FT_UInt leftIndex = FT_Get_Char_Index(face, leftCharcode);
    // Получаем индекс правого символа
    FT_UInt rightIndex = FT_Get_Char_Index(face, rightCharcode);
 
    // Здесь будет хранится кернинг в формате 26.6
    FT_Vector delta;
    // Получаем кернинг для двух символов
    FT_Get_Kerning(face, leftIndex, rightIndex, FT_KERNING_DEFAULT, &delta);
    return delta.x;
}
 
 
void savePNG(uint8_t *image, int32_t width, int32_t height)
{
    // Файл для сохранения картинки
    FILE *f = fopen("output.png", "wb");
 
    png_structp png_ptr =
        png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
 
    png_infop info_ptr = png_create_info_struct(png_ptr);
 
    png_init_io(png_ptr, f);
 
    // Изображение в формате RGBA по 8 бит на 
    // канал и по четыре канала на пиксель
    png_set_IHDR(
        png_ptr, 
        info_ptr, 
        width, 
        height, 
        8, 
        PNG_COLOR_TYPE_RGBA, 
        PNG_INTERLACE_NONE, 
        PNG_COMPRESSION_TYPE_BASE, 
        PNG_FILTER_TYPE_BASE);
 
    png_write_info(png_ptr, info_ptr);
 
    // Одна строка в формате RGBA, 4 канала
    std::vector<uint8_t> row(width * 4);
 
    // Сохраняем PNG построчно
    for (int32_t y = 0; y < height; ++y)
    {
        // Преобразуем нашу строку из одноканальной в формат RGBA
        for (int32_t x = 0; x < width; ++x)
        {
            // Цвет одинаковый для всех пикселей 0x202020
            row[x * 4 + 0] = 0x20;
            row[x * 4 + 1] = 0x20;
            row[x * 4 + 2] = 0x20;
            // Прозрачность берём из исходных данных
            row[x * 4 + 3] = image[y * width + x];
        }
 
        // Сохраняем строку в PNG
        png_write_row(png_ptr, row.data());
    }
 
    png_write_end(png_ptr, 0);    
 
    // Закончили работу, освобождаем ресурсы
    fclose(f);
    png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
    png_destroy_write_struct(&png_ptr, 0);
}

Исходный код С (без комментариев)

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
 
#include <png.h>
#include <stdint.h>
 
FT_Glyph getGlyph(FT_Face face, uint32_t charcode);
FT_Pos getKerning(FT_Face face, uint32_t leftCharcode, uint32_t rightCharcode);
void savePNG(uint8_t *image, int32_t width, int32_t height);
 
struct Symbol
{
    int32_t posX;
    int32_t posY;
    int32_t width;
    int32_t height;
    FT_Glyph glyph;
};
 
const size_t MAX_SYMBOLS_COUNT = 128;
 
#define MIN(x, y) ((x) > (y) ? (y) : (x))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
 
 
int main()
{
    FT_Library ftLibrary = 0;
    FT_Init_FreeType(&ftLibrary);
 
    FT_Face ftFace = 0;
    FT_New_Face(ftLibrary, "arial.ttf", 0, &ftFace);
 
    FT_Set_Pixel_Sizes(ftFace, 100, 0);
 
    const char *exampleString = "FreeType it's amazing!";
    const size_t exampleStringLen = strlen(exampleString);
 
    struct Symbol symbols[MAX_SYMBOLS_COUNT];
    size_t numSymbols = 0;
 
    int32_t left = INT_MAX;
    int32_t top = INT_MAX;
    int32_t bottom = INT_MIN;
    uint32_t prevCharcode = 0;
 
    size_t i = 0;
 
    int32_t posX = 0;
 
    for (i = 0; i < exampleStringLen; ++i)
    {
        const uint32_t charcode = exampleString[i];
 
        FT_Glyph glyph = getGlyph(ftFace, charcode);
 
        if (!glyph)
        {
            continue;
        }
 
        if (prevCharcode)
        {
            posX += getKerning(ftFace, prevCharcode, charcode);
        }
        prevCharcode = charcode;
 
        struct Symbol *symb = &(symbols[numSymbols++]);
 
        FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) glyph;
        symb->posX = (posX >> 6) + bitmapGlyph->left;
        symb->posY = -bitmapGlyph->top;
        symb->width = bitmapGlyph->bitmap.width;
        symb->height = bitmapGlyph->bitmap.rows;
        symb->glyph = glyph;
 
        posX += glyph->advance.x >> 10;
 
        left = MIN(left, symb->posX);
        top = MIN(top, symb->posY);
        bottom = MAX(bottom, symb->posY + symb->height);
    }
 
    for (i = 0; i < numSymbols; ++i)
    {
        symbols[i].posX -= left;
    }
 
    const struct Symbol *lastSymbol = &(symbols[numSymbols - 1]);
 
    const int32_t imageW = lastSymbol->posX + lastSymbol->width;
    const int32_t imageH = bottom - top;
 
    uint8_t *image = malloc(imageW * imageH);
 
    for (i = 0; i < numSymbols; ++i)
    {
        const struct Symbol *symb = symbols + i;
 
        FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph) symb->glyph;
        FT_Bitmap bitmap = bitmapGlyph->bitmap;
 
        for (int32_t srcY = 0; srcY < symb->height; ++srcY)
        {
            const int32_t dstY = symb->posY + srcY - top;
 
            for (int32_t srcX = 0; srcX < symb->width; ++srcX)
            {
                const uint8_t c = bitmap.buffer[srcX + srcY * bitmap.pitch];
 
                if (0 == c)
                {
                    continue;
                }
 
                const float a = c / 255.0f;
                const int32_t dstX = symb->posX + srcX;
                uint8_t *dst = image + dstX + dstY * imageW;
                dst[0] = (uint8_t)(a * 255 + (1 - a) * dst[0]);
            }
        }
    }
 
    savePNG(image, imageW, imageH);
 
    free(image);
 
    for (i = 0; i < numSymbols; ++i)
    {
        FT_Done_Glyph(symbols[i].glyph);
    }
 
    FT_Done_Face(ftFace);
    ftFace = 0;
 
    FT_Done_FreeType(ftLibrary);
    ftLibrary = 0;
 
    return 0;
}
 
 
FT_Glyph getGlyph(FT_Face face, uint32_t charcode)
{
    FT_Load_Char(face, charcode, FT_LOAD_RENDER);
    FT_Glyph glyph = 0;
    FT_Get_Glyph(face->glyph, &glyph);
    return glyph;
}
 
 
FT_Pos getKerning(FT_Face face, uint32_t leftCharcode, uint32_t rightCharcode)
{
    FT_UInt leftIndex = FT_Get_Char_Index(face, leftCharcode);
    FT_UInt rightIndex = FT_Get_Char_Index(face, rightCharcode);
 
    FT_Vector delta;
    FT_Get_Kerning(face, leftIndex, rightIndex, FT_KERNING_DEFAULT, &delta);
    return delta.x;
}
 
 
void savePNG(uint8_t *image, int32_t width, int32_t height)
{
    FILE *f = fopen("output.png", "wb");
 
    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
 
    png_infop info_ptr = png_create_info_struct(png_ptr);
 
    png_init_io(png_ptr, f);
 
    png_set_IHDR(
        png_ptr, 
        info_ptr, 
        width, 
        height, 
        8, 
        PNG_COLOR_TYPE_RGBA, 
        PNG_INTERLACE_NONE, 
        PNG_COMPRESSION_TYPE_BASE, 
        PNG_FILTER_TYPE_BASE);
 
    png_write_info(png_ptr, info_ptr);
 
    uint8_t *row = malloc(width * 4);
 
    for (int32_t y = 0; y < height; ++y)
    {
        for (int32_t x = 0; x < width; ++x)
        {
            row[x * 4 + 0] = 0xc0;
            row[x * 4 + 1] = 0xc0;
            row[x * 4 + 2] = 0xc0;
            row[x * 4 + 3] = image[y * width + x];
        }
 
        png_write_row(png_ptr, row);
    }
 
    free(row);
 
    png_write_end(png_ptr, 0);    
 
    png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
    png_destroy_write_struct(&png_ptr, 0);
 
    fclose(f);
}

UPD: Если вам необходимо получить данные о символах в векторном виде, рекомендую ознамиться с этим примером, особенно со структурой FT_Outline_Funcs и функцией FT_Outline_Decompose().

Непрофессиональный отзыв о Highscreen Boost 3

На днях стал счастливым обладателем Highscreen Boost 3. Устройство позиционируется как российская разработка. Хотите — верьте, хотите — нет.

Единственная причина по которой мне хотелось получить данный смартфон — это исправленный звук динамика по сравнению с предыдущей версией — Highscreen Boost 2 SE. В остальном к старой версии модели претензий было немного (внешний вид, камера, звук).

«Буду брать!» — решил я сразу, как только включил камеру на новом телефоне в одной руке и камеру в старом в другой руке. Небо и земля.

Информация о Nokia N9 на телефоне Highscreen Boost 3, сфотографированная на Nokia N9

На смартфоне установлен голый Android версии 5.1. Через три часа использования сменил оболочку на бесплатный Nova Launcher. Ещё через пару часов купил расширенную версию за 300 рублей. Вам это делать необязательно, просто меня раздражает гугл-поиск на рабочем столе. Кто захочет увидеть такой же рабочий стол, как на предыдущей версии мобильника — вы знаете теперь что делать.

Использую уже месяц, проблем нет, камера работает хорошо, звук громкий. 31-го декабря зарядил, 2-го января он ещё работал (а я уже нет, ахаха).

Ложка дёгтя в бочку мёда — штатная защитная плёнка отвалилась через три дня, а купленная на замену оказалась слишком узкой для мобильника. С тех самых пор хожу без плёнки, не очень-то и хотелось. (:

Мобильник выдержал первую командировку со мной в Южную Корею, затем отпуск в Грузии, после этого опять Южная Корея, Сеул.

Достойный аппарат, рекомендую.

Если вас не устраивает вес, можно поставить аккумулятор на 3000mAh и быть, как все.

Если вес устраивает, но не устраивает цвет, то в официальном магазине продаются сменные крышки на любой вкус (милитари, хипстер, девочка в розовом).

Бюджетный вариант хлопушки на праздники

Пиротехника в наше время стоит немалых денег. Поэтому на этот Новый год мы выбрали вариант дешевле и веселее — называется «сигнал охотника».

Продаётся каждому гражданину старше 18-ти лет, выглядит вот так (стоит 400 рублей):
fire-device

К нему надо будет докупить заряды (пластиковые, по 400 рублей за 15 штук):
fire-plastic

Бывают ещё металлические по 600 рублей за те же 15 штук (на фотографии использованные заряды):
fire-metal

Устройство работает примитивно — отводим боёк в режим зарядки, затем завинчиваем заряд (пластиковые завинчивать проще, металлические сложнее). После этого боёк отводится до упора и, БАХ, полетел огонёк. Громко, весело — вот это всё.

Только не направляйте на людей, машины, животных и провода. Когда будете накручивать заряд, будьте аккуратней — смотрите чтобы заряд не бахнул вам в ладонь или по пальцам. Всё-таки пальцев всего десять штук а зарядов пятнадцать.

Как мы играли гранатой в футбол

Давным-давно, лет двадцать назад на улицах нашей криминальной столицы одни бандиты решали других бандитов и прочих граждан, некстати попавших под горячую руку. Это были времена моей юности, это были лихие девяностые.

В то время я был мал телом и глуп умом и однажды, гуляя с соседом во дворе нашего дома, мы нашли под машиной клёвую, круглую, зелёную штуку. Выглядела она примерно так:


Граната РГД-5 без взрывателя (источник картинки — сайт 61.mvd.ru)

Что обычно делает детвора с круглыми штуками во дворе, когда ей нечем заняться? Правильно! Мы начали её пинать об стену, другие машины и случайных прохожих. В результате круглая штука приняла  довольно жалкий вид. Она перестала быть такой круглой, стала помятой, краска местами облупилась, но по-прежнему была клёвой. Поэтому, наигравшись час-полтора или около того, я притащил её домой и положил в прихожей.

Надо заметить, что в той громадной коммунальной квартире прихожие были у всех соседей. Ну как прихожая… просто шкафчик такой, советский, с местом где присесть. Но это так, к слову.

Через пару часов вернулся с работы отец и, сделав большие глаза (вот такие О_О), схватил эту штуку и куда-то убежал. Конечно было слегка обидно лишиться такого артефакта, но что поделаешь — отец всегда прав. Если отец не прав, то смотри пункт один.

В общем, этой штукой оказалась граната без взрывателя — как объяснил по возвращению из милиции отец и я уже сейчас не вспомню — боевая она была или учебная.

Придя в милицию, он выложил перед дежурным артефакт и сказал: «Вот! Дети нашли под машиной». Дежурный поменялся в лице и, отодвинувшись подальше от стола, ответил: «Подождите, я вызову саперов».

Дальнейшая судьба артефакта мне неизвестна. Возможно её вывезли на полигон и взорвали, а может просто вернули владельцу с извинениями. То время было довольно мрачным, так что всё возможно. Вот, например, батю на дороге подставили, он заплатил бешеные деньги за царапину на чужой машине, а спустя пару дней нашу квартиру ограбили, но это уже другая история…

Где-то полгода-год назад в ЖЖ проходила акция: «Выложим фотографии тех счастливых 90-х», когда толпа блогеров пела дифирамбы о том, как всё было прекрасно лично у них. Можете считать мою историю заявкой на участие.

Блог Евгения Жирнова