using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using GMap.NET;
namespace gheat
{
/// <summary>
/// Gets a single tile
/// NOTE: Blank tiles are cached so don't dispose them
/// </summary>
public class Tile
{
private static Dictionary<string, Bitmap> _emptyTile = new Dictionary<string, Bitmap>();
private static object _emptyTileLocker = new Object();
private static int[] _zoomOpacity = null;
private static Tile _thisInstance = new Tile();
private Tile()
{
Opacity o = new Opacity();
_zoomOpacity = o.BuildZoomMapping();
}
/// <summary>
/// Generates a tile
///
/// For blank Tiles:
/// You must NOT modify the image. Don't Dispose it!!! It is cached, so if you dispose it you have nothing!!
/// </summary>
/// <param name="colorScheme">Image Color scheme</param>
/// <param name="dot">Image dot</param>
/// <param name="zoom">Current zoom level</param>
/// <param name="tileX">Tile x coordinante</param>
/// <param name="tileY">Tile y coordinante</param>
/// <param name="points">Points to add</param>
/// <param name="changeOpacityWithZoom">If false the default opacity is used instead of a changing value</param>
/// <param name="defaultOpacity">Used when change opacity with zoom is false</param>
/// <returns></returns>
public static Bitmap Generate(Bitmap colorScheme, Bitmap dot, int zoom,int tileX, int tileY, GMap.NET.Point[] points, bool changeOpacityWithZoom,int defaultOpacity)
{
int expandedWidth;
int expandedHeight;
int dotHalfSize;
int pad;
int x1;
int x2;
int y1;
int y2;
if(defaultOpacity < Opacity.TRANSPARENT || defaultOpacity > Opacity.OPAQUE)
throw new Exception("The default opacity of '" + defaultOpacity.ToString() + "' doesn't fall between '" + Opacity.TRANSPARENT.ToString() + "' and '" + Opacity.OPAQUE.ToString() + "'") ;
//Translate tile to pixel coords.
x1 = tileX * GHeat.SIZE;
x2 = x1 + 255;
y1 = tileY * GHeat.SIZE;
y2 = y1 + 255;
dotHalfSize = dot.Width;
pad = dotHalfSize;
int extraPad = dot.Width * 2;
//Expand bounds by one dot width.
x1 = x1 - extraPad;
x2 = x2 + extraPad;
y1 = y1 - extraPad;
y2 = y2 + extraPad;
expandedWidth = x2 - x1;
expandedHeight = y2 - y1;
Bitmap tile;
if (points.Length == 0)
{
if (changeOpacityWithZoom)
tile = GetEmptyTile(colorScheme, _zoomOpacity[zoom]);
else
tile = GetEmptyTile(colorScheme, defaultOpacity);
}
else
{
tile = GetBlankImage(expandedHeight, expandedWidth);
tile = AddPoints(tile, dot, points);
tile = Trim(tile, dot);
if (changeOpacityWithZoom)
tile = Colorize(tile, colorScheme, _zoomOpacity[zoom]);
else
tile = Colorize(tile, colorScheme, defaultOpacity);
}
return tile;
}
/// <summary>
/// Takes the gray scale and applies the color scheme to it.
/// </summary>
/// <param name="tile"></param>
/// <returns></returns>
public static Bitmap Colorize(Bitmap tile, Bitmap colorScheme,int zoomOpacity)
{
Color tilePixelColor;
Color colorSchemePixel;
for(int x=0;x< tile.Width ;x++)
{
for (int y = 0; y < tile.Height; y++)
{
//Get color for this intensity
tilePixelColor = tile.GetPixel(x, y);
//Get the color of the scheme from the intensity on the tile
//Only need to get one color in the tile, because it is grayscale, so each color will have the same intensity
colorSchemePixel = colorScheme.GetPixel(0, tilePixelColor.R);
zoomOpacity = (int)(
(
((float)zoomOpacity / 255.0f)
*
((float)colorSchemePixel.A / 255.0f)
) * 255f
);
tile.SetPixel(x, y, Color.FromArgb(zoomOpacity, colorSchemePixel));
}
}
return tile;
}
/// <summary>
/// Trim the larger tile to the correct size
/// </summary>
/// <param name="tile"></param>
/// <returns></returns>
public static Bitmap Trim(Bitmap tile,Bitmap dot)
{
Bitmap croppedTile = new Bitmap(GHeat.SIZE, GHeat.SIZE, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(croppedTile);
int adjPad = dot.Width + (dot.Width / 2);
g.DrawImage(
tile, // Source Image
new System.Drawing.Rectangle(0, 0, GHeat.SIZE, GHeat.SIZE),
adjPad, // source x, adjusted for padded amount
adjPad, // source y, adjusted for padded amount
GHeat.SIZE, //source width
GHeat.SIZE, // source height
GraphicsUnit.Pixel
);
return croppedTile;
}
/// <summary>
/// Add all of the points to the tile
/// </summary>
/// <param name="tile"></param>
/// <param name="points"></param>
/// <returns></returns>
public static Bitmap AddPoints(Bitmap tile,Bitmap dot, GMap.NET.Point[] points)
{
KVImage.ImageBlender blender = new KVImage.ImageBlender();
for (int i = 0; i < points.Length; i++)
{
if (points[i].Weight.HasValue && points[i].Weight.Value == 0)
{
//Skip a value of zero, it scored so low it should not show up.
}
else
{
//Blend each dot to the existing images
blender.BlendImages(
tile, // Destination Image
points[i].X + dot.Width, //Dest x
points[i].Y + dot.Width, //Dest y
dot.Width, // Dest width
dot.Height, // Dest height
//If their is a weight then change the dot so it reflects that intensity
points[i].Weight.HasValue ? ApplyWeightToImage(dot, points[i].Weight.Value) : dot, // Src Image
0, // Src x
0, // Src y
KVImage.ImageBlender.BlendOperation.Blend_Multiply);
}
}
return tile;
}
/// <summary>
/// Change the intensity of the image
/// </summary>
/// <param name="image">Dot image</param>
/// <param name="weight">Weight to apply</param>
/// <returns></returns>
private static Bitmap ApplyWeightToImage(Bitmap image, decimal weight)
{
Graphics graphic;
float tempWeight;
Bitmap tempImage = new Bitmap(image.Width,image.Height,PixelFormat.Format32bppArgb );
graphic = Graphics.FromImage(tempImage);
//I want to make the color more intense (White/bright)
tempWeight = (float)(weight);
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {tempWeight, 0, 0, 0, 0},
new float[] {0, tempWeight, 0, 0, 0},
new float[] {0, 0,tempWeight, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
// Create a ColorMatrix
ColorMatrix clrMatrix = new ColorMatrix(ptsArray);
// Create ImageAttributes
ImageAttributes imgAttribs = new ImageAttributes();
// Set color matrix
//imgAttribs.SetColorMatrix(clrMatrix,
// //I dont know why, but when i tell it to Skip the Grays the color altering
// // works on all computers. Otherwise it does not shade correctly.
// ColorMatrixFlag.SkipGrays,
// ColorAdjustType.Bitmap);
//http://www.c-sharpcorner.com/UploadFile/puranindia/610/
//Gamma values range from 0.1 to 5.0 (normally 0.1 to 2.2), with 0.1 being the brightest and 5.0 the darkest.
//Convert the 100% to a range of 0-5 by multiplying it by 5
imgAttribs.SetGamma((tempWeight == 0 ? .1f : (tempWeight * 5)), ColorAdjustType.Bitmap);
// Draw Image with the attributes
graphic.DrawImage(image,
new System.Drawing.Rectangle(0, 0, image.Width, image.Height),
0, 0, image.Width, image.Height,
GraphicsUnit.Pixel, imgAttribs);
//New dot with a different intensity
return tempImage;
}
/// <summary>
/// Gets a blank image / canvas
/// </summary>
/// <returns></returns>
public static Bitmap GetBlankImage(int height, int width)
{
Bitmap newImage;
Graphics g;
//Create a blank tile that is 32 bit and has an alpha
newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
g = Graphics.FromImage(newImage);
//Background must be white so the dots can blend
g.FillRectangle(Brushes.White, new System.Drawing.Rectangle(0, 0, width, height));
return newImage;
}
/// <summary>
/// Empty tile with no points on it.
/// NOTE: You must not modify this image. Don't Dispose it!!!
/// </summary>
/// <returns></returns>
public static Bitmap GetEmptyTile(Bitmap colorScheme,int zoomOpacity)
{
Color colorSchemePixelColor;
Bitmap tile;
Graphics graphic;
SolidBrush solidBrush;
//If we have already created the empty tile then return it
if (_emptyTile.ContainsKey(colorScheme.GetHashCode().ToString() + "_" + zoomOpacity.ToString() ))
return _emptyTile[colorScheme.GetHashCode() + "_" + zoomOpacity.ToString()];
//Create a blank tile that is 32 bit and has an alpha
tile = new Bitmap(GHeat.SIZE, GHeat.SIZE, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
graphic = Graphics.FromImage(tile);
//Get the first pixel of the color scheme, on the dark side
colorSchemePixelColor = colorScheme.GetPixel(0, colorScheme.Height - 1);
zoomOpacity = (int)((
(zoomOpacity / 255.0f) //# From Configuration
*
(colorSchemePixelColor.A / 255.0f) //#From per-pixel alpha
) * 255.0f);
solidBrush = new SolidBrush(Color.FromArgb(zoomOpacity, colorSchemePixelColor.R, colorSchemePixelColor.G, colorSchemePixelColor.B));
graphic.FillRectangle(solidBrush, 0, 0, GHeat.SIZE, GHeat.SIZE);
graphic.Dispose();
//Save the newly created empty tile
//There is a empty tile for each scheme and zoom level
lock (_emptyTileLocker)
{
//Double check it does not already exists
if (!_emptyTile.ContainsKey(colorScheme.GetHashCode().ToString()))
_emptyTile.Add(colorScheme.GetHashCode().ToString() , tile);
}
return tile;
}
}
}