using Color = std::uint32_t;
/// Тип "координата". Синоним 32-битного беззнакового целого.
using Coordinate = std::uint32_t;
/// Тип "точка". Имеет две координаты.
struct Point
{
Coordinate x, y;
/// Создать точку в начале координат.
Point()
: x(0), y(0) {}
/// Создать точку с заданными координатами.
Point(Coordinate x, Coordinate y)
: x(x), y(y) {}
};
/// Тип "размеры" (прямоугольной области). Два размера: ширина и высота.
struct Extents
{
Coordinate width, height;
/// Создать область нулевых размеров.
Extents()
: width(0), height(0) {}
/// Создать область с заданными шириной и высотой.
Extents(Coordinate width, Coordinate height)
: width(width), height(height) {}
/// Вычислить площадь области.
std::size_t area() const { return std::size_t(width) * height; }
/// Проверить, выходят ли координаты точки за пределы области.
bool contains(Point point) const
{
return point.x < width && point.y < height;
}
};
/// Тип "картинка". Управляет массивом пикселей.
class Picture
{
Color *pixels; // упакованный двумерный массив
Extents extents;
/// Выделить память и выполнить копирование данных из другой картинки.
void copy_other(const Picture &other)
{
const auto area = extents.area();
pixels = new Color[area];
std::memcpy(pixels, other.pixels, area);
}
/// Удостовериться, что координаты точки корректны.
void assert_point(Point point) const
{
assert(extents.contains(point));
}
public:
/// Освободить занимаемую память.
void clear()
{
delete[] pixels;
pixels = nullptr;
extents = Extents();
}
/// Обменять содержимое двух объектов.
void swap(Picture &other)
{
std::swap(pixels, other.pixels);
std::swap(extents, other.extents);
}
/// Завершить существование объекта картинки.
~Picture() { clear(); }
/// Создать пустую картинку.
Picture()
: pixels(nullptr) {}
/// Создать картинку заданных размеров с неопределённым содержимым.
explicit Picture(Extents extents)
: pixels(nullptr), extents(extents)
{
pixels = new Color[extents.area()];
}
/// Создать копию картинки.
Picture(const Picture &other)
: pixels(nullptr), extents(other.extents)
{
copy_other(other);
}
/// Скопировать картинку присваиванием.
Picture& operator=(const Picture &other)
{
return *this = Picture(other);
}
/// Переместить картинку, не копируя данные.
Picture(Picture &&other)
: pixels(other.pixels), extents(other.extents)
{
other.pixels = nullptr;
other.extents = Extents();
}
/// Переместить картинку при присваивании, не копируя данные.
Picture& operator=(Picture &&other)
{
assert(this != &other);
clear();
swap(other);
return *this;
}
/// Получить размеры картинки.
const Extents& size() const { return extents; }
/// Получить указатель на пиксели.
const Color* data() const { return pixels; }
Color* data() { return pixels; }
/// Обратиться к одному пикселю по его координатам.
const Color& operator()(Point point) const
{
assert_point(point);
return pixels[point.x + extents.width * point.y];
}
Color& operator()(Point point)
{
assert_point(point);
return pixels[point.x + extents.width * point.y];
}
};
/// Получить значение цвета в формате BGRX для заданных значений трёх каналов модели RGB.
inline Color bgrx(unsigned red, unsigned green, unsigned blue)
{
return (blue & 0xFF) | ((green & 0xFF) << 8) | ((red & 0xFF) << 16);
}
/// Закрасить картинку целиком одним цветом.
void fill(Picture &picture, Color color)
{
const auto pixels = picture.size().area();
const auto data = picture.data();
for (std::size_t i = 0; i < pixels; ++i)
data[i] = color;
}
/// Заливка связной области.
/// Рекурсия заменена на стек+цикл = поиск в глубину по соседним пикселям.
void flood_fill(Picture &picture, Point start, Color color)
{
const auto target_color = picture(start);
std::stack
points;
points.push(start);
do
{
start = points.top();
points.pop();
if (picture.size().contains(start) && picture(start) == target_color)
{
picture(start) = color;
points.emplace(start.x - 1, start.y);
points.emplace(start.x + 1, start.y);
points.emplace(start.x, start.y - 1);
points.emplace(start.x, start.y + 1);
}
} while (!points.empty());
}
/// Обвести фрагменты заданного цвета (what) контуром заданной толщины (в пикселях).
/// Алгоритм основан на ограниченном поиске в ширину.
void make_outline(Picture &picture, Color what, Color outline, unsigned width)
{
struct Pos
{
Point pos;
Color* ptr;
unsigned distance;
Pos(Coordinate x, Coordinate y, Color *ptr, unsigned d)
: pos(x, y), ptr(ptr), distance(d) {}
};
std::queue
positions;
const auto data = picture.data();
const auto pw = picture.size().width,
ph = picture.size().height;
// Сканируем картинку в поисках пикселей цвета what и
// добавляем их координаты в очередь со значением distances == 0.
for (Coordinate row = 0, i = 0; row < ph; ++row)
for (Coordinate col = 0; col < pw; ++col, ++i)
if (data[i] == what)
positions.emplace(col, row, data + i, 0);
// Выполняем ограниченный по дальности поиск в ширину, используя накопленную очередь.
while (!positions.empty())
{
const auto &cur = positions.front();
const auto x = cur.pos.x, y = cur.pos.y;
const auto ptr = cur.ptr;
auto distance = cur.distance;
positions.pop();
if (*ptr != what)
*ptr = outline;
if (++distance < width)
{
if (x != 0)
positions.emplace(x - 1, y, ptr - 1, distance);
if (x != pw - 1)
positions.emplace(x + 1, y, ptr + 1, distance);
if (y != 0)
positions.emplace(x, y - 1, ptr - pw, distance);
if (y != ph - 1)
positions.emplace(x, y + 1, ptr + pw, distance);
}
}
}
// Загрузка и сохранение простого варианта BMP.
// По данным https://en.wikipedia.org/wiki/BMP_file_format
/// Follows minimalistic BITMAPINFOHEADER structure.
#if defined(_MSC_VER) // Компилируем MSVC?
#pragma pack(push) // Сохранить текущее значение упаковки структур.
#pragma pack(1) // Нестандартное расширение MSVC: упакованная структура.
struct
#else
struct __attribute__((packed)) // Нестандартное расширение GCC: упакованная структура.
#endif
Bmp_header
{
// BMP header
char b = 'B', m = 'M';
std::uint32_t file_size = 54; // == data_offset + pixels size in bytes
std::uint16_t reserved1 = 0, reserved2 = 0;
std::uint32_t data_offset = 54; // this header size in bytes
// DIB header (BITMAPINFOHEADER)
std::uint32_t header_size = 40;
std::int32_t bitmap_width = 0, bitmap_height = 0;
std::uint16_t color_planes = 1;
std::uint16_t bpp = 32; // bits per pixel
std::uint32_t compression_method = 0; // 0 = none (BGRX), 3 = XBGR
std::uint32_t image_size = 0;
std::uint32_t x_dpi = 96, y_dpi = 96; // actually ignored
std::uint32_t palette_size = 0;
std::uint32_t important_colors = 0;
/// Конструктор по умолчанию (пустая картинка).
Bmp_header() = default;
/// Конструктор на основе картинки.
explicit Bmp_header(const Picture &picture)
{
bitmap_width = picture.size().width;
bitmap_height = picture.size().height;
file_size = 54 + picture.size().area() * sizeof(Color); // возможно переполнение
}
/// Проверка заголовка на корректность (ограничено возможностями данной реализации).
bool is_valid() const
{
return b == 'B' && m == 'M' &&
data_offset >= 54 && header_size >= 40 &&
bitmap_width != 0 && bitmap_height != 0 && color_planes == 1 &&
bpp == 32 && (compression_method == 0 || compression_method == 3);
// other values ignored
}
};
#if defined(_MSC_VER)
#pragma pack(pop) // Восстановить предыдущее значение упаковки структур.
#endif
// Вспомогательные функции, выполняющие насилие над системой типов
// (конверсия произвольного указателя в указатель на char/const char).
inline char* to_byte_ptr(void *ptr) { return (char*)ptr; }
inline const char* to_byte_ptr(const void *ptr) { return (const char*)ptr; }
/// Преобразование формата пикселя.
inline Color xbgr_to_bgrx(Color color)
{
return (color << 24) | (color >> 8);
}
/// Прочитать BMP из потока (фиксированный формат: 32 бита на пиксель, нет сжатия).
/// Поток должен быть открыт в двоичном режиме.
std::istream& read_bmp_32bpp(std::istream &is, Picture &picture)
{
// Считать заголовок из файла.
Bmp_header header;
is.read(to_byte_ptr(&header), header.data_offset);
if (!is || !header.is_valid())
{
is.setstate(std::ios::failbit);
return is;
}
// Создать картинку нужных размеров.
picture = Picture(Extents(header.bitmap_width, header.bitmap_height));
// Пропустить заданное число байт после заголовка.
is.ignore(header.data_offset - sizeof(header));
// Считать пиксели.
is.read(to_byte_ptr(picture.data()), picture.size().area() * sizeof(Color));
// Если "метод компрессии" == 3 (RGBA, результат работы GIMP), обратить порядок байт.
if (header.compression_method == 3)
{
const auto data = picture.data();
const auto sz = picture.size().area();
for (std::size_t i = 0; i < sz; ++i)
data[i] = xbgr_to_bgrx(data[i]);
}
// Вернуть результирующую картинку.
return is;
}
/// Записать BMP в двоичный поток.
std::ostream& write_bmp_32bpp(std::ostream &os, const Picture &picture)
{
// Построить и записать в файл соответствующий картинке заголовок.
Bmp_header header(picture);
os.write(to_byte_ptr(&header), header.data_offset);
// Записать пиксели.
os.write(to_byte_ptr(picture.data()), picture.size().area() * sizeof(Color));
return os;
}
///////////////////////////////////////////////////////////////////////////////
// Тестирование
#include
int main()
{
using namespace std;
// Test 1.
fstream bmp("100x50orange.bmp", ios::binary | ios::out | ios::trunc);
Picture picture(Extents(100, 50));
fill(picture, bgrx(255, 144, 0));
write_bmp_32bpp(bmp, picture);
bmp.close();
// Test 2.
picture.clear();
bmp.open("input.bmp", ios::binary | ios::in);
read_bmp_32bpp(bmp, picture);
bmp.close();
// Заливка белым из центра.
flood_fill(picture,
Point(picture.size().width / 2, picture.size().height / 2),
0xFFFFFFFF);
bmp.open("flood_fill.bmp", ios::binary | ios::out | ios::trunc);
write_bmp_32bpp(bmp, picture);
bmp.close();
// Test 3.
picture.clear();
bmp.open("input.bmp", ios::binary | ios::in);
read_bmp_32bpp(bmp, picture);
bmp.close();
// Белый контур вокруг чёрных пикселей.
make_outline(picture, bgrx(0, 0, 0), bgrx(255, 255, 255), 4);
bmp.open("outline.bmp", ios::binary | ios::out | ios::trunc);
write_bmp_32bpp(bmp, picture);
bmp.close();
Do'stlaringiz bilan baham: |