Category: Code


If you are using a Mac OS, you may come across a situation where you are not able to see the collection in the Pivot viewer, while you will be able to see the same collection in Windows/Linux. And this can happen in the case of both when the cxml comes from a file or it is generated dynamically using the PivotServerTools JIT collection. There can be multiple reasons for this:

  1. The website that hosts the cxml doesn’t have anonymous access on. If this is case, just switch on the anonymous access and you should see the collection now.
  2. This case is usually found to be obstacle. It comes because of an incompatibility between Mac and PivotViewer’s DeepZoom. The DeepZoom bug is reproduced on all the Mac browsers if the “source” attribute for your items in the collection’s dzc file is empty. To see if you are the victim of this, do the following:

    1. Try to navigate to your .cxml from a browser on the mac. Make sure it comes back i.e. if the URL of your collection is http://www.abc.com/collection.cxml, visit this URL and see if the cxml is populated in your browser. In case of chrome, you’ll have to right click on the page and click on “View Page Source” to see if the collection is coming back.
    2. b. If the cxml is returned, find out the dzc from it. You can find it in the “ImgBase” attribute of the “Items” tag; it’ll look something like “collection-5f7c6a376516425598d3d822d2bd3cbf.dzc”.
    3. c. Then try to navigate to your .dzc file from the same browser, the URL will now be http://www.abc.com/collection-5f7c6a376516425598d3d822d2bd3cbf.dzc and see if the dzc is sent back. Again in chrome, check the Page’s source to see the result. The collection returned will now be something like this:

	<?xml version="1.0" encoding="utf-8" ?>
	<Collection MaxLevel="8" TileSize="256" Format="jpg" NextItemId="183" xmlns="http://schemas.microsoft.com/deepzoom/2008">
	  <Items>
		<I Id="0" N="0" IsPath="1" Source="items-5f7c6a376516425598d3d822d2bd3cbf/dzi/0.dzi">
		  <Size Width="256" Height="256" />
		</I>
		<I Id="1" N="1" IsPath="1" Source="items-5f7c6a376516425598d3d822d2bd3cbf/dzi/1.dzi">
		  <Size Width="256" Height="256" />
		</I>
		<I Id="2" N="2" IsPath="1" Source="items-5f7c6a376516425598d3d822d2bd3cbf/dzi/2.dzi">
		  <Size Width="256" Height="256" />
		</I>
		<I Id="3" N="3" IsPath="1" Source="items-5f7c6a376516425598d3d822d2bd3cbf/dzi/3.dzi">
		  <Size Width="256" Height="256" />
		</I>
		<I Id="4" N="4" IsPath="1" Source="items-5f7c6a376516425598d3d822d2bd3cbf/dzi/4.dzi">
		  <Size Width="256" Height="256" />
		</I>
		<I Id="5" N="5" IsPath="1" Source="items-5f7c6a376516425598d3d822d2bd3cbf/dzi/5.dzi">
		  <Size Width="256" Height="256" />
		</I>
	  </Items>
	</Collection>

This dzc file contains the information about the DeepZoom for the collection items. Here what you have to check is that for an item you have the value in the Source attribute. If there’s no value in the Source attribute i.e. Source=””, then we have a problem. And this is what brings us to the DeepZoom bug on the Mac OS. So if you can be put a value to this Source attribute even though a fake one (Yes, I said fake), you will start seeing the collection. It’s funny how an attribute whose value doesn’t matter can be so problematic (FYI, the source attribute is used for getting the DZI). If you are doing a JIT collection, the reason for this could be that the size of your images used for the collection items is 256 by 256. You can check this by debugging the “PivotServerTools” project, go to PivotServerTools à Internal à MakeItemContent


	string source = string.Empty;
	if ((null != item.ImageProvider) && (null != item.ImageProvider.DziPath))
	{
		source = item.ImageProvider.DziPath;
	}
	else if (IsAutoGenerateDzi)
	{
		//Only provide an auto-DZI if the image is larger than a collection tile
		if (null != item.ImageProvider)
		{
			Size size = item.ImageProvider.Size;
			if ((size.Width >= TileDimension) || (size.Height >= TileDimension))
			{
				source = DziSerializer.MakeDziPath(m_collection.CollectionKey, id);
			}
		}
	}

	yield return new XAttribute("Source", source);


