Тестирование
На рисунке 1 представлено исходное изображение, разрешение 483 604, глубина цвета 32 бит, размер 1,11 Мб.
Рисунок 1 – Исходное изображение
Масштабирование
На рисунке 2 представлено изображение, получившееся в результате выполнения программы. Разрешение 1000 500, размер 1,28 Мб.
Рисунок 2 – Изображение после масштабирование
Уменьшение глубины цвета
На рисунке 3 представлено изображение после уменьшения глубины цвета до 4 бит, разрешение 483 604, размер 144 кб.
Рисунок 3 – Изображение после уменьшения глубины изображения до 4 бит
На рисунке 4 представлено изображение после уменьшения глубины цвета до 8 бит, разрешение 483 604, размер 286 кб.
Рисунок 4 – Изображение после уменьшения глубины изображения до 8 бит
Код реализации
Image* im1 = new Image((char*)"Безымянный.bmp"); // Создание объекта изображения из файла
Image* im2 = new Image(0, 24, 900, 500);// Создание изображения с заданными параметрами
Image im3, im4;// Создание пустого изображения
(*im2) /= *im1; // Приведение img к масштабу img2
im3 = *im1 / 8; // делаем битность 8
im4 = *im1 / 4; // делаем битность 4
im3.writeimage((char*)"img_8.bmp");
im4.writeimage((char*)"img_4.bmp");
im2->writeimage((char*)"img_res.bmp");
Вывод
В ходе выполнения работы был изучен теоретический материал для работы с bmp файлами. Также была написана программа, позволяющая изменять размер исходного изображения и глубину цвета до 4 и 8 бит. Для хранения изображения используется одномерный массив.
Листинг программы
#define _CRT_SECURE_NO_WARNINGS
#include <locale>
#include <stdio.h>
using namespace std;
#pragma pack(push,1)
struct BITMAPFILEHEADER
{
unsigned short Type;
unsigned long Size; // Размер файла в байтах, BitCount*Height*Width+ OffsetBits
unsigned short Reserved1; // Зарезервирован; должен быть нуль
unsigned short Reserved2; // Зарезервирован; должен быть нуль
unsigned long OffsetBits; // Смещение данных от начала файла в байтах
};
#pragma pack(pop)
#pragma pack(push,1)
struct BITMAPINFOHEADER
{
unsigned long Size; // Число байтов необходимое для структуры = 40
unsigned long Width; // Ширина точечного рисунка в пикселях
unsigned long Height; // Высота точечного рисунка в пикселях
unsigned short Planes; // Число плоскостей целевого устройства = 1
unsigned short BitCount; // Глубина цвета, число бит на точку = 0,1,4,8,16,24,32
unsigned long Compression; // Тип сжатия = 0 для несжатого изображения
unsigned long SizeImage; // Размер изображения в байтах BitCount*Height*Width
unsigned long XPelsPerMeter; // Разрешающая способность по горизонтали
unsigned long YPelsPerMeter; // Разрешающая способность по вертикали
unsigned long ColorUsed; // Число индексов используемых цветов. Если все цвета = 0
unsigned long ColorImportant; // Число необходимых цветов = 0
};
#pragma pack(pop)
#pragma pack(push,1)
struct RGBTRIPLE
{
unsigned char Blue;
unsigned char Green;
unsigned char Red;
};
#pragma pack(pop)
#pragma pack(push,1)
struct RGBQUAD
{
unsigned char Blue;
unsigned char Green;
unsigned char Red;
unsigned char Reserved;
};
#pragma pack(pop)
class Image {
BITMAPINFOHEADER BMInfoHeader; // Информационный заголовок изображения
RGBTRIPLE* Rgbtriple; // Массив с описанием пикселей типа RGBTRIPLE
RGBQUAD* Palette; // Палитра изображения(присутствует только, если глубина цвета равна 1, 4 или 8)
void setEmptyImageParams(); // Выставление внутренних параметров, соответствующих
// пустому изображению
int loadImageFF(FILE* f); // загружает изображение из переданного файла
void saveImageTF(FILE* f); // сохраняет изображение в файл
void SETHEADER(int Width, int Height, int BitCount); // устанавливает параметры заголовка изображения (BMInfoHeader)
// выделяет память под изображение
// если требуется форматом, создает и заполняет палитру
void INITIMAGE(const Image& Inp); // делает копию изображения (заполняет заголовок и копирует данные)
void copyDataIMAGE(const Image& Inp); // копирует данные при совпадении заголовков изображений
void copyAndConvertDataFromImage(const Image& Inp); // копирует и преобразует данные переданного изображения в текущий формат
bool COUNTBIT(unsigned short BitCount) // проверяет допустимость битности изображения
{
return BitCount == 24 || BitCount == 8 || BitCount == 4;
}
int addCOUNTBIT(int Width, unsigned short BitCount) // считает сколько нужно дописывать в файл байт после записи строки изображения,
// чтобы получить размет строки кратным 4
{
int remainder = getImageRowSize(Width, BitCount) % 4; // вычисляем остаток от деления длины строки на 4
return remainder ? 4 - remainder : 0; // возвращаем дополнение до 4
}
int getImageRowSize(int Width, unsigned short BitCount) // возвращает сколько байт требуется для записи строки изображения в файл
{
// Width * BitCount дает необходимое число бит для хранения строки изображения
// ((Width * BitCount + 7) / 8) вычисляет количество байт и округляет в большую сторону
return (Width * BitCount + 7) / 8;
}
int getTotalImageSize(int Width, int Height, unsigned short BitCount) // возвращает полный размер данных при записи в файл
{
// вычисляем сколько байт требуется для записи строки изображения в файл, добавляем нужное количество байт для кратности 4 и умножаем на количество строк
return (getImageRowSize(BMInfoHeader.Width, BMInfoHeader.BitCount) + addCOUNTBIT(Width, BitCount)) * Height;
}
unsigned char getGrayscaleColor(unsigned char Red, unsigned char Green, unsigned char Blue) // вычисляет градацию серого для переданного цвета
{
int result = Red * 0.299 + Green * 0.597 + Blue * 0.114; // формула из методички
if (result > 255) // проверяем выход за границы байта
{
result = 255;
}
return (unsigned char)result;
}
unsigned char getGrayscaleColor(RGBTRIPLE color) // вычисляет градацию серого для переданного цвета
{
return getGrayscaleColor(color.Red, color.Green, color.Blue);
}
unsigned char getGrayscaleColor(RGBQUAD color) // вычисляет градацию серого для переданного цвета
{
return getGrayscaleColor(color.Red, color.Green, color.Blue);
}
unsigned char getNearestPaletteColorIndex(unsigned char grayscaleColor) // бинарным поиском ищет ближайший цвет в палитре (так как она упорядочена по возрастанию оттенков серого)
{
int minIndex = 0; // установка минимального индекса для поиска
int maxIndex = BMInfoHeader.ColorUsed - 1; // установка максимального индекса для поиска
while (maxIndex >= minIndex) // продолжаем искать пока минимальный индекс не перейдет за максимальный
// (в этом случае значение grayscaleColor находится между Palette[minIndex - 1] и Palette[minIndex], так как сначала всегда
// проверяем можно ли изменить minIndex)
{
int middleIndex = (minIndex + maxIndex) / 2; // вычисление среднего индекса между крайними
if (Palette[middleIndex].Red == grayscaleColor) // проверяем не нашли ли искомое значение (так как палитка состоит из оттенков серого
// Palette[middleIndex].Red == Palette[middleIndex].Green == Palette[middleIndex].Blue)
{
return middleIndex; // возвращаем индекс палитры в случае успеха
}
else if (Palette[middleIndex].Red < grayscaleColor) // если текущее значение палитры оказалось меньше требуемого,
// сдвигаем левый край поиска
{
minIndex = middleIndex + 1;
}
else
maxIndex = middleIndex - 1; // если текущее значение палитры оказалось больше требуемого,
// сдвигаем правый край поиска
}
if (minIndex == BMInfoHeader.ColorUsed) // если minIndex стало равным общему количеству элементов, то значит переданное значение
// больше максимального значения в палитре
{
return BMInfoHeader.ColorUsed - 1; // поэтому возвращаем индекс наибольшего значения палитры
}
if (minIndex == 0) // если minIndex не изменился, то все время сдвигался правый конец области поиска
// значит значение grayscaleColor меньше минимального в палитре
{
return 0; // поэтому возвращаем индекс минимального значения палитры
}
int prevDelta = grayscaleColor - Palette[minIndex - 1].Red; // в оставшемся случае, как было указано ранее,
// значение grayscaleColor находится между Palette[minIndex - 1] и Palette[minIndex]
// потому считаем от какого значения оно отстоит меньше
int nextDelta = Palette[minIndex].Red - grayscaleColor;
return prevDelta < nextDelta ? minIndex - 1 : minIndex; // и возвращаем этот индекс
}
public:
Image(char Mode, unsigned short BCount, int Width, int Height); // Конструктор создания изображения
Image(char* fileName); // Конструктор объекта изображения из файла
Image(); // Конструктор без параметров, создает пустой контейнер под изображение
Image(const Image& i); // Конструктор копии
~Image(); // Деструктор
int loadimage(char* fileName); // метод загрузки изображения аналогичный конструктору, возвращает 0 в случае ошибки
void writeimage(char* fileName); // метод записи изображения в файл
Image& operator = (const Image& Inp); // Перегрузка оператора =
Image operator / (short Depth); // Перегрузка оператора /
Image& operator /= (const Image& Inp); // Перегрузка оператора /=
};
// Реализация конструктора без параметров
Image::Image()
{
setEmptyImageParams(); // Заполняем параметры пустого изображения
}
Image::Image(char* fileName)
{
setEmptyImageParams(); // Заполняем параметры пустого изображения
loadimage(fileName); // выполняем загрузку из файла с переданным именем
}
// Реализация деструктора
Image::~Image()
{
if (Rgbtriple) // Если есть указатель на данные, то нужно очистить выделенную под изображение память
{
/* for (int i = 0; i < BMInfoHeader.Height; i++) // Проходим по всем строкам изображения
{
delete[] Rgbtriple[i]; // и удаляем выделенную под строку память
}*/
delete[] Rgbtriple; // Удаляем память, содержащую массив указателей на строки
Rgbtriple = NULL; // Инициализируем указатель на данные невалидным значением
// (не обязательно, так как пороисходит в деструкторе)
}
if (Palette) // Удаление палитры, если она была создана
{
delete[] Palette;
Palette = NULL;
}
}
void Image::SETHEADER(int Width, int Height, int BitCount)
{
// Заполняем необходимые поля информации об изображении
BMInfoHeader.Width = Width; // Ширина точечного рисунка в пикселях
BMInfoHeader.Height = Height; // Высота точечного рисунка в пикселях
BMInfoHeader.Planes = 1; // Число плоскостей целевого устройства = 1
BMInfoHeader.BitCount = BitCount; // Глубина цвета, число бит на точку
BMInfoHeader.SizeImage = getTotalImageSize(Width, Height, BitCount); // Размер изображения в байтах
if (BMInfoHeader.BitCount <= 8)
{
BMInfoHeader.ColorUsed = 1 << BMInfoHeader.BitCount; // = 2 в степени BMInfoHeader.BitCount
Palette = new RGBQUAD[BMInfoHeader.ColorUsed]; // Выделение памяти под палитру цветов
for (int i = 0; i < BMInfoHeader.ColorUsed; i++) // заполняем значения палитры градациями серого от 0 до 255
{
unsigned char color = (255 * i / (BMInfoHeader.ColorUsed - 1));
Palette[i].Red = color;
Palette[i].Green = color;
Palette[i].Blue = color;
Palette[i].Reserved = 0;
}
}
// Выделение памяти для двумерного массива размером Height*Width типа RGBTRIPLE
// Выделяем память под указатели на строки
if (BMInfoHeader.SizeImage > 0)
{
Rgbtriple = new RGBTRIPLE[BMInfoHeader.Height * BMInfoHeader.Width];
/*Rgbtriple = new RGBTRIPLE * [BMInfoHeader.Height];
for (int i = 0; i < BMInfoHeader.Height; i++)
{
// Выделяем память под каждую строку изображения
Rgbtriple[i] = new RGBTRIPLE[BMInfoHeader.Width];
}*/
}
else
{
Rgbtriple = NULL;
}
}
// Реализация конструктора создания изображения
Image::Image(char Mode, unsigned short BCount, int Width, int Height)
{
// Выставление начальных параметров, соответствующих пустому изображению
setEmptyImageParams();
// Проверяем какую желаемую битность нам передали в конструктор
// Поддерживается хранение данных только в RGBTRIPLE
if (COUNTBIT(BCount))
{
SETHEADER(Width, Height, BCount); // заполняем заголовок изображения и выделяем памят
if (Palette)
{
Mode = Palette[getNearestPaletteColorIndex(Mode)].Red; // получаем ближайший к переданному цвет в палитре, если она используется
}
// Заполнение данных изображения переданным значением
for (int i = 0; i < (int)BMInfoHeader.Height; i++){
for (int j = 0; j < (int)BMInfoHeader.Width; j++)
{
Rgbtriple[i * BMInfoHeader.Width + j].Red = Mode;
Rgbtriple[i * BMInfoHeader.Width + j].Green = Mode;
Rgbtriple[i * BMInfoHeader.Width + j].Blue = Mode;
}
}
}
// Задали неподдерживаемый параметр битности
}
// Выставление параметров, соответствующих пустому изображению
void Image::setEmptyImageParams()
{
Rgbtriple = NULL;
Palette = NULL;
BMInfoHeader.Size = 40;
BMInfoHeader.Width = 0;
BMInfoHeader.Height = 0;
BMInfoHeader.Planes = 0;
BMInfoHeader.BitCount = 0;
BMInfoHeader.Compression = 0;
BMInfoHeader.SizeImage = 0;
BMInfoHeader.XPelsPerMeter = 0;
BMInfoHeader.YPelsPerMeter = 0;
BMInfoHeader.ColorUsed = 0;
BMInfoHeader.ColorImportant = 0;
}
int Image::loadImageFF(FILE* f)
{
BITMAPFILEHEADER BMFileHeader;
// считываем заголовок изображения
fread(&BMFileHeader, sizeof(BITMAPFILEHEADER), 1, f);
// загружаем заголовок изображения
BITMAPINFOHEADER FileBMInfoHeader;
fread(&FileBMInfoHeader, sizeof(BITMAPINFOHEADER), 1, f);
// заполняем заголовок и выделяем память для картинки, описанной в файле
SETHEADER(FileBMInfoHeader.Width, FileBMInfoHeader.Height, FileBMInfoHeader.BitCount);
RGBQUAD* filePalette = NULL;
// Создаем и загружаем файловую палитру, если она присутствует
if (BMInfoHeader.BitCount <= 8)
{
filePalette = new RGBQUAD[BMInfoHeader.ColorUsed];
fread(filePalette, sizeof(RGBQUAD), BMInfoHeader.ColorUsed, f);
}
// переходим к началу данных изображения
fseek(f, BMFileHeader.OffsetBits, SEEK_SET);
// вычисляем смещение между строками
const int additionalRowOffset = addCOUNTBIT(FileBMInfoHeader.Width, FileBMInfoHeader.BitCount);
if (FileBMInfoHeader.BitCount == 24)
{
for (int i = BMInfoHeader.Height - 1; i >= 0; i--)
{
// считываем сразу всю строку изображенмя, так как она совпадает с внутренним форматом хранения в памяти
fread(Rgbtriple + i * FileBMInfoHeader.Width, sizeof(RGBTRIPLE), FileBMInfoHeader.Width, f);//
//fread(Rgbtriple[i], sizeof(RGBTRIPLE), FileBMInfoHeader.Width, f);
// пропускаем смещение между строками
if (additionalRowOffset)
{
fseek(f, additionalRowOffset, SEEK_CUR);
}
}
}
else if (FileBMInfoHeader.BitCount == 32)
{
// считываем каждый пиксель отдельно
for (int i = BMInfoHeader.Height - 1; i >= 0; i--)
{
for (int j = 0; j < BMInfoHeader.Width; j++)
{
RGBQUAD fileQuad;
fread(&fileQuad, sizeof(RGBQUAD), 1, f);
Rgbtriple[i * BMInfoHeader.Width + j].Red = fileQuad.Red;
Rgbtriple[i * BMInfoHeader.Width + j].Green = fileQuad.Green;
Rgbtriple[i * BMInfoHeader.Width + j].Blue = fileQuad.Blue;
}
}
}
else // загрузка изображений с палитрой
{
const unsigned char topBitsOffset = 8 - BMInfoHeader.BitCount; // показывает на сколько нужно переметить биты из старшей части байта, чтобы получить цвет
unsigned char colorMask = (1 << BMInfoHeader.BitCount) - 1; // выставляем младшие BMInfoHeader.BitCount бит в 1 для получения маски цвета
colorMask <<= 8 - BMInfoHeader.BitCount; // перемещаем эти единицы в старшие биты байта, так как данные располагаются от старших битов к младшим
for (int i = BMInfoHeader.Height - 1; i >= 0; i--)
{
int leftBits = 0; // указывает сколько непрочитанных бит осталось в байте
unsigned char paletteByte = 0; // текущий считанный байт в изображении
for (int j = 0; j < BMInfoHeader.Width; j++)
{
if (!leftBits) // если в текущем байте рассмотрели все биты, то считываем следующий
{
fread(&paletteByte, 1, 1, f);
leftBits = 8;
}
int paletteIndex = (paletteByte & colorMask) >> topBitsOffset; // получаем текущий индекс в палитре файла применением маски и сдвигом
// полученного значения
leftBits -= BMInfoHeader.BitCount; // обновляем количество необработанных бит
paletteByte <<= BMInfoHeader.BitCount; // перемещаем их в старшую часть байта
unsigned char sourceGrayscale = getGrayscaleColor(filePalette[paletteIndex]); // вычисляем оттенок серого для цвета пикселя из файла
unsigned char grayscale = Palette[getNearestPaletteColorIndex(sourceGrayscale)].Red; // получаем ближайший к нему цвет из палиты
// заполняем данные изображения
Rgbtriple[i * BMInfoHeader.Width + j].Red = grayscale;
Rgbtriple[i * BMInfoHeader.Width + j].Green = grayscale;
Rgbtriple[i * BMInfoHeader.Width + j].Blue = grayscale;
}
// пропускаем смещение между строками изображения в файле
if (additionalRowOffset)
{
fseek(f, additionalRowOffset, SEEK_CUR);
}
}
}
// удаляем файловую палитру, если она была создана
if (filePalette)
{
delete[] filePalette;
}
return 1;
}
// Реализация метода загрузки изображения
int Image::loadimage(char* fileName)
{
FILE* f;
// открываем требуемый файл
f = fopen(fileName, "rb"); // необходимо открывать бинарный файл
// производим загрузку данных
int resultCode = loadImageFF(f);
// закрываем файл
fclose(f);
return resultCode;
}
void Image::saveImageTF(FILE* f) // сохраняет данные в переданный файл
{
// получаем смещение между строками изображения в файле
const int additionalRowOffset = addCOUNTBIT(BMInfoHeader.Width, BMInfoHeader.BitCount);
// Заполняем заголовок файла BMP
BITMAPFILEHEADER BMFileHeader;
BMFileHeader.Type = 0x4D42; // сигнатура
BMFileHeader.OffsetBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // смещение до данных изображения
// полный размер файла = сумма размера всех заголовков, палитры и данных файла
BMFileHeader.Size = BMFileHeader.OffsetBits + getTotalImageSize(BMInfoHeader.Width, BMInfoHeader.Height, BMInfoHeader.BitCount);
BMFileHeader.Reserved1 = 0; // не используется
BMFileHeader.Reserved2 = 0; // не используется
if (Palette) // добавляем данные о палитре
{
const int paletteSize = sizeof(RGBQUAD) * BMInfoHeader.ColorUsed; // вычисляем размер палитры
BMFileHeader.Size += paletteSize; // добавляем его к общему размеру файла
BMFileHeader.OffsetBits += paletteSize; // и к смещению до данных изображения
}
// записываем заголовок файла
fwrite(&BMFileHeader, sizeof(BITMAPFILEHEADER), 1, f);
// записываем заголовок изображения
fwrite(&BMInfoHeader, sizeof(BITMAPINFOHEADER), 1, f);
if (BMInfoHeader.BitCount == 24)
{
for (int i = BMInfoHeader.Height - 1; i >= 0; i--)
{
// для формата 24 бит просто копируем в файл строку изображения, так как он совпадает с внутренним форматом хранения
fwrite(Rgbtriple + i * BMInfoHeader.Width, sizeof(RGBTRIPLE), BMInfoHeader.Width, f);
// записываем нужное количество 0 для смещения между строками изображения
if (additionalRowOffset)
{
const int Zero = 0;
fwrite(&Zero, 1, additionalRowOffset, f);
}
}
}
else // изображение с палитрой
{
// вычисляем смещение необходимое для перемещения индекса цвета первого пикселя в старшие биты
const int startPaletteDataOffset = 8 - BMInfoHeader.BitCount;
// сохранение изображений с палитрой
for (int i = BMInfoHeader.Height - 1; i >= 0; i--)
{
int currentPaletteDataOffset = startPaletteDataOffset; // выставляем на сколько нужно сместить следующее значение при записи в байт
int leftBits = 8; // храним сколько бит еще можно использовать в байте
int paletteByte = 0; // хранит текущее значение для записи в файл
for (int j = 0; j < BMInfoHeader.Width; j++)
{
unsigned char paletteColorIndex = getNearestPaletteColorIndex(Rgbtriple[i * BMInfoHeader.Width + j].Red); // получаем индекс палитры для текущего цвета
paletteByte |= paletteColorIndex << currentPaletteDataOffset; // смещаем его на нужное количество бит и записываем в результат
currentPaletteDataOffset -= BMInfoHeader.BitCount; // уменьшаем требуемый сдвиг для следующего индека
leftBits -= BMInfoHeader.BitCount; // уменьшаем количество оставшихся бит
if (!leftBits)
{
// если кончились свободные биты, то записываем собранный байт в файл
fwrite(&paletteByte, 1, 1, f);
// и выставляем параметры на начальные
currentPaletteDataOffset = startPaletteDataOffset;
leftBits = 8;
paletteByte = 0;
}
}
// если после завершения обработки строки остались незаписанные биты (байт полностью не заполнился), то записываем их в файл
if (leftBits != 8)
{
fwrite(&paletteByte, 1, 1, f);
}
// записываем нужное количество 0 для смещения между строками изображения
if (additionalRowOffset)
{
const int Zero = 0;
fwrite(&Zero, 1, additionalRowOffset, f);
}
}
}
}
void Image::INITIMAGE(const Image& Inp) // делает копию изображения (заполняет заголовок и копирует данные)
{
setEmptyImageParams(); // Заполняем параметры пустого изображения
SETHEADER(Inp.BMInfoHeader.Width, Inp.BMInfoHeader.Height, Inp.BMInfoHeader.BitCount); // заполняем заголовок и выделяем нужную память
copyDataIMAGE(Inp); // копируем данные изображения
}
void Image::copyDataIMAGE(const Image& Inp)
{
// Проверяем совпадение разрешения и битности, после чего скопируем данные
if (BMInfoHeader.Width == Inp.BMInfoHeader.Width
&& BMInfoHeader.Height == Inp.BMInfoHeader.Height
&& BMInfoHeader.BitCount == Inp.BMInfoHeader.BitCount)
{
//Копирование строк изображения
//const int rowSize = BMInfoHeader.Width * sizeof(RGBTRIPLE);
memcpy(Rgbtriple, Inp.Rgbtriple, BMInfoHeader.Height * BMInfoHeader.Width * sizeof(RGBTRIPLE));
/* for (int i = 0; i < BMInfoHeader.Height; ++i)
{
memcpy(Rgbtriple[i], Inp.Rgbtriple[i], rowSize);
}*/
// Замечание: палитру не копируем, так как она создается автоматически при инициализации заголовка изображения
// и одинакова для выбранной битности
}
}
Image::Image(const Image& im)
{
INITIMAGE(im); // делаем копию изображения
}
void Image::writeimage(char* fileName) // выполняет запись в файл
{
FILE* f;
f = fopen(fileName, "w+b"); // необходимо открывать бинарный файл
saveImageTF(f); // сохраняет данные в файл
fclose(f);
}
Image& Image::operator = (const Image& Inp) // Перегрузка оператора =
{
if (Rgbtriple) // если изображение уже создано
{
copyDataIMAGE(Inp); // то пробуем только скопировать данные (при совпадении форматов)
}
else
{
INITIMAGE(Inp); // иначе создаем копию
}
return *this;
}
void Image::copyAndConvertDataFromImage(const Image& Inp) // функция преобразования данных переданного изображения в текущий формат
{
const bool isSourceWithPalette = Inp.Palette != NULL; // используется ли в исходном изображении палитра
for (int i = 0; i < BMInfoHeader.Height; i++)
{
for (int j = 0; j < BMInfoHeader.Width; j++)
{
unsigned char grayscale = isSourceWithPalette ? Inp.Rgbtriple[i * BMInfoHeader.Width + j].Red // для исходного изображения с палитрой берем оттенок серого сразу из данных картинки
: getGrayscaleColor(Inp.Rgbtriple[i * BMInfoHeader.Width + j]); // иначе вычисляем его
grayscale = Palette[getNearestPaletteColorIndex(grayscale)].Red; // получаем значение серого в палитре итогового изображения
// записываем оттенок серого в пиксель итогового изображения
Rgbtriple[i * BMInfoHeader.Width + j].Red = grayscale;
Rgbtriple[i * BMInfoHeader.Width + j].Green = grayscale;
Rgbtriple[i * BMInfoHeader.Width + j].Blue = grayscale;
}
}
}
Image Image::operator / (short Depth) // Перегрузка оператора
{
Image result(0, Depth, BMInfoHeader.Width, BMInfoHeader.Height); // создаем пустое изображение
result.copyAndConvertDataFromImage(*this); // выполняем преобразование текущего изображения в итоговое
return result;
}
Image& Image::operator /= (const Image& Inp) // Перегрузка оператора /=, выполняет запись переданного изображения в текущее с изменением размера
{
float xRatio = (float)Inp.BMInfoHeader.Width / BMInfoHeader.Width; // вычисление соотношения ширины изображений
float yRatio = (float)Inp.BMInfoHeader.Height / BMInfoHeader.Height; // вычисление соотношения высоты изображений
for (int i = 0; i < BMInfoHeader.Height; i++)
{
for (int j = 0; j < BMInfoHeader.Width; j++)
{
int sourceX = (int)(j * xRatio); // вычисление x координаты в исходном изображении
int sourceY = (int)(i * yRatio); // вычисление y координаты в исходном изображении
Rgbtriple[i * BMInfoHeader.Width + j] = Inp.Rgbtriple[sourceY * Inp.BMInfoHeader.Width + sourceX]; // запись значения цвета из исходного изображения в текущее
}
}
return *this;
}
int main(void)
{
setlocale(LC_ALL, "Russian");
Image* im1 = new Image((char*)"Image.bmp"); // Создание объекта изображения из файла
Image* im2 = new Image(0, 24, 900, 500);// Создание изображения с заданными параметрами
Image im3, im4;// Создание пустого изображения
(*im2) /= *im1; // Приведение img к масштабу img2
im3 = *im1 / 8; // делаем битность 8
im4 = *im1 / 4; // делаем битность 4
im3.writeimage((char*)"img_8.bmp");
im4.writeimage((char*)"img_4.bmp");
im2->writeimage((char*)"img_res.bmp");
return 0;
}