diff options
Diffstat (limited to 'plugins/FreeImage/Source/FreeImageToolkit/Resize.cpp')
| -rw-r--r-- | plugins/FreeImage/Source/FreeImageToolkit/Resize.cpp | 656 | 
1 files changed, 0 insertions, 656 deletions
diff --git a/plugins/FreeImage/Source/FreeImageToolkit/Resize.cpp b/plugins/FreeImage/Source/FreeImageToolkit/Resize.cpp deleted file mode 100644 index 5421bd47ab..0000000000 --- a/plugins/FreeImage/Source/FreeImageToolkit/Resize.cpp +++ /dev/null @@ -1,656 +0,0 @@ -// ========================================================== -// Upsampling / downsampling classes -// -// Design and implementation by -// - Hervé Drolon (drolon@infonie.fr) -// - Detlev Vendt (detlev.vendt@brillit.de) -// -// This file is part of FreeImage 3 -// -// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY -// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES -// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE -// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED -// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT -// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY -// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL -// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER -// THIS DISCLAIMER. -// -// Use at your own risk! -// ========================================================== - -#include "Resize.h" - -/** -  Filter weights table. -  This class stores contribution information for an entire line (row or column). -*/ -CWeightsTable::CWeightsTable(CGenericFilter *pFilter, unsigned uDstSize, unsigned uSrcSize) { -	unsigned u; -	double dWidth; -	double dFScale = 1.0; -	const double dFilterWidth = pFilter->GetWidth(); - -	// scale factor -	const double dScale = double(uDstSize) / double(uSrcSize); - -	if(dScale < 1.0) { -		// minification -		dWidth = dFilterWidth / dScale;  -		dFScale = dScale;  -	} else { -		// magnification -		dWidth= dFilterWidth;  -	} - -	// allocate a new line contributions structure -	// -	// window size is the number of sampled pixels -	m_WindowSize = 2 * (int)ceil(dWidth) + 1;  -	m_LineLength = uDstSize;  -	 // allocate list of contributions  -	m_WeightTable = (Contribution*)malloc(m_LineLength * sizeof(Contribution)); -	for(u = 0 ; u < m_LineLength ; u++) { -		// allocate contributions for every pixel -		m_WeightTable[u].Weights = (double*)malloc(m_WindowSize * sizeof(double)); -	} - -	// offset for discrete to continuous coordinate conversion -	const double dOffset = (0.5 / dScale) - 0.5; - - -	for(u = 0; u < m_LineLength; u++) { -		// scan through line of contributions -		const double dCenter = (double)u / dScale + dOffset;   // reverse mapping -		// find the significant edge points that affect the pixel -		int iLeft = MAX (0, (int)floor (dCenter - dWidth));  -		int iRight = MIN ((int)ceil (dCenter + dWidth), int(uSrcSize) - 1);  - -		// cut edge points to fit in filter window in case of spill-off -		if ((iRight - iLeft + 1) > int(m_WindowSize)) { -			if(iLeft < (int(uSrcSize) - 1 / 2)) { -				iLeft++;  -			} else { -				iRight--;  -			} -		} - -		m_WeightTable[u].Left = iLeft;  -		m_WeightTable[u].Right = iRight; - -		int iSrc = 0; -		double dTotalWeight = 0;  // zero sum of weights -		for(iSrc = iLeft; iSrc <= iRight; iSrc++) { -			// calculate weights -			const double weight = dFScale * pFilter->Filter(dFScale * (dCenter - (double)iSrc)); -			m_WeightTable[u].Weights[iSrc-iLeft] = weight; -			dTotalWeight += weight; -		} -		if ((dTotalWeight > 0) && (dTotalWeight != 1)) { -			// normalize weight of neighbouring points -			for(iSrc = iLeft; iSrc <= iRight; iSrc++) { -				// normalize point -				m_WeightTable[u].Weights[iSrc-iLeft] /= dTotalWeight;  -			} -			// simplify the filter, discarding null weights at the right -			iSrc = iRight - iLeft; -			while(m_WeightTable[u].Weights[iSrc] == 0){ -				m_WeightTable[u].Right--; -				iSrc--; -				if(m_WeightTable[u].Right == m_WeightTable[u].Left) -					break; -			} - -		} -   }  -} - -CWeightsTable::~CWeightsTable() { -	for(unsigned u = 0; u < m_LineLength; u++) { -		// free contributions for every pixel -		free(m_WeightTable[u].Weights); -	} -	// free list of pixels contributions -	free(m_WeightTable); -} - -// --------------------------------------------- - -/** - CResizeEngine<br> - This class performs filtered zoom. It scales an image to the desired dimensions with  - any of the CGenericFilter derived filter class.<br> - It works with 8-, 24- and 32-bit buffers.<br><br> - - <b>References</b> : <br> - [1] Paul Heckbert, C code to zoom raster images up or down, with nice filtering.  - UC Berkeley, August 1989. [online] http://www-2.cs.cmu.edu/afs/cs.cmu.edu/Web/People/ph/heckbert.html - [2] Eran Yariv, Two Pass Scaling using Filters. The Code Project, December 1999.  - [online] http://www.codeproject.com/bitmap/2_pass_scaling.asp - -*/ - -FIBITMAP* CResizeEngine::scale(FIBITMAP *src, unsigned dst_width, unsigned dst_height) {  -	unsigned src_width  = FreeImage_GetWidth(src);  -	unsigned src_height = FreeImage_GetHeight(src); - -	unsigned redMask	= FreeImage_GetRedMask(src); -	unsigned greenMask	= FreeImage_GetGreenMask(src); -	unsigned blueMask	= FreeImage_GetBlueMask(src); - -	unsigned bpp = FreeImage_GetBPP(src); -	if(bpp == 1) { -		// convert output to 8-bit -		bpp = 8; -	} - -	FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src); - -	// allocate the dst image -	FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, bpp, redMask, greenMask, blueMask); -	if (!dst) return NULL; -	 -	if(bpp == 8) { -		if(FreeImage_GetColorType(src) == FIC_MINISWHITE) { -			// build an inverted greyscale palette -			RGBQUAD *dst_pal = FreeImage_GetPalette(dst); -			for(unsigned 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); -			for(unsigned i = 0; i < 256; i++) { -				dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)i; -			} -		} -	} - -	/** -	Decide which filtering order (xy or yx) is faster for this mapping.  -	--- The theory --- -	Try to minimize calculations by counting the number of convolution multiplies -	if(dst_width*src_height <= src_width*dst_height) { -		// xy filtering -	} else { -		// yx filtering -	} -	--- The practice --- -	Try to minimize calculations by counting the number of vertical convolutions (the most time consuming task) -	if(dst_width*dst_height <= src_width*dst_height) { -		// xy filtering -	} else { -		// yx filtering -	} -	*/ - -	if(dst_width <= src_width) { -		// xy filtering -		// ------------- - -		// allocate a temporary image -		FIBITMAP *tmp = FreeImage_AllocateT(image_type, dst_width, src_height, bpp, redMask, greenMask, blueMask); -		if (!tmp) { -			FreeImage_Unload(dst); -			return NULL; -		} - -		// scale source image horizontally into temporary image -		horizontalFilter(src, src_width, src_height, tmp, dst_width, src_height); - -		// scale temporary image vertically into result image     -		verticalFilter(tmp, dst_width, src_height, dst, dst_width, dst_height); - -		// free temporary image -		FreeImage_Unload(tmp); - -	} else { -		// yx filtering -		// ------------- - -		// allocate a temporary image -		FIBITMAP *tmp = FreeImage_AllocateT(image_type, src_width, dst_height, bpp, redMask, greenMask, blueMask); -		if (!tmp) { -			FreeImage_Unload(dst); -			return NULL; -		} - -		// scale source image vertically into temporary image -		verticalFilter(src, src_width, src_height, tmp, src_width, dst_height); - -		// scale temporary image horizontally into result image     -		horizontalFilter(tmp, src_width, dst_height, dst, dst_width, dst_height); - -		// free temporary image -		FreeImage_Unload(tmp); -	} - -	return dst; -}  - - -/// Performs horizontal image filtering -void CResizeEngine::horizontalFilter(FIBITMAP *src, unsigned src_width, unsigned src_height, FIBITMAP *dst, unsigned dst_width, unsigned dst_height) {  -	if(dst_width == src_width) { -		// no scaling required, just copy -		switch(FreeImage_GetBPP(src)) { -			case 1: -			{ -				if(FreeImage_GetBPP(dst) != 8) break; -				for(unsigned y = 0; y < dst_height; y++) { -					// convert each row  -					BYTE *src_bits = FreeImage_GetScanLine(src, y); -					BYTE *dst_bits = FreeImage_GetScanLine(dst, y); -					FreeImage_ConvertLine1To8(dst_bits, src_bits, dst_width); -				} -			} -			break; - -			default: -			{ -				const BYTE *src_bits = FreeImage_GetBits(src); -				BYTE *dst_bits = FreeImage_GetBits(dst); -				memcpy(dst_bits, src_bits, dst_height * FreeImage_GetPitch(dst)); -			} -			break; -		} -	} -	else { - -		// allocate and calculate the contributions -		CWeightsTable weightsTable(m_pFilter, dst_width, src_width);  -		 -		// step through rows -		switch(FreeImage_GetImageType(src)) { -			case FIT_BITMAP: -			{ -				switch(FreeImage_GetBPP(src)) { -					case 1: -					{ -						// scale and convert to 8-bit -						if(FreeImage_GetBPP(dst) != 8) break; - -						for(unsigned y = 0; y < dst_height; y++) { -							// scale each row  -							const BYTE *src_bits = FreeImage_GetScanLine(src, y); -							BYTE *dst_bits = FreeImage_GetScanLine(dst, y); - -							for(unsigned x = 0; x < dst_width; x++) { -								// loop through row -								double value = 0; -								const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary -								const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary - -								for(unsigned i = iLeft; i <= iRight; i++) { -									// scan between boundaries -									// accumulate weighted effect of each neighboring pixel -									const double weight = weightsTable.getWeight(x, i-iLeft); -									 -									const BYTE pixel = (src_bits[i >> 3] & (0x80 >> (i & 0x07))) != 0; -									value += (weight * (double)pixel); -								}  -								value *= 255; - -								// clamp and place result in destination pixel -								dst_bits[x] = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 255); -							}  -						} -					} -					break; - -					case 8: -					case 24: -					case 32: -					{ -						// Calculate the number of bytes per pixel (1 for 8-bit, 3 for 24-bit or 4 for 32-bit) -						const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); - -						for(unsigned y = 0; y < dst_height; y++) { -							// scale each row  -							const BYTE *src_bits = FreeImage_GetScanLine(src, y); -							BYTE *dst_bits = FreeImage_GetScanLine(dst, y); - -							for(unsigned x = 0; x < dst_width; x++) { -								// loop through row -								double value[4] = {0, 0, 0, 0};					// 4 = 32 bpp max -								const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary -								const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary - -								for(unsigned i = iLeft; i <= iRight; i++) { -									// scan between boundaries -									// accumulate weighted effect of each neighboring pixel -									const double weight = weightsTable.getWeight(x, i-iLeft); -									 -									unsigned index = i * bytespp;	// pixel index -									for (unsigned j = 0; j < bytespp; j++) { -										value[j] += (weight * (double)src_bits[index++]);  -									} -								}  - -								// clamp and place result in destination pixel -								for (unsigned j = 0; j < bytespp; j++) { -									dst_bits[j] = (BYTE)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFF); -								} - -								dst_bits += bytespp; -							}  -						} -					} -					break; -				} -			} -			break; - -			case FIT_UINT16: -			case FIT_RGB16: -			case FIT_RGBA16: -			{ -				// Calculate the number of words per pixel (1 for 16-bit, 3 for 48-bit or 4 for 64-bit) -				const unsigned wordspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(WORD); - -				for(unsigned y = 0; y < dst_height; y++) { -					// scale each row  -					const WORD *src_bits = (WORD*)FreeImage_GetScanLine(src, y); -					WORD *dst_bits = (WORD*)FreeImage_GetScanLine(dst, y); - -					for(unsigned x = 0; x < dst_width; x++) { -						// loop through row -						double value[4] = {0, 0, 0, 0};					// 4 = 64 bpp max -						const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary -						const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary - -						for(unsigned i = iLeft; i <= iRight; i++) { -							// scan between boundaries -							// accumulate weighted effect of each neighboring pixel -							const double weight = weightsTable.getWeight(x, i-iLeft); -							 -							unsigned index = i * wordspp;	// pixel index -							for (unsigned j = 0; j < wordspp; j++) { -								value[j] += (weight * (double)src_bits[index++]);  -							} -						}  - -						// clamp and place result in destination pixel -						for (unsigned j = 0; j < wordspp; j++) { -							dst_bits[j] = (WORD)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFFFF); -						} - -						dst_bits += wordspp; -					}  -				} -			} -			break; - -			case FIT_FLOAT: -			case FIT_RGBF: -			case FIT_RGBAF: -			{ -				// Calculate the number of floats per pixel (1 for 32-bit, 3 for 96-bit or 4 for 128-bit) -				const unsigned floatspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(float); - -				for(unsigned y = 0; y < dst_height; y++) { -					// scale each row  -					const float *src_bits = (float*)FreeImage_GetScanLine(src, y); -					float *dst_bits = (float*)FreeImage_GetScanLine(dst, y); - -					for(unsigned x = 0; x < dst_width; x++) { -						// loop through row -						double value[4] = {0, 0, 0, 0};					// 4 = 128 bpp max -						const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary -						const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary - -						for(unsigned i = iLeft; i <= iRight; i++) { -							// scan between boundaries -							// accumulate weighted effect of each neighboring pixel -							const double weight = weightsTable.getWeight(x, i-iLeft); -							 -							unsigned index = i * floatspp;	// pixel index -							for (unsigned j = 0; j < floatspp; j++) { -								value[j] += (weight * (double)src_bits[index++]);  -							} -						}  - -						// place result in destination pixel -						for (unsigned j = 0; j < floatspp; j++) { -							dst_bits[j] = (float)value[j]; -						} - -						dst_bits += floatspp; -					}  -				} -			} -			break; - -		} -	} -}  - -/// Performs vertical image filtering -void CResizeEngine::verticalFilter(FIBITMAP *src, unsigned src_width, unsigned src_height, FIBITMAP *dst, unsigned dst_width, unsigned dst_height) {  -	if(src_height == dst_height) { -		// no scaling required, just copy -		switch(FreeImage_GetBPP(src)) { -			case 1: -			{ -				if(FreeImage_GetBPP(dst) != 8) break; -				for(unsigned y = 0; y < dst_height; y++) { -					// convert each row  -					BYTE *src_bits = FreeImage_GetScanLine(src, y); -					BYTE *dst_bits = FreeImage_GetScanLine(dst, y); -					FreeImage_ConvertLine1To8(dst_bits, src_bits, dst_width); -				} -			} -			break; - -			default: -			{ -				const BYTE *src_bits = FreeImage_GetBits(src); -				BYTE *dst_bits = FreeImage_GetBits(dst); -				memcpy(dst_bits, src_bits, dst_height * FreeImage_GetPitch(dst)); -			} -			break; -		} - -	} -	else { - -		// allocate and calculate the contributions -		CWeightsTable weightsTable(m_pFilter, dst_height, src_height);  - -		// step through columns -		switch(FreeImage_GetImageType(src)) { -			case FIT_BITMAP: -			{ -				switch(FreeImage_GetBPP(src)) { -					case 1: -					{ -						// scale and convert to 8-bit -						if(FreeImage_GetBPP(dst) != 8) break; - -						const unsigned src_pitch = FreeImage_GetPitch(src); -						const unsigned dst_pitch = FreeImage_GetPitch(dst); - -						for(unsigned x = 0; x < dst_width; x++) { - -							// work on column x in dst -							BYTE *dst_bits = FreeImage_GetBits(dst) + x; - -							// scale each column -							for(unsigned y = 0; y < dst_height; y++) { -								// loop through column -								double value = 0; -								const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary -								const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary - -								BYTE *src_bits = FreeImage_GetScanLine(src, iLeft); - -								for(unsigned i = iLeft; i <= iRight; i++) { -									// scan between boundaries -									// accumulate weighted effect of each neighboring pixel -									const double weight = weightsTable.getWeight(y, i-iLeft); -									 -									const BYTE pixel = (src_bits[x >> 3] & (0x80 >> (x & 0x07))) != 0; -									value += (weight * (double)pixel); - -									src_bits += src_pitch; -								} -								value *= 255; - -								// clamp and place result in destination pixel -								*dst_bits = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF); - -								dst_bits += dst_pitch; -							} -						} -					} -					break; - -					case 8: -					case 24: -					case 32: -					{ -						// Calculate the number of bytes per pixel (1 for 8-bit, 3 for 24-bit or 4 for 32-bit) -						const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src); - -						const unsigned src_pitch = FreeImage_GetPitch(src); -						const unsigned dst_pitch = FreeImage_GetPitch(dst); - -						for(unsigned x = 0; x < dst_width; x++) { -							const unsigned index = x * bytespp;	// pixel index - -							// work on column x in dst -							BYTE *dst_bits = FreeImage_GetBits(dst) + index; - -							// scale each column -							for(unsigned y = 0; y < dst_height; y++) { -								// loop through column -								double value[4] = {0, 0, 0, 0};					// 4 = 32 bpp max -								const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary -								const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary - -								const BYTE *src_bits = FreeImage_GetScanLine(src, iLeft) + index; - -								for(unsigned i = iLeft; i <= iRight; i++) { -									// scan between boundaries -									// accumulate weighted effect of each neighboring pixel -									const double weight = weightsTable.getWeight(y, i-iLeft); -									for (unsigned j = 0; j < bytespp; j++) { -										value[j] += (weight * (double)src_bits[j]); -									} - -									src_bits += src_pitch; -								} - -								// clamp and place result in destination pixel -								for (unsigned j = 0; j < bytespp; j++) { -									dst_bits[j] = (BYTE)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFF); -								} - -								dst_bits += dst_pitch; -							} -						} -					} -					break; -				} -			} -			break; - -			case FIT_UINT16: -			case FIT_RGB16: -			case FIT_RGBA16: -			{ -				// Calculate the number of words per pixel (1 for 16-bit, 3 for 48-bit or 4 for 64-bit) -				const unsigned wordspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(WORD); - -				const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD); -				const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(WORD); - -				for(unsigned x = 0; x < dst_width; x++) { -					const unsigned index = x * wordspp;	// pixel index - -					// work on column x in dst -					WORD *dst_bits = (WORD*)FreeImage_GetBits(dst) + index; - -					// scale each column -					for(unsigned y = 0; y < dst_height; y++) { -						// loop through column -						double value[4] = {0, 0, 0, 0};					// 4 = 64 bpp max -						const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary -						const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary - -						const WORD *src_bits = (WORD*)FreeImage_GetScanLine(src, iLeft) + index; - -						for(unsigned i = iLeft; i <= iRight; i++) { -							// scan between boundaries -							// accumulate weighted effect of each neighboring pixel -							const double weight = weightsTable.getWeight(y, i-iLeft); -							for (unsigned j = 0; j < wordspp; j++) { -								value[j] += (weight * (double)src_bits[j]); -							} - -							src_bits += src_pitch; -						} - -						// clamp and place result in destination pixel -						for (unsigned j = 0; j < wordspp; j++) { -							dst_bits[j] = (WORD)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFFFF); -						} - -						dst_bits += dst_pitch; -					} -				} -			} -			break; - -			case FIT_FLOAT: -			case FIT_RGBF: -			case FIT_RGBAF: -			{ -				// Calculate the number of floats per pixel (1 for 32-bit, 3 for 96-bit or 4 for 128-bit) -				const unsigned floatspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(float); - -				const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(float); -				const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(float); - -				for(unsigned x = 0; x < dst_width; x++) { -					const unsigned index = x * floatspp;	// pixel index - -					// work on column x in dst -					float *dst_bits = (float*)FreeImage_GetBits(dst) + index; - -					// scale each column -					for(unsigned y = 0; y < dst_height; y++) { -						// loop through column -						double value[4] = {0, 0, 0, 0};					// 4 = 128 bpp max -						const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary -						const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary - -						const float *src_bits = (float*)FreeImage_GetScanLine(src, iLeft) + index; - -						for(unsigned i = iLeft; i <= iRight; i++) { -							// scan between boundaries -							// accumulate weighted effect of each neighboring pixel -							const double weight = weightsTable.getWeight(y, i-iLeft); -							for (unsigned j = 0; j < floatspp; j++) { -								value[j] += (weight * (double)src_bits[j]); -							} - -							src_bits += src_pitch; -						} - -						// clamp and place result in destination pixel -						for (unsigned j = 0; j < floatspp; j++) { -							dst_bits[j] = (float)value[j]; -						} - -						dst_bits += dst_pitch; -					} -				} -			} -			break; - -		} -	} -}  -  | 