Check here what the exact reason is and correct it else you can use a fake URL to get it working. Enjoy!

Advertisements

When you are using dynamic collections with PivotViewer i.e. JIT collections generated using the PivotServerTools, there comes a situation when the Pivot Viewer stops taking the fresh data from the server. This is because the Pivot Viewer caches the collection, so when you reload the collection again (in the same session), it doesn’t ask the JIT server for the cxml again and instead uses the cxml from the cache. This is good in terms of the performance however it’s bad if your data keeps on updating at the server and you are not able to reflect the latest data in your collection. What you should know is that the PivotViewer caches the collection based on the URL that you pass in the PivotViewer’s LoadCollection method, so if you call the method:

PivotViewer.LoadCollection(“url”, “viewerstate”);

again and again without changing the url parameter, only for the first call data will be fetched from your server, for all subsequent calls PivotViewer will take the collection from the cache. To avoid this, you have to keep changing the URL, whenever you call the above method. The best way to do it, is to add a query string parameter which you may/may not use and update it every time you load the collection. You can do something like below:

PivotViewer.LoadCollection(“url”+ “?guid=”+Guid.NewGuid(), “viewerstate”);

this adds a new query string parameter “guid” to your url who’s value change every time you load the collection and since it’s a different url, PivotViewer doesn’t find the collection in its cache and hence sends a call to the JIT server to get the new collection. Therefore, you will always see fresh data in your collection but what you should keep in mind is that this will degrade the performance of the PivotViewer (but only at the time of loading), so what you can do as a further tweak is only change the guid parameter when you need fresh data in your collection and in all the other cases you can use the url with the same guid parameter which will help in keeping the performance of the PivotViewer intact, this tweak will be a boon if your JIT server takes time for generating the collection and the data is not updated always.

Happy Coding 🙂

I have been working around with the Silverlight Pivot Viewer for long time now and I have noticed that whenever I use a JIT collection I dont see Description in the facet panel but when I use a static collection, Description is available. First I thought maybe description facet is not allowed in JIT collections but then I remembered that the Collection class constructor does ask for Description in its parameter, this made my curious and I started dive into the code of PivotServerTools Project and I found that there’s no place in the code where this Description is used apart from when creating the default item images. After a lot of searching I was able to find a solution using which Description can be made visible. To do so, follow the below steps:

1. Go to the PivotServerTools project.
2. Open the Internal folder.
3. There you’ll see a “CxmlSerializer” class, this class is used when the method Collection.ToCXML() is invoked, which is done by the Pivot Viewer. Open this class and navigate to the method “MakeItemContent”. In this method add the following line:

if (!string.IsNullOrEmpty(item.Description)){
	yield return new XStreamingElement(Xmlns + "Description", item.Description);
}

This way Description will start showing up in the Facet panel of every collection provided you specified a value for it in the Item’s collection class constructor.

Excel Operations – Open Xml Unleashed

My today’s post is about working with the Excel documents where I mention about how you can import data from an Excel file into a DataTable and Export a DataTable to an Excel File. I’ll be using Office Open Xml to accomplish the requirement which has its own pros and cons.

Pros:

  1. When reading/generating an Excel file through other methods, you need to have installed on the server machine the office runtime dlls and is some cases the office should be installed too. And while generating the excel file, always an instance of the Excel.exe is created on the server machine, meaning that if 100 files are getting generated 100 Excel.exe instances will be opened on the server machine, which could be stressful for the server performance and also handling the closing of the instances after the generation is finished is another problem. But in case of Office Open Xml, all you need to have is to install the Open Xml SDK (currently it’s in v2.0) and nothing else. You don’t need have office on the server and no instances or what so ever are created, so the performance of the server is always in control.
  2. The Open Xml format is completely interoperable, meaning the excel document created in this format can be worked on across any platform.

Cons:

