Вернуться к разделу "Реализация проекта BookScanLib".
Алгоритм Простой Bilinear Resize (изменение размера с билинеарным сглаживанием) применяется для произвольного изменения размера растровой картинки.
Этот алгоритм является однопроходным. В библиотеке FreeImage есть алгоритм Bilinear Resize - но он двухпроходный.
Двухпроходный Bilinear Resize - это неплохой по качеству алгоритм. Но у него есть большой недостаток: он слишком медленный.
Именно поэтому данный однопроходный алгоритм представляет определённый интерес. Есть надежда, что он достаточно неплох по качеству. А по скорости - любой однопроходный алгоритм, разумеется, быстрее, чем соответствующий двухпроходный алгоритм.
Я написал простейшую консольную программу для демонстрации работы Простой Bilinear Resize. На входе она принимает следующие параметры:
res_bil <input_file> <ratio (double)>
Ratio - это коэффициент увеличения / уменьшения. Точность - до одной десятой.
На выходе программа выдаёт этот же файл, обработанный этим алгоритмом.
Программа работает только с серыми изображениями и цветными изображениями.
Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:
Скачать пакет res_bil (52 КБ)
(Для работы программы требуется FreeImage dll-библиотека из пакета FreeImage DLL v3.9.2 - см. статью 1. Знакомство с FreeImage).
Рассмотрим исходные коды этой программы:
// AForge Image Processing Library // // Copyright © Andrew Kirillov, 2005-2007 // andrew.kirillov@gmail.com // // This algorithm was taken from the AForge.NET sourcecodes // and adopted for the FreeImage library // // Copyright (C) 2007-2008: // monday2000 monday2000@yandex.ru // Resize image using bilinear interpolation #include "FreeImage.h" #include "Utilities.h" //////////////////////////////////////////////////////////////////////////////// FIBITMAP* ProcessFilter(FIBITMAP* src_dib, unsigned new_width, unsigned new_height) { // get source image size unsigned width = FreeImage_GetWidth(src_dib); unsigned height = FreeImage_GetHeight(src_dib); unsigned src_pitch = FreeImage_GetPitch(src_dib); unsigned bpp = FreeImage_GetBPP(src_dib); unsigned btpp = bpp/8; FIBITMAP* dst_dib = FreeImage_Allocate(new_width, new_height, bpp); if(bpp == 8) { if(FreeImage_GetColorType(src_dib) == FIC_MINISWHITE) { // build an inverted greyscale palette RGBQUAD *dst_pal = FreeImage_GetPalette(dst_dib); for(int i = 0; i < 256; i++) { dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)(255 - i); } } else { // build a greyscale palette RGBQUAD *dst_pal = FreeImage_GetPalette(dst_dib); for(int i = 0; i < 256; i++) { dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)i; } } } unsigned dst_pitch = FreeImage_GetPitch(dst_dib); BYTE* src_bits = (BYTE*)FreeImage_GetBits(src_dib); // The image raster BYTE* dst_bits = (BYTE*)FreeImage_GetBits(dst_dib); // The image raster BYTE* lined; unsigned d; double xFactor = (double) width / new_width; double yFactor = (double) height / new_height; // coordinates of source points double ox, oy, dx1, dy1, dx2, dy2; int ox1, oy1, ox2, oy2; // temporary pointers BYTE *tp1, *tp2; // width and height decreased by 1 int ymax = height - 1; int xmax = width - 1; // for each line for ( int y = 0; y < new_height; y++ ) { // Y coordinates oy = (double) y * yFactor; oy1 = (int) oy; oy2 = ( oy1 == ymax ) ? oy1 : oy1 + 1; dy1 = oy - (double) oy1; dy2 = 1.0 - dy1; // get temp pointers tp1 = src_bits + oy1 * src_pitch; tp2 = src_bits + oy2 * src_pitch; lined = dst_bits + y * dst_pitch; // for each pixel for ( int x = 0; x < new_width; x++ ) { // X coordinates ox = (double) x * xFactor; ox1 = (int) ox; ox2 = ( ox1 == xmax ) ? ox1 : ox1 + 1; dx1 = ox - (double) ox1; dx2 = 1.0 - dx1; // interpolate using 4 points for (d = 0; d<btpp; d++) lined[x * btpp + d] = (BYTE) ( dy2 * ( dx2 * tp1[ox1 * btpp + d] + dx1 * tp1[ox2 * btpp + d]) + dy1 * (dx2 * tp2[ox1 * btpp + d] + dx1 * tp2[ox2 * btpp + d])); } } // Copying the DPI... FreeImage_SetDotsPerMeterX(dst_dib, FreeImage_GetDotsPerMeterX(src_dib)); FreeImage_SetDotsPerMeterY(dst_dib, FreeImage_GetDotsPerMeterY(src_dib)); return dst_dib; } //////////////////////////////////////////////////////////////////////////////// /** FreeImage error handler @param fif Format / Plugin responsible for the error @param message Error message */ void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) { printf("\n*** "); printf("%s Format\n", FreeImage_GetFormatFromFIF(fif)); printf(message); printf(" ***\n"); } //////////////////////////////////////////////////////////////////////////////// /** Generic image loader @param lpszPathName Pointer to the full file name @param flag Optional load flag constant @return Returns the loaded dib if successful, returns NULL otherwise */ FIBITMAP* GenericLoader(const char* lpszPathName, int flag) { FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; // check the file signature and deduce its format // (the second argument is currently not used by FreeImage) fif = FreeImage_GetFileType(lpszPathName, 0); FIBITMAP* dib; if(fif == FIF_UNKNOWN) { // no signature ? // try to guess the file format from the file extension fif = FreeImage_GetFIFFromFilename(lpszPathName); } // check that the plugin has reading capabilities ... if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) { // ok, let's load the file dib = FreeImage_Load(fif, lpszPathName, flag); // unless a bad file format, we are done ! if (!dib) { printf("%s%s%s\n","File \"", lpszPathName, "\" not found."); return NULL; } } return dib; } //////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { // call this ONLY when linking with FreeImage as a static library #ifdef FREEIMAGE_LIB FreeImage_Initialise(); #endif // FREEIMAGE_LIB // initialize your own FreeImage error handler FreeImage_SetOutputMessage(FreeImageErrorHandler); if(argc != 3) { printf("Usage : res_bil <input_file> <ratio (double)>\n"); return 0; } FIBITMAP *dib = GenericLoader(argv[1], 0); double ratio = atof(argv[2]); unsigned new_width = (unsigned)FreeImage_GetWidth(dib)*ratio; unsigned new_height = (unsigned)FreeImage_GetHeight(dib)*ratio; if (dib) { // bitmap is successfully loaded! if (FreeImage_GetImageType(dib) == FIT_BITMAP) { if (FreeImage_GetBPP(dib) == 8 || FreeImage_GetBPP(dib) == 24) { FIBITMAP* dst_dib = ProcessFilter(dib, new_width, new_height); if (dst_dib) { // save the filtered bitmap const char *output_filename = "filtered.tif"; // first, check the output format from the file name or file extension FREE_IMAGE_FORMAT out_fif = FreeImage_GetFIFFromFilename(output_filename); if(out_fif != FIF_UNKNOWN) { // then save the file FreeImage_Save(out_fif, dst_dib, output_filename, 0); } // free the loaded FIBITMAP FreeImage_Unload(dst_dib); } } else printf("%s\n", "Unsupported color mode."); } else // non-FIT_BITMAP images are not supported. printf("%s\n", "Unsupported color mode."); FreeImage_Unload(dib); } // call this ONLY when linking with FreeImage as a static library #ifdef FREEIMAGE_LIB FreeImage_DeInitialise(); #endif // FREEIMAGE_LIB return 0; } |
Реализация данного алгоритма позаимствована из программной графической библиотеки AForge.NET.
Находим коэффициенты масштабирования по длине и ширине. При этом на каждом шаге обращаемся через коэффициенты масштабирования к нужному пикселю исходной картинки. Составляем пропорцию по 3 соседним текущему пикселям и по текущему (в источнике), вычисляем пропорциональный цвет и вставляем его в пиксель назначения. Слаживание осуществляется за счёт простейшей "школьной" линейной интерполяции. Билинейная она называется оттого, что делается по вертикали и по горизонтали.