Алгоритм масштабирования изображений Для масштабирования изображения используется следующий подход: цвет пикселя в масштабированном изображении принимается равным цвету ближайшего к нему пикселя исходного изображения. Блок схема алгоритма масштабирования изображения. xRatio= Inp.BMInfoHeader.Width / BMInfoHeader.Width; yRatio = Inp.BMInfoHeader.Height / BMInfoHeader.Height; | sourceX = j * xRatio; sourceY = i * yRatio; Rgbtriple[i][j] = Inp.Rgbtriple[sourceY][sourceX]; | j = 0, j < BMInfoHeader.Width i<0 | i = 0, i < BMInfoHeader.Height i<0 | Масштабирования изображения происходит следующим образом: - Для начала вычисляются отношения высоты и ширины текущего изображения к исходному. - Затем вычисляются каждые координаты x и y исходного изображения. - После чего записывается значение цвета исходного изображения в текущее. Результаты масштабирования и изменения глубины цвета. Тестовый пример №1. Изначальное изображение имеет следующие параметры: Размер - 1666x1200; Глубина цвета - 24 бит.  Изображение, получившееся после выполнения программы: Размер - 400x300; Глубина цвета – 4 бит.  Код выполнения: Image im("anime.bmp"); // загружаем картинку в im Image im2(0, 4, 400, 300); // задаём параметры. Image*im3 = new Image(im / 4); //создание в памяти картинки являющейся копием им но с битностью 4 im2 /= *im3; // преобразуем битность к 4 создавай новое изображение im2.writeimage("result.bmp"); im3->writeimage("result2.bmp"); delete im3; Тестовый пример №2: Изначальное изображение имеет следующие параметры: Размер - 450 x 338; Глубина цвета - 24 бит.  Изображение, получившееся после выполнения программы: Размер - 90 x 65; Глубина цвета - 24 бит.  Код выполнения: Image*im4 = new Image("cat.bmp"); Image*im5 = new Image(0 , 24 , 90 , 65); (*im5)/=*im4; // зарисываем im4 в im5 по заданым размерам.а точнее уменьшаем картинку im5->writeimage("cats1.bmp"); Тестовый пример №3 В этом примере будет хорошо видна разница 8 битной и 4 битной картинки. Изначальное изображение имеет следующие параметры: Размер - 450 x 338; Глубина цвета - 24 бит.  Изображение, получившееся после выполнения программы: Размер - 900 x 680; Глубина цвета - 8 бит.  Переводим картинку в 4 бита и сравниваем их: Размер - 900 x 680; Глубина цвета - 4 бит  Как видно с примера,чем больше приобладает картинками битами,чем лучше её качество. Код выполнения: Image* im7 = new Image("cats1.bmp"); Image* im8 = new Image(0, 24, 900, 680); (*im8) /= *im7; Image im9(*im8 / 8); // делаем битность 8 im9.writeimage("cat_bw.bmp"); im8->writeimage("cat_res.bmp"); Листинг кода программы 1) Листинг описания класса и структур. #include <locale> #include<stdio.h> #pragma pack(push,1) struct BITMAPFILEHEADER { unsigned short Type; // ‘BM’ 0x4D42 unsigned long Size; // Размер файла в байтах, BitCount*Height*Width+ OffsetBits unsigned short Reserved1; // Зарезервирован; должен быть нуль unsigned short Reserved2; // Зарезервирован; должен быть нуль unsigned long OffsetBits; // Смещение данных от начала файла в байтах // = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER) }; #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 (6-ой вариант) RGBQUAD *Palette; // Палитра изображения(присутствует только, если глубина цвета равна 1, 4 или 8) void setEmptyImageParams(); // Выставление внутренних параметров, соответствующих // пустому изображению int loadImageDataFromFile(FILE* f); // загружает изображение из переданного файла void saveImageDataToFile(FILE* f); // сохраняет изображение в файл void setHeaderAndAllocateMemory(int Width, int Height, int BitCount); // устанавливает параметры заголовка изображения (BMInfoHeader) // выделяет память под изображение // если требуется форматом, создает и заполняет палитру void initializeFromImage(const Image &Inp); // делает копию изображения (заполняет заголовок и копирует данные) void copyDataFromImage(const Image &Inp); // копирует данные при совпадении заголовков изображений void copyAndConvertDataFromImage(const Image &Inp); // копирует и преобразует данные переданного изображения в текущий формат bool isAllowedBitCount(unsigned short BitCount) // проверяет допустимость битности изображения { return BitCount == 24 || BitCount == 8 || BitCount == 4; // 6-ой вариант } int getAdditionalRowOffsetInFile(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) + getAdditionalRowOffsetInFile(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) // если текущее значение палитры оказалось меньше требуемого, // сдвигаем левый край поиска // Замечание: если значения grayscaleColor нет в палитре, // то на предпоследнем заходе в while будет ситуация // когда minIndex == middleIndex == maxIndex - 1 и // Palette[middleIndex].Red < grayscaleColor < Palette[middleIndex + 1].Red // Тогда на этом заходе произойдет выполнение данного условия и minIndex станет равно maxIndex // После чего состоится последний заход в whilе и maxIndex станет равен minIndex - 1 // Таким образом, значение grayscaleColor будет находится между Palette[minIndex - 1] и Palette[minIndex] { 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::setHeaderAndAllocateMemory(int Width, int Height, int BitCount) { // Заполняем необходимые поля информации об изображении BMInfoHeader.Width = Width; // Ширина точечного рисунка в пикселях BMInfoHeader.Height = Height; // Высота точечного рисунка в пикселях BMInfoHeader.Planes = 1; // Число плоскостей целевого устройства = 1 BMInfoHeader.BitCount = BitCount; // Глубина цвета, число бит на точку = 1,4,8,24 (вариант 6) 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]; 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(вариант 6) if (isAllowedBitCount(BCount)) { setHeaderAndAllocateMemory(Width, Height, BCount); // заполняем заголовок изображения и выделяем память if (Palette) { Mode = Palette[getNearestPaletteColorIndex(Mode)].Red; // получаем ближайший к переданному цвет в палитре, если она используется } // Заполнение данных изображения переданным значением for (int i=0;i<BMInfoHeader.Height;i++) { for (int j=0;j<BMInfoHeader.Width;j++) { Rgbtriple[i][j].Red = Mode; Rgbtriple[i][j].Green = Mode; Rgbtriple[i][j].Blue = Mode; } } } // Задали неподдерживаемый параметр битности else { // Выводим сообщение об ошибке printf("Ошибка: поддерживается создание изображения только 24 бит.\n"); } } // Выставление параметров, соответствующих пустому изображению 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::loadImageDataFromFile(FILE* f) { BITMAPFILEHEADER BMFileHeader; // считываем заголовок изображения if (fread(&BMFileHeader,sizeof(BITMAPFILEHEADER),1,f) != 1) { printf("Ошибка: не удалось считать заголовок файла изображения\n"); return 0; } // проверяем сигнатуру if (BMFileHeader.Type != 0x4D42) { printf("Ошибка: неизвестный формат файла\n"); return 0; } // загружаем заголовок изображения BITMAPINFOHEADER FileBMInfoHeader; if (fread(&FileBMInfoHeader,sizeof(BITMAPINFOHEADER),1,f) != 1) { printf("Ошибка: не удалось считать информацию об изображении\n"); return 0; } // проверяем, можем ли загружать данный формат if (!isAllowedBitCount(FileBMInfoHeader.BitCount) && FileBMInfoHeader.BitCount != 32) { printf("Ошибка: неподдерживаемая битность изображения: '%i'\n", (int)FileBMInfoHeader.BitCount); return 0; } // заполняем заголовок и выделяем память для картинки, описанной в файле setHeaderAndAllocateMemory(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 = getAdditionalRowOffsetInFile(FileBMInfoHeader.Width, FileBMInfoHeader.BitCount); if (FileBMInfoHeader.BitCount == 24) { for (int i = BMInfoHeader.Height - 1; i >= 0; i--) { // считываем сразу всю строку изображенмя, так как она совпадает с внутренним форматом хранения в памяти 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][j].Red = fileQuad.Red; Rgbtriple[i][j].Green = fileQuad.Green; Rgbtriple[i][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][j].Red = grayscale; Rgbtriple[i][j].Green = grayscale; Rgbtriple[i][j].Blue = grayscale; } // пропускаем смещение между строками изображения в файле if (additionalRowOffset) { fseek(f, additionalRowOffset, SEEK_CUR); } } } // удаляем файловую палитру, если она была создана if (filePalette) { delete[] filePalette; } return 1; } // Реализация метода загрузки изображения int Image::loadimage(char *fileName) { if (Rgbtriple) { printf("Ошибка: нельзя загружать данные в уже созданное изображение\n"); return 0; } FILE *f; // открываем требуемый файл f = fopen(fileName,"rb"); // необходимо открывать бинарный файл if (!f) { printf("Ошибка: не удалось прочитать файл %s\n", fileName); return 0; } // производим загрузку данных int resultCode = loadImageDataFromFile(f); // закрываем файл fclose(f); return resultCode; } void Image::saveImageDataToFile(FILE* f) // сохраняет данные в переданный файл { // получаем смещение между строками изображения в файле const int additionalRowOffset = getAdditionalRowOffsetInFile(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; // и к смещению до данных изображения } // записываем заголовок файла if (fwrite(&BMFileHeader,sizeof(BITMAPFILEHEADER),1,f) != 1) { printf("Ошибка: не удалось записать заголовок файла изображения\n"); return; } // записываем заголовок изображения if (fwrite(&BMInfoHeader,sizeof(BITMAPINFOHEADER),1,f) != 1) { printf("Ошибка: не удалось записать информацию об изображении\n"); return; } if (BMInfoHeader.BitCount == 24) { for (int i = BMInfoHeader.Height - 1; i >= 0; i--) { // для формата 24 бит просто копируем в файл строку изображения, так как он совпадает с внутренним форматом хранения fwrite(Rgbtriple[i], sizeof(RGBTRIPLE), BMInfoHeader.Width, f); // записываем нужное количество 0 для смещения между строками изображения if (additionalRowOffset) { const int Zero = 0; fwrite(&Zero, 1, additionalRowOffset, f); } } } else // изображение с палитрой { // сначала записываем палитру if (fwrite(Palette, sizeof(RGBQUAD), BMInfoHeader.ColorUsed, f) != BMInfoHeader.ColorUsed) { printf("Ошибка: не удалось записать палитру\n"); return; } // вычисляем смещение необходимое для перемещения индекса цвета первого пикселя в старшие биты 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][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::initializeFromImage(const Image &Inp) // делает копию изображения (заполняет заголовок и копирует данные) { setEmptyImageParams(); // Заполняем параметры пустого изображения setHeaderAndAllocateMemory(Inp.BMInfoHeader.Width, Inp.BMInfoHeader.Height, Inp.BMInfoHeader.BitCount); // заполняем заголовок и выделяем нужную память copyDataFromImage(Inp); // копируем данные изображения } void Image::copyDataFromImage(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); for (int i = 0; i < BMInfoHeader.Height; ++i) { memcpy(Rgbtriple[i], Inp.Rgbtriple[i], rowSize); } // Замечание: палитру не копируем, так как она создается автоматически при инициализации заголовка изображения // и одинакова для выбранной битности } else { printf("Ошибка: в изображение уже проинициализированно другим разрешением и/или битностью\n"); } } Image::Image (const Image &im) { initializeFromImage(im); // делаем копию изображения } void Image::writeimage(char *fileName) // выполняет запись в файл { if (!Rgbtriple) { printf("Ошибка: в изображении нет данных для сохранения\n"); return; } FILE *f; // открываем файл на запись f = fopen(fileName,"w+b"); // необходимо открывать бинарный файл if (!f) { printf("Ошибка: не удалось создать файл %s\n", fileName); return; } saveImageDataToFile(f); // сохраняет данные в файл fclose(f); // закрывает файл } Image& Image::operator = (const Image& Inp) // Перегрузка оператора = { if (Rgbtriple) // если изображение уже создано { copyDataFromImage(Inp); // то пробуем только скопировать данные (при совпадении форматов) } else { initializeFromImage(Inp); // иначе создаем копию } return *this; } void Image::copyAndConvertDataFromImage(const Image &Inp) // функция преобразования данных переданного изображения в текущий формат { // Проверяем совпадение разрешения и битности, после чего скопируем данные if (BMInfoHeader.Width != Inp.BMInfoHeader.Width && BMInfoHeader.Height != Inp.BMInfoHeader.Height) { printf("Ошибка: не совпадают разрешения при конвертировании битности изображений\n"); return; } // проверяем, что произоводится уменьшение битности if (Inp.BMInfoHeader.BitCount < BMInfoHeader.BitCount) { printf("Ошибка: возможно только уменьшение битности изображений\n"); return; } 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][j].Red // для исходного изображения с палитрой берем оттенок серого сразу из данных картинки : getGrayscaleColor(Inp.Rgbtriple[i][j]); // иначе вычисляем его grayscale = Palette[getNearestPaletteColorIndex(grayscale)].Red; // получаем значение серого в палитре итогового изображения // записываем оттенок серого в пиксель итогового изображения Rgbtriple[i][j].Red = grayscale; Rgbtriple[i][j].Green = grayscale; Rgbtriple[i][j].Blue = grayscale; } } } Image Image::operator / (short Depth) // Перегрузка оператора /, возвращает новое изображение, являющееся копией данного, но с битностью Depth { // проверяем, что параметр допустим if (!isAllowedBitCount(Depth)) { printf("Ошибка: неподдерживаемая битность\n"); return Image(*this); } if (Depth > BMInfoHeader.BitCount) { printf("Ошибка: возможно только уменьшение битности\n"); return Image(*this); } Image result(0, Depth, BMInfoHeader.Width, BMInfoHeader.Height); // создаем пустое изображение result.copyAndConvertDataFromImage(*this); // выполняем преобразование текущего изображения в итоговое return result; } Image& Image::operator /= (const Image& Inp) // Перегрузка оператора /=, выполняет запись переданного изображения в текущее с изменением размера { // проверка совпадения форматов if (BMInfoHeader.BitCount != Inp.BMInfoHeader.BitCount) { printf("Ошибка: разная битность изображений, получение изображения с новым размером невозможно\n"); return *this; } 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][j] = Inp.Rgbtriple[sourceY][sourceX]; // запись значения цвета из исходного изображения в текущее } } return *this; } |