There’s only one problem with Open Xml, it major supports only the documents created in Office 2007 or later (i.e. xlsx, docx), which means that if you wish to work on documents created in the older version of the office will not work as desired. But, the documents that are created in Office 2007 or later and saved in the Office 97-2003 format will still work.

Below I am going to post my code that contains the methods for working with the Excel Documents using the Open Xml SDK v2.0 which you can use directly for generating/reading excel documents.

Generating an Excel File

	#region Generating an Excel File

	/// <summary>
	/// Converts the DataTable into the excel format and stores at the target location (also takes care of the date values)
	/// </summary>
	/// <param name="sourceTable">Source DataTable</param>
	/// <param name="targetFilePath">Target File Path</param>
	/// <param name="dateColumnsInDataTable">List of Columns with Date datatype in the DataTable</param>
	public static void ConvertDataTableToExcel(DataTable sourceTable, string targetFilePath, List<string> dateColumnsInDataTable)
	{
		if (File.Exists(targetFilePath))
			File.Delete(targetFilePath);

		SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(targetFilePath, SpreadsheetDocumentType.Workbook);
		
		spreadsheetDocument.AddWorkbookPart();
		spreadsheetDocument.Close();

		InsertWorksheet(targetFilePath);

		using (SpreadsheetDocument myWorkbook = SpreadsheetDocument.Open(targetFilePath, true))
		{
			var wrkSheet = myWorkbook.WorkbookPart.WorksheetParts.First().Worksheet;
			var sheetData = wrkSheet.GetFirstChild<SheetData>();

			var colValueList = new List<KeyValuePair<string, string>>();

			//Add Rows to the Sheet                                
			for (int j = 0; j < sourceTable.Columns.Count; j++)
			{
				colValueList.Add(new KeyValuePair<string, string>(FindDestinationColLetter("A", j), sourceTable.Columns[j].ColumnName));
			}

			var columnRow = CreateContentRow(1, colValueList);
			sheetData.AppendChild(columnRow);

			for (int i = 0; i < sourceTable.Rows.Count; i++)
			{
				var rowValList = new List<KeyValuePair<string, string>>();

				for (int j = 0; j < sourceTable.Columns.Count; j++)
				{
					string rowVal;
					if (dateColumnsInDataTable.Contains(sourceTable.Columns[j].ColumnName))
					{
						//Checks if the date is in date format or Julian format (that excel returns) and stores it accordingly
						var dateVal = sourceTable.Rows[i][j].ToString();
						if (IsDateFormat(dateVal))
						{
							rowVal = dateVal;
						}
						else
						{
							int no;
							rowVal = int.TryParse(dateVal, out no)
											  ? ExcelSerialDateToDMY(no)
											  : dateVal;
						}
					}
					else
						rowVal = sourceTable.Rows[i][j].ToString();

					rowValList.Add(new KeyValuePair<string, string>(FindDestinationColLetter("A", j), rowVal));
				}

				var row = CreateContentRow(i + 2, rowValList);
				sheetData.AppendChild(row);
			}
			wrkSheet.Save();
		}
	}

	/// <summary>
	/// Checks if the give string is a date
	/// </summary>
	/// <param name="dateStr">String</param>
	/// <returns></returns>
	private static bool IsDateFormat(string dateStr)
	{
		//Remove the trailing timestamp
		dateStr = dateStr.Split(' ')[0];

		DateTime date;
		if (DateTime.TryParse(dateStr, out date))
			return true;

		if (DateTime.TryParse(dateStr, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out date))
		{
			return true;
		}

		if (DateTime.TryParse(dateStr, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowWhiteSpaces, out date))
		{
			return true;
		}

		var dateArr = dateStr.Split('-');
		if (dateArr.Length == 3)
		{
			int no;
			return int.TryParse(dateArr[0], out no) && int.TryParse(dateArr[1], out no) && int.TryParse(dateArr[2], out no);
		}

		return false;
	}

	/// <summary>
	/// Converts the given Excel Julian date to the Date-Month-Year format
	/// </summary>
	/// <param name="nSerialDate">Date in the Excel Julian Date Format</param>
	/// <returns></returns>
	public static string ExcelSerialDateToDMY(int nSerialDate)
	{
		// Excel/Lotus 123 have a problem with 29-02-1900. 1900 is not a leap year, but Excel/Lotus 123 think it is...

		int nDay;
		int nMonth;
		int nYear;

		if (nSerialDate == 60)
		{
			nDay = 29;
			nMonth = 2;
			nYear = 1900;

			return nDay + "-" + nMonth + "-" + nYear;
		}

		if (nSerialDate < 60)
		{
			// Because of the 29-02-1900 problem, any serial date under 60 is one off... Compensate.
			nSerialDate++;
		}

		// Modified Julian to DMY calculation with an addition of 2415019
		var l = nSerialDate + 68569 + 2415019;
		var n = (4 * l) / 146097;
		l = l - (146097 * n + 3) / 4;
		var i = (4000 * (l + 1)) / 1461001;
		l = l - (1461 * i) / 4 + 31;
		var j = (80 * l) / 2447;
		nDay = l - (2447 * j) / 80;
		l = j / 11;
		nMonth = j + 2 - (12 * l);
		nYear = 100 * (n - 49) + i + l;

		return nDay + "-" + nMonth + "-" + nYear;
	}

	/// <summary>
	/// Inserts a worksheet inside the given excel path
	/// </summary>
	/// <param name="excelFilePath"></param>
	private static void InsertWorksheet(string excelFilePath)
	{
		// Open the document for editing.
		using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(excelFilePath, true))
		{
			// Add a blank WorksheetPart.
			spreadSheet.WorkbookPart.Workbook = new Workbook();
			var newWorksheetPart = spreadSheet.WorkbookPart.AddNewPart<WorksheetPart>();
			newWorksheetPart.Worksheet = new Worksheet(new SheetData());
			newWorksheetPart.Worksheet.Save();

			spreadSheet.WorkbookPart.Workbook.AppendChild(new Sheets());
			var sheets = spreadSheet.WorkbookPart.Workbook.GetFirstChild<Sheets>();
			string relationshipId = spreadSheet.WorkbookPart.GetIdOfPart(newWorksheetPart);

			// Get a unique ID for the new worksheet.
			uint sheetId = 1;
			if (sheets.Elements<Sheet>().Count() > 0)
			{
				sheetId = sheets.Elements<Sheet>().Select(s => s.SheetId.Value).Max() + 1;
			}

			// Give the new worksheet a name.
			string sheetName = "Sheet" + sheetId;

			// Append the new worksheet and associate it with the workbook.
			var sheet = new Sheet { Id = relationshipId, SheetId = sheetId, Name = sheetName };
			sheets.Append(sheet);
			spreadSheet.WorkbookPart.Workbook.Save();
		}
	}

	/// <summary>
	/// Inserts the give row data in the excel file
	/// </summary>
	/// <param name="rowIndex">The index at which the row has to be inserted</param>
	/// <param name="valueList">The Row content</param>
	/// <returns></returns>
	private static Row CreateContentRow(int rowIndex, IEnumerable<KeyValuePair<string, string>> valueList)
	{
		//Create new row
		var row = new Row { RowIndex = (UInt32)rowIndex };

		foreach (var keyValuePair in valueList)
		{
			row.AppendChild(CreateTextCell(keyValuePair.Key, keyValuePair.Value, rowIndex));
		}

		return row;

	}

	/// <summary>
	/// Create a text cell that can be inserted in the excel row
	/// </summary>
	/// <param name="headerCellName">Header cell name</param>
	/// <param name="cellText">Cell Text</param>
	/// <param name="cellRowIndex">Row Index</param>
	/// <returns></returns>
	private static Cell CreateTextCell(string headerCellName, string cellText, int cellRowIndex)
	{
		int intText;
		var cell = new Cell { CellReference = headerCellName + cellRowIndex };
		if (int.TryParse(cellText, out intText))
		{
			var cellValue = new CellValue { Text = cellText };
			cell.AppendChild(cellValue);
			return cell;
		}

		//Create new inline string cell
		cell.DataType = CellValues.InlineString;

		//Add text to text cell
		var inlineString = new InlineString();
		if (string.IsNullOrWhiteSpace(cellText))
		{
			cellText = string.Empty;
		}
		
		var t = new Text { Text = SecurityElement.Escape(cellText) };
		inlineString.AppendChild(t);

		cell.AppendChild(inlineString);

		return cell;
	}

	/// <summary>
	/// Finds the Column to be worked on, based on the first column letter and target Column Index
	/// </summary>
	/// <param name="startingColumnName">Header name</param>
	/// <param name="targetColumnIndex">Index of the target column starting from the header</param>
	/// <returns></returns>
	private static string FindDestinationColLetter(string startingColumnName, int targetColumnIndex)
	{
		var columnLetters = startingColumnName.ToCharArray();

		for (var i = 1; i <= targetColumnIndex; i++)
		{
			if (columnLetters[columnLetters.Length - 1] == 'Z')
				columnLetters = ChangeColumnLetter(columnLetters, columnLetters.Length - 1);
			else
				columnLetters[columnLetters.Length - 1]++;
		}
		return new string(columnLetters);
	}

	private static char[] ChangeColumnLetter(char[] columnLetters, int columnIndex)
	{
		if (columnIndex == -1)
		{
			var newCharArray = new List<char> { 'A' };
			newCharArray.AddRange(columnLetters);
			columnLetters = newCharArray.ToArray();
		}
		else if (columnLetters[columnIndex] == 'Z')
		{
			columnLetters[columnIndex] = 'A';
			columnLetters = ChangeColumnLetter(columnLetters, --columnIndex);
		}
		else
		{
			columnLetters[columnIndex]++;
		}

		return columnIndex != -2 ? columnLetters : null;
	}

	#endregion
	

