Вернуться к разделу "Реализация проекта 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 величин округления окрестности текущего исходного пикселя на бикубический коэффициент, всё это суммируем и вставляем в пиксель назначения. Если не в пределах габаритов - вставляем в пиксель назначения заранее заданный цвет фоновой заливки (белый).
Слаживание осуществляется за счёт применения бикубического коэффициента.