Вернуться к разделу "Реализация проекта BookScanLib".


18. Простой Bilinear Resize

Алгоритм Простой 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 соседним текущему пикселям и по текущему (в источнике), вычисляем пропорциональный цвет и вставляем его в пиксель назначения. Слаживание осуществляется за счёт простейшей "школьной" линейной интерполяции. Билинейная она называется оттого, что делается по вертикали и по горизонтали.

Hosted by uCoz