Reading an Excel File


	#region Reading an Excel File

	/// <summary>
	/// Get the DataTable from the Excel at the given file path
	/// </summary>
	/// <param name="filePath">Path of the excel file</param>
	/// <returns></returns>
	public static DataTable GetDataTableFromExcel(string filePath)
	{
		FileStream stream = File.Open(filePath, FileMode.Open, FileAccess.Read);

		//1. Reading from a binary Excel file ('97-2003 format; *.xls)
		//IExcelDataReader excelReader = ExcelReaderFactory.CreateBinaryReader(stream);            

		//2. Reading from a OpenXml Excel file (2007 format; *.xlsx)
		IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);

		//3. DataSet - The result of each spreadsheet will be created in the result.Tables
		//DataSet result = excelReader.AsDataSet();

		//4. DataSet - Create column names from first row
		excelReader.IsFirstRowAsColumnNames = true;
		DataSet result = excelReader.AsDataSet();

		//6. Free resources (IExcelDataReader is IDisposable)
		excelReader.Close();
		return result.Tables[0];
	}                

	#endregion

Using the above methods, you can easily start playing with the Excel documents using Open Xml. Go ahead and start playing! J

?

WCF Maximum Array Length Quota

Recently, while working with the WCF services I faced a problem which seemed to be very common but the solution to it is not clearly available. Let me explain what the problem is all about, when you try to consume your WCF service in another project like Silverlight, WPF, ASP.Net, etc; sometimes when you request the service for some data you may see the following error message:

