Вернуться к разделу "Реализация проекта BookScanLib".
Алгоритм Bicubic Rotate (поворот с бикубическим сглаживанием) применяется для поворота растровой картинки на произвольный угол.
В библиотеке FreeImage есть аналогичный алгоритм FreeImage_RotateEx (поворот со сглаживанием B-сплайном) - но он не меняет размер изображения при повороте (обрезая результат по размерам исходного изображения), а также не закрашивает пустые треугольники, образующиеся при повороте.
Я написал простейшую консольную программу для демонстрации работы Bicubic Rotate. На входе она принимает следующие параметры:
rot_bic <input_file> <angle (double degrees)>
Angle - это угол поворота в градусах. Точность - до одной десятой.
На выходе программа выдаёт этот же файл, обработанный этим алгоритмом.
Программа работает только с серыми изображениями и цветными изображениями.
Всё необходимое для тестирования этой программы (компиляционный проект, готовый экзешник, файл-пример и bat-файлы для тестирования программы) я оформил в небольшой пакет:
Скачать пакет rot_bic (51 КБ)
(Для работы программы требуется FreeImage dll-библиотека из пакета FreeImage DLL v3.9.2 - см. статью 1. Знакомство с FreeImage).
Рассмотрим исходные коды этой программы:
// AForge Image Processing Library // // Copyright © Andrew Kirillov, 2005-2007 // andrew.kirillov@gmail.com // /// Rotate image using bicubic interpolation // // This algorithm was taken from the AForge.NET sourcecodes // and adopted for the FreeImage library // // Copyright (C) 2007-2008: // monday2000 monday2000@yandex.ru #include "FreeImage.h" #include "Utilities.h" #define PI ((double)3.14159265358979323846264338327950288419716939937510) //////////////////////////////////////////////////////////////////////////////// double BiCubicKernel(double x) { if ( x > 2.0 ) return 0.0; double a, b, c, d; double xm1 = x - 1.0; double xp1 = x + 1.0; double xp2 = x + 2.0; a = ( xp2 <= 0.0 ) ? 0.0 : xp2 * xp2 * xp2; b = ( xp1 <= 0.0 ) ? 0.0 : xp1 * xp1 * xp1; c = ( x <= 0.0 ) ? 0.0 : x * x * x; d = ( xm1 <= 0.0 ) ? 0.0 : xm1 * xm1 * xm1; return ( 0.16666666666666666667 * ( a - ( 4.0 * b ) + ( 6.0 * c ) - ( 4.0 * d ) ) ); } //////////////////////////////////////////////////////////////////////////////// // Calculates rotated image size. // param - Source image data. // // returns New image size. // void CalculateRotatedSize(FIBITMAP* dib, double angle, unsigned& new_width, unsigned& new_height) { // angle's sine and cosine double angleRad = -angle * PI / 180; double angleCos = cos(angleRad); double angleSin = sin(angleRad); unsigned width = FreeImage_GetWidth(dib); unsigned height = FreeImage_GetHeight(dib); // calculate half size double halfWidth = (double) width / 2; double halfHeight = (double) height / 2; // rotate corners double cx1 = halfWidth * angleCos; double cy1 = halfWidth * angleSin; double cx2 = halfWidth * angleCos - halfHeight * angleSin; double cy2 = halfWidth * angleSin + halfHeight * angleCos; double cx3 = -halfHeight * angleSin; double cy3 = halfHeight * angleCos; double cx4 = 0; double cy4 = 0; // recalculate image size halfWidth = MAX( MAX( cx1, cx2 ), MAX( cx3, cx4 ) ) - MIN( MIN( cx1, cx2 ), MIN( cx3, cx4 ) ); halfHeight = MAX( MAX( cy1, cy2 ), MAX( cy3, cy4 ) ) - MIN( MIN( cy1, cy2 ), MIN( cy3, cy4 ) ); new_width = (int) ( halfWidth * 2 + 0.5 ); new_height = (int) ( halfHeight * 2 + 0.5 ); } //////////////////////////////////////////////////////////////////////////////// FIBITMAP* ProcessFilter(FIBITMAP* src_dib, double angle) { // 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; unsigned new_width, new_height; CalculateRotatedSize(src_dib, angle, new_width, new_height); 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* lines, *lined, *linek; unsigned d; // get source image size double halfWidth = (double) width / 2; double halfHeight = (double) height / 2; // get destination image size double halfNewWidth = (double) new_width / 2; double halfNewHeight = (double) new_height / 2; // angle's sine and cosine double angleRad = -angle * PI / 180; double angleCos = cos( angleRad ); double angleSin = sin( angleRad ); // fill values BYTE fill = 0xFF; // white // destination pixel's coordinate relative to image center double cx, cy; // coordinates of source points and cooefficients double ox, oy, dx, dy, k1, k2; int ox1, oy1, ox2, oy2; // destination pixel values double* p_g = new double[btpp]; // width and height decreased by 1 int ymax = height - 1; int xmax = width - 1; cy = -halfNewHeight; for ( int y = 0; y < new_height; y++ ) { cx = -halfNewWidth; lines = dst_bits + y * src_pitch; lined = dst_bits + y * dst_pitch; for ( int x = 0; x < new_width; x++) { // coordinates of source point ox = angleCos * cx + angleSin * cy + halfWidth; oy = -angleSin * cx + angleCos * cy + halfHeight; ox1 = (int) ox; oy1 = (int) oy; // validate source pixel's coordinates if ( ( ox1 < 0 ) || ( oy1 < 0 ) || ( ox1 >= width ) || ( oy1 >= height ) ) { // fill destination image with filler for (d=0; d<btpp; d++) lined[x * btpp + d] = fill; } else { dx = ox - (double) ox1; dy = oy - (double) oy1; // initial pixel value memset(p_g, 0, sizeof(double)*btpp); for ( int n = -1; n < 3; n++ ) { // get Y cooefficient k1 = BiCubicKernel( dy - (double) n ); oy2 = oy1 + n; if ( oy2 < 0 ) oy2 = 0; if ( oy2 > ymax ) oy2 = ymax; linek = src_bits + oy2 * src_pitch; for ( int m = -1; m < 3; m++ ) { // get X cooefficient k2 = k1 * BiCubicKernel( (double) m - dx ); ox2 = ox1 + m; if ( ox2 < 0 ) ox2 = 0; if ( ox2 > xmax ) ox2 = xmax; for (d=0; d<btpp; d++) p_g[d] += k2 * linek[ox2 * btpp + d]; } } for (d=0; d<btpp; d++) lined[x * btpp + d] = (BYTE) p_g[d]; } cx++; } cy++; } // Copying the DPI... FreeImage_SetDotsPerMeterX(dst_dib, FreeImage_GetDotsPerMeterX(src_dib)); FreeImage_SetDotsPerMeterY(dst_dib, FreeImage_GetDotsPerMeterY(src_dib)); delete [] p_g; 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 : rot_bic <input_file> <angle (double degrees)>\n"); return 0; } FIBITMAP *dib = GenericLoader(argv[1], 0); double angle = atof(argv[2]); 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, angle); 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.
Вычисляем размеры повёрнутой картинки. Находим центры вращения исходной и повёрнутой картинок. Задаём 2 (по длине и по ширине) тригонометрические формулы обратного поворота координат. Находим 4 габарита повёрнутого изображения.
Обычная квадратная апертура 3х3 проходит в цикле по всем пикселям будующего повёрнутого изображения. При этом на каждом шаге обращаемся через коэффициенты поворота к нужному пикселю исходной картинки. Если исходная точка находится в пределах габаритов - умножаем 9 величин округления окрестности текущего исходного пикселя на бикубический коэффициент, всё это суммируем и вставляем в пиксель назначения. Если не в пределах габаритов - вставляем в пиксель назначения заранее заданный цвет фоновой заливки (белый).
Слаживание осуществляется за счёт применения бикубического коэффициента.