“Error in deserializing body of reply message for operation ‘Your Method Name’. The maximum array length quota (16384) has been exceeded while reading XML data. This quota may be increased by changing the MaxArrayLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader.”

This happens because of the size of response message i.e. when your service prepares the result into a byte[] as response to your request, it finds out that the “Maximum Array Length” is set to 16384 and since your response byte[] would be greater than this limit it throws an exception. In order to cure this problem, open the config file of the project where this WCF service is consumed – ServiceReferences.ClientConfig (Silverlight), app.config (WPF) or web.config(ASP.Net). Here you’ll see the binding settings of the service (please note: you may see multiple bindings, one for each service you have consumed. In this case, identify the binding of the service which is giving this exception). In the setting you’ll see something similar to below:


<binding name="CustomBinding_Testervice">
	<binaryMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16" maxSessionSize="2048">
		<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" 
			maxBytesPerRead="4096" maxNameTableCharCount="16384" />
	</binaryMessageEncoding>
	<httpTransport manualAddressing="false" maxBufferPoolSize="524288" 
		maxReceivedMessageSize="65536" allowCookies="false" authenticationScheme="Anonymous" 
		bypassProxyOnLocal="false" decompressionEnabled="true" 
		hostNameComparisonMode="StrongWildcard" keepAliveEnabled="true" maxBufferSize="65536"
		proxyAuthenticationScheme="Anonymous" realm="" transferMode="Buffered"
		unsafeConnectionNtlmAuthentication="false" useDefaultWebProxy="true" />
</binding>

Over here you’ll see that the value of “maxArrayLength” is set to “16384”. Just increase this length (you can change other limits too) and your problem will be cured. After correction, the binding may look like this:


<binding name="CustomBinding_TestService">
	<binaryMessageEncoding maxReadPoolSize="2147483647" maxWritePoolSize="2147483647" 
		maxSessionSize="2147483647">
		<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" 
		maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
	</binaryMessageEncoding>
	<httpTransport manualAddressing="false" maxBufferPoolSize="2147483647" 
		maxReceivedMessageSize="2147483647" allowCookies="false" authenticationScheme="Anonymous"
		bypassProxyOnLocal="false" decompressionEnabled="true" 
		hostNameComparisonMode="StrongWildcard" keepAliveEnabled="true" maxBufferSize="2147483647" 
		proxyAuthenticationScheme="Anonymous" realm="" transferMode="Buffered" 
		unsafeConnectionNtlmAuthentication="false" useDefaultWebProxy="true" />
</binding>

Please see that the max length cannot go over “2147483647” as its max integer value WCF supports. Also, it’s not required to increase the limits of other properties but it’s a good practice and also helps in preventing any future unanticipated errors.

Create a SQL Table from a view

In order to create a table from a view that you have already created, all u need is to execute the following query:

select * INTO <Table Name> from <View Name>

Image operations in C# – Mysteries Solved

In C#, we can do a lot of image operations using which one can easily manipulate images and use them in their projects. The post is divided in 6 sections with each section with their respective codes answering questions asked to me many time before and apparently people are still looking for them:

1. Image conversion

a. Converting a image to byte array – in various scenarios you’ll come across this situation where you would require to convert an image into a byte [] so that you can store it into the database or transmit the image over the web. It can be done like follows:


public static Byte[] ConvertImageToByteArray(Bitmap image)
{
	var ic = new ImageConverter();
    return ic.ConvertTo(image, typeof(byte[])) as byte[];
}

The same thing can be done using Memory Stream as follows:


public byte[] ImageToByteArray(Image imageIn)
{
   var ms = new MemoryStream();
   imageIn.Save(ms, ImageFormat.Bmp);
   return ms.ToArray();
}

b. Converting a byte array t0 Image – similar to the above situation, you would like to convert a byte [] that you obtained from the database (or any other source) into an image object. You can do it like below:


public static Bitmap ConvertByteArrayToImage(byte[] byteArray)
{
    var ic = new ImageConverter();
    var img = (Image)ic.ConvertFrom(byteArray);
    return new Bitmap(img);
}

Using Memory Stream it can be done as follows:


public Image ByteArrayToImage(byte[] byteArrayIn)
{
    var ms = new MemoryStream(byteArrayIn);
	var returnImage = Image.FromStream(ms);
    return returnImage;
}

2. Creating an Image from Text

Yes, this is possible via code J, its somewhat like creating a captcha that is used by many site for security purpose. The code to it is:


public static Bitmap CreateBitmapImageFromText(string sImageText, int width, int height)
{
	var objBmpImage = new Bitmap(width, height);

	// Create the Font object for the image text drawing.
	var objFont = new Font("Arial", 40, FontStyle.Bold, GraphicsUnit.Pixel);

	// Create a graphics object to measure the text's width and height.
	Graphics objGraphics = Graphics.FromImage(objBmpImage);

	// This is where the bitmap size is determined.
	var intWidth = (int)objGraphics.MeasureString(sImageText, objFont).Width;
	var intHeight = (int)objGraphics.MeasureString(sImageText, objFont).Height;

	// Create the bmpImage again with the correct size for the text and font.
	objBmpImage = new Bitmap(objBmpImage, new Size(intWidth, intHeight));

    // Add the colors to the new bitmap.
	objGraphics = Graphics.FromImage(objBmpImage);

	// Set Background color
	objGraphics.Clear(Color.Black);
	objGraphics.SmoothingMode = SmoothingMode.AntiAlias;
	objGraphics.TextRenderingHint = TextRenderingHint.AntiAlias;
	objGraphics.DrawString(sImageText, objFont, new SolidBrush(Color.White), 0, 0);
	objGraphics.Flush();
	return (objBmpImage);

}

3. Getting an Image from a Url

If you wish to download an image from any given url and convert into an image object, you can do so as follows:


public static Image GetImageFromURL(string strURL)
{
	Image retVal;
	
	try
	{
		var request = (HttpWebRequest)WebRequest.Create(strURL);
		request.Timeout = 60000; // 5 seconds in milliseconds
		request.ReadWriteTimeout = 20000; // allow up to 20 seconds to elapse

		// execute the request
		var response = (HttpWebResponse)request.GetResponse();
		retVal = Image.FromStream(response.GetResponseStream());
	}
	catch (Exception)
	{
		retVal = null;
	}

	return retVal;
}

4. Getting thumbnail of a word document

If you require using a thumbnail of word document you can do that too. The code I mentioned below uses Open Xml and to be able to use it, follow the below steps:

  • Download and install the Open Xml SDK 2.0.
  • In your project, add reference to “DocumentFormat.OpenXml” and “WindowsBase”.
  • Add this statement at the top of the file “using DocumentFormat.OpenXml.Packaging;”

Now you can use the following code:


public Image GetDocThumnail(string docFilePath)
{
	var wordprocessingDocument = WordprocessingDocument.Open(docFilePath, false);
	var thumbnailPart = wordprocessingDocument.ThumbnailPart;
	if (thumbnailPart != null)
	{
		var stream = thumbnailPart.GetStream(FileMode.Open, FileAccess.Read);
		var byteArr = ReadAllBytes(stream);
		return ByteArrayToImage(byteArr);               
	}
	
	return null;
}

public static byte[] ReadAllBytes(Stream source)
{
	var originalPosition = source.Position;
	source.Position = 0;

	try
	{
		var readBuffer = new byte[4096];
		var totalBytesRead = 0;
		int bytesRead;

		while ((bytesRead = source.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
	    {
			totalBytesRead += bytesRead;
			if (totalBytesRead != readBuffer.Length) continue;

		    var nextByte = source.ReadByte();
			if (nextByte == -1) continue;

			var temp = new byte[readBuffer.Length * 2];
			System.Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
			System.Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
			readBuffer = temp;
			totalBytesRead++;

		}

	    var buffer = readBuffer;
	    if (readBuffer.Length == totalBytesRead) return buffer;

		buffer = new byte[totalBytesRead];
		System.Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
		return buffer;

	}
	finally
	{
		source.Position = originalPosition;
	}
}

Please note that a method named “ByteArrayToImage” is used, this has been already defined in the first section i.e. Image Conversion (you can copy it from there).

5. Combining two images

In a scenario where you would like to combine two images either horizontally or vertically, you can do so by using 2 or more bitmap images as follows:

a. Combining images vertically


public static Bitmap CombineVertically(Bitmap[] files)
{
	//read all images into memory
	var images = new List<Bitmap>();
	Bitmap finalImage = null;

	try
	{
		int width = 0;
		int height = 0;
		
		foreach (Bitmap image in files)
		{
			//create a Bitmap from the file and add it to the list
			Bitmap bitmap = image;

			//update the size of the final bitmap
			width = bitmap.Width > width ? bitmap.Width : width;
			height += bitmap.Height;
			images.Add(bitmap);
		}

		//create a bitmap to hold the combined image
		finalImage = new Bitmap(width, height);

		//get a graphics object from the image so we can draw on it
		using (Graphics g = Graphics.FromImage(finalImage))
		{
			//set background color
			g.Clear(Color.Black);

		    //go through each image and draw it on the final image
			int offset = 0;

			foreach (Bitmap image in images)
			{
				g.DrawImage(image, new Rectangle(0, offset, image.Width, image.Height));
				offset += image.Height;
			}
		}

		return finalImage;
	}
	catch (Exception)
	{
		if (finalImage != null)
			finalImage.Dispose();

		throw;
	}
	finally
	{
		//clean up memory
		foreach (Bitmap image in images)
		{
			image.Dispose();
		}
	}
}

b. Combining images horizontally


public static Bitmap CombineHorizontally(Bitmap[] files, int[,] dimentions, int[,] finalImageDimentions)
{
	//read all images into memory
	var images = new List<Bitmap>();

	Bitmap finalImage = null;

	try
	{
		int width = 0;
		int height = 0;

		foreach (Bitmap image in files)
		{
			//create a Bitmap from the file and add it to the list
			Bitmap bitmap = image;

		    //update the size of the final bitmap
			width += bitmap.Width;
			height = bitmap.Height;
			images.Add(bitmap);
		}

		//create a bitmap to hold the combined image
		finalImage = new Bitmap(finalImageDimentions[0, 0], finalImageDimentions[0, 1]);

		//get a graphics object from the image so we can draw on it
		using (Graphics g = Graphics.FromImage(finalImage))
		{
			//set background color
			g.Clear(Color.Black);

			//go through each image and draw it on the final image
			int offset = 0;
			
			foreach (Bitmap image in images)
			{
				if (offset == 0)
				{
					int titleimageSpaceWidthCenter = dimentions[0, 0] / 2;
					int titleimageoffset = titleimageSpaceWidthCenter - image.Width / 2;
					g.DrawImage(image, new Rectangle(titleimageoffset, 0, image.Width, image.Height));
					offset += dimentions[0, 0];
				}
				else
				{
					int i = dimentions[0, 1] / 2;
					g.DrawImage(image, new Rectangle(offset, i, dimentions[1, 0], dimentions[1, 1]));
					offset += dimentions[1, 0];
				}
			}
		}

	   return finalImage;
	}
	catch (Exception)
	{
		if (finalImage != null)
			finalImage.Dispose();
		
		throw;
	}
	finally
	{
		//clean up memory
		foreach (Bitmap image in images)
			image.Dispose();
	}
}

6. Resizing an image while maintaining the aspect ratio and maximum width & height

This use case I came across just while writing this blog and believe me, it’s one of the most required codes that I have come across. It’s almost like resizing an image using Photoshop and without losing any quality at all. The method resizes a given image in such a way that the dimensions of the image are contained inside the maxWidth and maxHeight values.


public static Bitmap ResizeImage(Bitmap fullsizeImage, int maxWidth, int maxHeight)
{
	// Prevent using images internal thumbnail
	fullsizeImage.RotateFlip(RotateFlipType.Rotate180FlipNone);
	fullsizeImage.RotateFlip(RotateFlipType.Rotate180FlipNone);

    int imageWidth;
	int imageHeight;

	var newWidth = maxHeight*fullsizeImage.Width/fullsizeImage.Height;
	if(newWidth > maxWidth)
	{
		imageHeight = fullsizeImage.Height * maxWidth / fullsizeImage.Width;               
		imageWidth = maxWidth;
	}
	else
	{
		imageWidth = newWidth;
		imageHeight = maxHeight;
	}

    Image newImage = fullsizeImage.GetThumbnailImage(imageWidth, imageHeight, null, IntPtr.Zero);
	
	// Clear handle to original file so that we can overwrite it if necessary
	fullsizeImage.Dispose();

	// Return resized picture
	return (Bitmap) newImage;
}

Using the above methods you will be able to do most of the image operations required by you. Just in case something is not clear to you or you need help on a related issue, you can contact me.