What is a good algorithm for pixelating an image in C# .NET?
A simple, yet unefficient solution would be to resize to a smaller size, then resize back using pixel duplication.
A better solution would be (pseudo-code):
(Time O(n), Additional space (besides mutable source image): O(1))
// Pixelize in x axis (choose a whole k s.t. 1 <= k <= Width)
var sum = Pixel[0, 0];
for (y = 0; y < Height; y++)
{
for (x = 0; x < Width + 1; x++)
{
if (x % k == 0)
{
sum /= k;
for (xl = Max(0, x-k); xl < x; xl++)
Pixel[y, xl] = sum;
sum = 0;
}
if (x == Width)
break;
sum += Pixel[y, x];
}
}
// Now do the same in the y axis
// (make sure to keep y the outer loop - for better performance)
// If your image has more than one channel, then then Pixel should be a struct.
The guy over at this forum has a pretty good algorithm. It works by taking the average of all of the colors in each "block."
I just used his implementation in C#/GDI+ today:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Linq;
using System.Text;
/// <summary>
/// Applies a pixelation effect to an image.
/// </summary>
[SuppressMessage(
"Microsoft.Naming",
"CA1704",
Justification = "'Pixelate' is a word in my book.")]
public class PixelateEffect : EffectBase
{
/// <summary>
/// Gets or sets the block size, in pixels.
/// </summary>
private int blockSize = 10;
/// <summary>
/// Gets or sets the block size, in pixels.
/// </summary>
public int BlockSize
{
get
{
return this.blockSize;
}
set
{
if (value <= 1)
{
throw new ArgumentOutOfRangeException("value");
}
this.blockSize = value;
}
}
/// <summary>
/// Applies the effect by rendering it onto the target bitmap.
/// </summary>
/// <param name="source">The source bitmap.</param>
/// <param name="target">The target bitmap.</param>
public override void DrawImage(Bitmap source, Bitmap target)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (target == null)
{
throw new ArgumentNullException("target");
}
if (source.Size != target.Size)
{
throw new ArgumentException("The source bitmap and the target bitmap must be the same size.");
}
using (var graphics = Graphics.FromImage(target))
{
graphics.PageUnit = GraphicsUnit.Pixel;
for (int x = 0; x < source.Width; x += this.BlockSize)
{
for (int y = 0; y < source.Height; y += this.BlockSize)
{
var sums = new Sums();
for (int xx = 0; xx < this.BlockSize; ++xx)
{
for (int yy = 0; yy < this.BlockSize; ++yy)
{
if (x + xx >= source.Width || y + yy >= source.Height)
{
continue;
}
var color = source.GetPixel(x + xx, y + yy);
sums.A += color.A;
sums.R += color.R;
sums.G += color.G;
sums.B += color.B;
sums.T++;
}
}
var average = Color.FromArgb(
sums.A / sums.T,
sums.R / sums.T,
sums.G / sums.T,
sums.B / sums.T);
using (var brush = new SolidBrush(average))
{
graphics.FillRectangle(brush, x, y, (x + this.BlockSize), (y + this.BlockSize));
}
}
}
}
}
/// <summary>
/// A structure that holds sums for color averaging.
/// </summary>
private struct Sums
{
/// <summary>
/// Gets or sets the alpha component.
/// </summary>
public int A
{
get;
set;
}
/// <summary>
/// Gets or sets the red component.
/// </summary>
public int R
{
get;
set;
}
/// <summary>
/// Gets or sets the blue component.
/// </summary>
public int B
{
get;
set;
}
/// <summary>
/// Gets or sets the green component.
/// </summary>
public int G
{
get;
set;
}
/// <summary>
/// Gets or sets the total count.
/// </summary>
public int T
{
get;
set;
}
}
}
Caveat emptor, works on my machine, & etc.
While I don't know of a well know algorithm for this, I did have to write something similar. The technique I used was pretty simple, but I am thinking not very efficient for large images. Basically I would take the image and do color averaging in 5 (or howerver big you want) pixel blocks and then make all those pixels the same color. You could speed this up by doing the average on just the diagonal pixels which would save a lot of cycles but be less accurate.
Related
To be brief, I'm trying to implement some motion detection algorithm and in my case I'm working on UWP using portable version of AForge library to processing image. Avoiding this issue, I have problem with conversion SoftwareBitmap object (that I get from MediaFrameReader) to Bitmap object (and vice versa) that I use in my code associated with motion detection. In consequence of this conversion, I get proper image with big red X in the foreground. Code below:
private async void FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
{
var frame = sender.TryAcquireLatestFrame();
if (frame != null && !_detectingMotion)
{
SoftwareBitmap aForgeInputBitmap = null;
var inputBitmap = frame.VideoMediaFrame?.SoftwareBitmap;
if (inputBitmap != null)
{
_detectingMotion = true;
//The XAML Image control can only display images in BRGA8 format with premultiplied or no alpha
if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Bgra8
&& inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
{
aForgeInputBitmap = SoftwareBitmap.Copy(inputBitmap);
}
else
{
aForgeInputBitmap = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore);
}
await _aForgeHelper.MoveBackgrounds(aForgeInputBitmap);
SoftwareBitmap aForgeOutputBitmap = await _aForgeHelper.DetectMotion();
_frameRenderer.PresentSoftwareBitmap(aForgeOutputBitmap);
_detectingMotion = false;
}
}
}
class AForgeHelper
{
private Bitmap _background;
private Bitmap _currentFrameBitmap;
public async Task MoveBackgrounds(SoftwareBitmap currentFrame)
{
if (_background == null)
{
_background = TransformToGrayscale(await ConvertSoftwareBitmapToBitmap(currentFrame));
}
else
{
// modifying _background in compliance with algorithm - in this case irrelevant
}
}
public async Task<SoftwareBitmap> DetectMotion()
{
// to check only this conversion
return await ConvertBitmapToSoftwareBitmap(_background);
}
private static async Task<Bitmap> ConvertSoftwareBitmapToBitmap(SoftwareBitmap input)
{
Bitmap output = null;
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
WriteableBitmap tmpBitmap = new WriteableBitmap(input.PixelWidth, input.PixelHeight);
input.CopyToBuffer(tmpBitmap.PixelBuffer);
output = (Bitmap)tmpBitmap;
});
return output;
}
private static async Task<SoftwareBitmap> ConvertBitmapToSoftwareBitmap(Bitmap input)
{
SoftwareBitmap output = null;
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
WriteableBitmap tmpBitmap = (WriteableBitmap)input;
output = new SoftwareBitmap(BitmapPixelFormat.Bgra8, tmpBitmap.PixelWidth, tmpBitmap.PixelHeight,
BitmapAlphaMode.Premultiplied);
output.CopyFromBuffer(tmpBitmap.PixelBuffer);
});
return output;
}
private static Bitmap TransformToGrayscale(Bitmap input)
{
Grayscale grayscaleFilter = new Grayscale(0.2125, 0.7154, 0.0721);
Bitmap output = grayscaleFilter.Apply(input);
return output;
}
Certainly, I've tried to detect some errors using try-catch clauses. I've found nothing. Thanks in advance.
EDIT (29/03/2018):
Generally, my app targets at providing some features associated with Kinect Sensor. App user has possibility to choose some feature from features list. First of all, this app have to be available on Xbox One, therefore I've chosen UWP. Due to 'scientific' issues, I've implemented MVVM pattern, using MVVM Light framework. As for PresentSoftwareBitmap() method, it comes from Windows-universal-samples repo and I paste FrameRenderer helper class below:
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
class FrameRenderer
{
private Image _imageElement;
private SoftwareBitmap _backBuffer;
private bool _taskRunning = false;
public FrameRenderer(Image imageElement)
{
_imageElement = imageElement;
_imageElement.Source = new SoftwareBitmapSource();
}
// Processes a MediaFrameReference and displays it in a XAML image control
public void ProcessFrame(MediaFrameReference frame)
{
var softwareBitmap = FrameRenderer.ConvertToDisplayableImage(frame?.VideoMediaFrame);
if (softwareBitmap != null)
{
// Swap the processed frame to _backBuffer and trigger UI thread to render it
softwareBitmap = Interlocked.Exchange(ref _backBuffer, softwareBitmap);
// UI thread always reset _backBuffer before using it. Unused bitmap should be disposed.
softwareBitmap?.Dispose();
// Changes to xaml ImageElement must happen in UI thread through Dispatcher
var task = _imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
// Don't let two copies of this task run at the same time.
if (_taskRunning)
{
return;
}
_taskRunning = true;
// Keep draining frames from the backbuffer until the backbuffer is empty.
SoftwareBitmap latestBitmap;
while ((latestBitmap = Interlocked.Exchange(ref _backBuffer, null)) != null)
{
var imageSource = (SoftwareBitmapSource)_imageElement.Source;
await imageSource.SetBitmapAsync(latestBitmap);
latestBitmap.Dispose();
}
_taskRunning = false;
});
}
}
// Function delegate that transforms a scanline from an input image to an output image.
private unsafe delegate void TransformScanline(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes);
/// <summary>
/// Determines the subtype to request from the MediaFrameReader that will result in
/// a frame that can be rendered by ConvertToDisplayableImage.
/// </summary>
/// <returns>Subtype string to request, or null if subtype is not renderable.</returns>
public static string GetSubtypeForFrameReader(MediaFrameSourceKind kind, MediaFrameFormat format)
{
// Note that media encoding subtypes may differ in case.
// https://learn.microsoft.com/en-us/uwp/api/Windows.Media.MediaProperties.MediaEncodingSubtypes
string subtype = format.Subtype;
switch (kind)
{
// For color sources, we accept anything and request that it be converted to Bgra8.
case MediaFrameSourceKind.Color:
return Windows.Media.MediaProperties.MediaEncodingSubtypes.Bgra8;
// The only depth format we can render is D16.
case MediaFrameSourceKind.Depth:
return String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.D16, StringComparison.OrdinalIgnoreCase) ? subtype : null;
// The only infrared formats we can render are L8 and L16.
case MediaFrameSourceKind.Infrared:
return (String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L8, StringComparison.OrdinalIgnoreCase) ||
String.Equals(subtype, Windows.Media.MediaProperties.MediaEncodingSubtypes.L16, StringComparison.OrdinalIgnoreCase)) ? subtype : null;
// No other source kinds are supported by this class.
default:
return null;
}
}
/// <summary>
/// Converts a frame to a SoftwareBitmap of a valid format to display in an Image control.
/// </summary>
/// <param name="inputFrame">Frame to convert.</param>
public static unsafe SoftwareBitmap ConvertToDisplayableImage(VideoMediaFrame inputFrame)
{
SoftwareBitmap result = null;
using (var inputBitmap = inputFrame?.SoftwareBitmap)
{
if (inputBitmap != null)
{
switch (inputFrame.FrameReference.SourceKind)
{
case MediaFrameSourceKind.Color:
// XAML requires Bgra8 with premultiplied alpha.
// We requested Bgra8 from the MediaFrameReader, so all that's
// left is fixing the alpha channel if necessary.
if (inputBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8)
{
System.Diagnostics.Debug.WriteLine("Color frame in unexpected format.");
}
else if (inputBitmap.BitmapAlphaMode == BitmapAlphaMode.Premultiplied)
{
// Already in the correct format.
result = SoftwareBitmap.Copy(inputBitmap);
}
else
{
// Convert to premultiplied alpha.
result = SoftwareBitmap.Convert(inputBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
break;
case MediaFrameSourceKind.Depth:
// We requested D16 from the MediaFrameReader, so the frame should
// be in Gray16 format.
if (inputBitmap.BitmapPixelFormat == BitmapPixelFormat.Gray16)
{
// Use a special pseudo color to render 16 bits depth frame.
var depthScale = (float)inputFrame.DepthMediaFrame.DepthFormat.DepthScaleInMeters;
var minReliableDepth = inputFrame.DepthMediaFrame.MinReliableDepth;
var maxReliableDepth = inputFrame.DepthMediaFrame.MaxReliableDepth;
result = TransformBitmap(inputBitmap, (w, i, o) => PseudoColorHelper.PseudoColorForDepth(w, i, o, depthScale, minReliableDepth, maxReliableDepth));
}
else
{
System.Diagnostics.Debug.WriteLine("Depth frame in unexpected format.");
}
break;
case MediaFrameSourceKind.Infrared:
// We requested L8 or L16 from the MediaFrameReader, so the frame should
// be in Gray8 or Gray16 format.
switch (inputBitmap.BitmapPixelFormat)
{
case BitmapPixelFormat.Gray16:
// Use pseudo color to render 16 bits frames.
result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor16BitInfrared);
break;
case BitmapPixelFormat.Gray8:
// Use pseudo color to render 8 bits frames.
result = TransformBitmap(inputBitmap, PseudoColorHelper.PseudoColorFor8BitInfrared);
break;
default:
System.Diagnostics.Debug.WriteLine("Infrared frame in unexpected format.");
break;
}
break;
}
}
}
return result;
}
/// <summary>
/// Transform image into Bgra8 image using given transform method.
/// </summary>
/// <param name="softwareBitmap">Input image to transform.</param>
/// <param name="transformScanline">Method to map pixels in a scanline.</param>
private static unsafe SoftwareBitmap TransformBitmap(SoftwareBitmap softwareBitmap, TransformScanline transformScanline)
{
// XAML Image control only supports premultiplied Bgra8 format.
var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
softwareBitmap.PixelWidth, softwareBitmap.PixelHeight, BitmapAlphaMode.Premultiplied);
using (var input = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read))
using (var output = outputBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
// Get stride values to calculate buffer position for a given pixel x and y position.
int inputStride = input.GetPlaneDescription(0).Stride;
int outputStride = output.GetPlaneDescription(0).Stride;
int pixelWidth = softwareBitmap.PixelWidth;
int pixelHeight = softwareBitmap.PixelHeight;
using (var outputReference = output.CreateReference())
using (var inputReference = input.CreateReference())
{
// Get input and output byte access buffers.
byte* inputBytes;
uint inputCapacity;
((IMemoryBufferByteAccess)inputReference).GetBuffer(out inputBytes, out inputCapacity);
byte* outputBytes;
uint outputCapacity;
((IMemoryBufferByteAccess)outputReference).GetBuffer(out outputBytes, out outputCapacity);
// Iterate over all pixels and store converted value.
for (int y = 0; y < pixelHeight; y++)
{
byte* inputRowBytes = inputBytes + y * inputStride;
byte* outputRowBytes = outputBytes + y * outputStride;
transformScanline(pixelWidth, inputRowBytes, outputRowBytes);
}
}
}
return outputBitmap;
}
/// <summary>
/// A helper class to manage look-up-table for pseudo-colors.
/// </summary>
private static class PseudoColorHelper
{
#region Constructor, private members and methods
private const int TableSize = 1024; // Look up table size
private static readonly uint[] PseudoColorTable;
private static readonly uint[] InfraredRampTable;
// Color palette mapping value from 0 to 1 to blue to red colors.
private static readonly Color[] ColorRamp =
{
Color.FromArgb(a:0xFF, r:0x7F, g:0x00, b:0x00),
Color.FromArgb(a:0xFF, r:0xFF, g:0x00, b:0x00),
Color.FromArgb(a:0xFF, r:0xFF, g:0x7F, b:0x00),
Color.FromArgb(a:0xFF, r:0xFF, g:0xFF, b:0x00),
Color.FromArgb(a:0xFF, r:0x7F, g:0xFF, b:0x7F),
Color.FromArgb(a:0xFF, r:0x00, g:0xFF, b:0xFF),
Color.FromArgb(a:0xFF, r:0x00, g:0x7F, b:0xFF),
Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0xFF),
Color.FromArgb(a:0xFF, r:0x00, g:0x00, b:0x7F),
};
static PseudoColorHelper()
{
PseudoColorTable = InitializePseudoColorLut();
InfraredRampTable = InitializeInfraredRampLut();
}
/// <summary>
/// Maps an input infrared value between [0, 1] to corrected value between [0, 1].
/// </summary>
/// <param name="value">Input value between [0, 1].</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // Tell the compiler to inline this method to improve performance
private static uint InfraredColor(float value)
{
int index = (int)(value * TableSize);
index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
return InfraredRampTable[index];
}
/// <summary>
/// Initializes the pseudo-color look up table for infrared pixels
/// </summary>
private static uint[] InitializeInfraredRampLut()
{
uint[] lut = new uint[TableSize];
for (int i = 0; i < TableSize; i++)
{
var value = (float)i / TableSize;
// Adjust to increase color change between lower values in infrared images
var alpha = (float)Math.Pow(1 - value, 12);
lut[i] = ColorRampInterpolation(alpha);
}
return lut;
}
/// <summary>
/// Initializes pseudo-color look up table for depth pixels
/// </summary>
private static uint[] InitializePseudoColorLut()
{
uint[] lut = new uint[TableSize];
for (int i = 0; i < TableSize; i++)
{
lut[i] = ColorRampInterpolation((float)i / TableSize);
}
return lut;
}
/// <summary>
/// Maps a float value to a pseudo-color pixel
/// </summary>
private static uint ColorRampInterpolation(float value)
{
// Map value to surrounding indexes on the color ramp
int rampSteps = ColorRamp.Length - 1;
float scaled = value * rampSteps;
int integer = (int)scaled;
int index =
integer < 0 ? 0 :
integer >= rampSteps - 1 ? rampSteps - 1 :
integer;
Color prev = ColorRamp[index];
Color next = ColorRamp[index + 1];
// Set color based on ratio of closeness between the surrounding colors
uint alpha = (uint)((scaled - integer) * 255);
uint beta = 255 - alpha;
return
((prev.A * beta + next.A * alpha) / 255) << 24 | // Alpha
((prev.R * beta + next.R * alpha) / 255) << 16 | // Red
((prev.G * beta + next.G * alpha) / 255) << 8 | // Green
((prev.B * beta + next.B * alpha) / 255); // Blue
}
/// <summary>
/// Maps a value in [0, 1] to a pseudo RGBA color.
/// </summary>
/// <param name="value">Input value between [0, 1].</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint PseudoColor(float value)
{
int index = (int)(value * TableSize);
index = index < 0 ? 0 : index > TableSize - 1 ? TableSize - 1 : index;
return PseudoColorTable[index];
}
#endregion
/// <summary>
/// Maps each pixel in a scanline from a 16 bit depth value to a pseudo-color pixel.
/// </summary>
/// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
/// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
/// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
/// <param name="depthScale">Physical distance that corresponds to one unit in the input scanline.</param>
/// <param name="minReliableDepth">Shortest distance at which the sensor can provide reliable measurements.</param>
/// <param name="maxReliableDepth">Furthest distance at which the sensor can provide reliable measurements.</param>
public static unsafe void PseudoColorForDepth(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes, float depthScale, float minReliableDepth, float maxReliableDepth)
{
// Visualize space in front of your desktop.
float minInMeters = minReliableDepth * depthScale;
float maxInMeters = maxReliableDepth * depthScale;
float one_min = 1.0f / minInMeters;
float range = 1.0f / maxInMeters - one_min;
ushort* inputRow = (ushort*)inputRowBytes;
uint* outputRow = (uint*)outputRowBytes;
for (int x = 0; x < pixelWidth; x++)
{
var depth = inputRow[x] * depthScale;
if (depth == 0)
{
// Map invalid depth values to transparent pixels.
// This happens when depth information cannot be calculated, e.g. when objects are too close.
outputRow[x] = 0;
}
else
{
var alpha = (1.0f / depth - one_min) / range;
outputRow[x] = PseudoColor(alpha * alpha);
}
}
}
/// <summary>
/// Maps each pixel in a scanline from a 8 bit infrared value to a pseudo-color pixel.
/// </summary>
/// /// <param name="pixelWidth">Width of the input scanline, in pixels.</param>
/// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
/// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
public static unsafe void PseudoColorFor8BitInfrared(
int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
{
byte* inputRow = inputRowBytes;
uint* outputRow = (uint*)outputRowBytes;
for (int x = 0; x < pixelWidth; x++)
{
outputRow[x] = InfraredColor(inputRow[x] / (float)Byte.MaxValue);
}
}
/// <summary>
/// Maps each pixel in a scanline from a 16 bit infrared value to a pseudo-color pixel.
/// </summary>
/// <param name="pixelWidth">Width of the input scanline.</param>
/// <param name="inputRowBytes">Pointer to the start of the input scanline.</param>
/// <param name="outputRowBytes">Pointer to the start of the output scanline.</param>
public static unsafe void PseudoColorFor16BitInfrared(int pixelWidth, byte* inputRowBytes, byte* outputRowBytes)
{
ushort* inputRow = (ushort*)inputRowBytes;
uint* outputRow = (uint*)outputRowBytes;
for (int x = 0; x < pixelWidth; x++)
{
outputRow[x] = InfraredColor(inputRow[x] / (float)UInt16.MaxValue);
}
}
}
// Displays the provided softwareBitmap in a XAML image control.
public void PresentSoftwareBitmap(SoftwareBitmap softwareBitmap)
{
if (softwareBitmap != null)
{
// Swap the processed frame to _backBuffer and trigger UI thread to render it
softwareBitmap = Interlocked.Exchange(ref _backBuffer, softwareBitmap);
// UI thread always reset _backBuffer before using it. Unused bitmap should be disposed.
softwareBitmap?.Dispose();
// Changes to xaml ImageElement must happen in UI thread through Dispatcher
var task = _imageElement.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
// Don't let two copies of this task run at the same time.
if (_taskRunning)
{
return;
}
_taskRunning = true;
// Keep draining frames from the backbuffer until the backbuffer is empty.
SoftwareBitmap latestBitmap;
while ((latestBitmap = Interlocked.Exchange(ref _backBuffer, null)) != null)
{
var imageSource = (SoftwareBitmapSource)_imageElement.Source;
await imageSource.SetBitmapAsync(latestBitmap);
latestBitmap.Dispose();
}
_taskRunning = false;
});
}
}
}
This is output image, that I've got after conversion issues and grayscale process:
Output image
As for VS version - Visual Studio Enterprise 2017, Version 15.6.1 (to be precise). Once more, thanks in advance for help.
Before anybody points it out I know that a there is a question with the same title that has already been asked here it just doesn't answer my issue I think.
Working in .NET 3.5 As in that question I am making an area selection component to select an area on a picture. The picture is displayed using a custom control in which the picture is drawn during OnPaint.
I have the following code for my selection rectangle:
internal class AreaSelection : Control
{
private Rectangle selection
{
get { return new Rectangle(Point.Empty, Size.Subtract(this.Size, new Size(1, 1))); }
}
private Size mouseStartLocation;
public AreaSelection()
{
this.Size = new Size(150, 150);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.FromArgb(70, 200, 200, 200);
}
protected override void OnMouseEnter(EventArgs e)
{
this.Cursor = Cursors.SizeAll;
base.OnMouseEnter(e);
}
protected override void OnMouseDown(MouseEventArgs e)
{
this.mouseStartLocation = new Size(e.Location);
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Point offset = e.Location - this.mouseStartLocation;
this.Left += offset.X;
this.Top += offset.Y;
}
base.OnMouseMove(e);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawRectangle(new Pen(Color.Black) { DashStyle = DashStyle.Dash }, this.selection);
Debug.WriteLine("Selection redrawn");
}
}
Which gives me a nice semi-transparent rectangle which I can drag around. The problem I have is that whilst dragging the underlying image which shows through the rectangle gets lags behind the position of the rectangle.
This gets more noticeable the faster I move the rectangle. When I stop moving it the image catches up and everything aligns perfectly again.
I assume that there is something wrong with the way the rectangle draws, but I really can't figure out what it is...
Any help would be much appreciated.
EDIT:
I have noticed that the viewer gets redrawn twice as often as the selection area when I drag the selection area. Could this be the cause of the problem?
EDIT 2:
Here is the code for the viewer in case it is relevant:
public enum ImageViewerViewMode
{
Normal,
PrintSelection,
PrintPreview
}
public enum ImageViewerZoomMode
{
None,
OnClick,
Lens
}
public partial class ImageViewer : UserControl
{
/// <summary>
/// The current zoom factor. Note: Use SetZoom() to set the value.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public float ZoomFactor
{
get { return this.zoomFactor; }
private set
{
this.zoomFactor = value;
}
}
/// <summary>
/// The maximum zoom factor to use
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public float MaximumZoomFactor
{
get
{
return this.maximumZoomFactor;
}
set
{
this.maximumZoomFactor = value;
this.SetZoomFactorLimits();
}
}
/// <summary>
/// The minimum zoom factort to use
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public float MinimumZoomFactor
{
get
{
return this.minimumZoomFactor;
}
set
{
this.minimumZoomFactor = value;
this.SetZoomFactorLimits();
}
}
/// <summary>
/// The multiplying factor to apply to each ZoomIn/ZoomOut command
/// </summary>
[Category("Behavior")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(2F)]
public float ZoomStep { get; set; }
/// <summary>
/// The image currently displayed by the control
/// </summary>
[Category("Data")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Image
{
get { return this.image; }
set
{
this.image = value;
this.ZoomExtents();
this.minimumZoomFactor = this.zoomFactor / 10;
this.MaximumZoomFactor = this.zoomFactor * 10;
}
}
public ImageViewerViewMode ViewMode { get; set; }
public ImageViewerZoomMode ZoomMode { get; set; }
private ImageViewerLens Lens { get; set; }
private float zoomFactor;
private float minimumZoomFactor;
private float maximumZoomFactor;
private bool panning;
private Point imageLocation;
private Point imageTranslation;
private Image image;
private AreaSelection areaSelection;
/// <summary>
/// Class constructor
/// </summary>
public ImageViewer()
{
this.DoubleBuffered = true;
this.MinimumZoomFactor = 0.1F;
this.MaximumZoomFactor = 10F;
this.ZoomStep = 2F;
this.UseScannerUI = true;
this.Lens = new ImageViewerLens();
this.ViewMode = ImageViewerViewMode.PrintSelection;
this.areaSelection = new AreaSelection();
this.Controls.Add(this.areaSelection);
// TWAIN
// Initialise twain
this.twain = new Twain(new WinFormsWindowMessageHook(this));
// Try to set the last used default scanner
if (this.AvailableScanners.Any())
{
this.twain.TransferImage += twain_TransferImage;
this.twain.ScanningComplete += twain_ScanningComplete;
if (!this.SetScanner(this.defaultScanner))
this.SetScanner(this.AvailableScanners.First());
}
}
/// <summary>
/// Saves the currently loaded image under the specified filename, in the specified format at the specified quality
/// </summary>
/// <param name="FileName">The file name (full file path) under which to save the file. File type extension is not required.</param>
/// <param name="Format">The file format under which to save the file</param>
/// <param name="Quality">The quality in percent of the image to save. This is optional and may or may not be used have an effect depending on the chosen file type. Default is maximum quality.</param>
public void SaveImage(string FileName, GraphicFormats Format, uint Quality = 100)
{
ImageCodecInfo encoder;
EncoderParameters encoderParameters;
if (FileName.IsNullOrEmpty())
throw new ArgumentNullException(FileName);
else
{
string extension = Path.GetExtension(FileName);
if (!string.IsNullOrEmpty(extension))
FileName = FileName.Replace(extension, string.Empty);
FileName += "." + Format.ToString();
}
Quality = Math.Min(Math.Max(1, Quality), 100);
if (!TryGetEncoder(Format, out encoder))
return;
encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (int)Quality);
this.Image.Save(FileName, encoder, encoderParameters);
}
/// <summary>
/// Tries to retrieve the appropriate encoder for the chose image format.
/// </summary>
/// <param name="Format">The image format for which to attempt retrieving the encoder</param>
/// <param name="Encoder">The encoder object in which to store the encoder if found</param>
/// <returns>True if the encoder was found, else false</returns>
private bool TryGetEncoder(GraphicFormats Format, out ImageCodecInfo Encoder)
{
ImageCodecInfo[] codecs;
codecs = ImageCodecInfo.GetImageEncoders();
Encoder = codecs.First(c => c.FormatDescription.Equals(Format.ToString(), StringComparison.CurrentCultureIgnoreCase));
return Encoder != null;
}
/// <summary>
/// Set the zoom level to view the entire image in the control
/// </summary>
public void ZoomExtents()
{
if (this.Image == null)
return;
this.ZoomFactor = (float)Math.Min((double)this.Width / this.Image.Width, (double)this.Height / this.Image.Height);
this.LimitBasePoint(imageLocation.X, imageLocation.Y);
this.Invalidate();
}
/// <summary>
/// Multiply the zoom
/// </summary>
/// <param name="NewZoomFactor">The zoom factor to set for the image</param>
public void SetZoom(float NewZoomFactor)
{
this.SetZoom(NewZoomFactor, Point.Empty);
}
/// <summary>
/// Multiply the zoom
/// </summary>
/// <param name="NewZoomFactor">The zoom factor to set for the image</param>
/// <param name="ZoomLocation">The point in which to zoom in</param>
public void SetZoom(float NewZoomFactor, Point ZoomLocation)
{
int x;
int y;
float multiplier;
multiplier = NewZoomFactor / this.ZoomFactor;
x = (int)((ZoomLocation.IsEmpty ? this.Width / 2 : ZoomLocation.X - imageLocation.X) / ZoomFactor);
y = (int)((ZoomLocation.IsEmpty ? this.Height / 2 : ZoomLocation.Y - imageLocation.Y) / ZoomFactor);
if ((multiplier < 1 && this.ZoomFactor > this.MinimumZoomFactor) || (multiplier > 1 && this.ZoomFactor < this.MaximumZoomFactor))
ZoomFactor *= multiplier;
else
return;
LimitBasePoint((int)(this.Width / 2 - x * ZoomFactor), (int)(this.Height / 2 - y * ZoomFactor));
this.Invalidate();
}
/// <summary>
/// Determines the base point for positioning the image
/// </summary>
/// <param name="x">The x coordinate based on which to determine the positioning</param>
/// <param name="y">The y coordinate based on which to determine the positioning</param>
private void LimitBasePoint(int x, int y)
{
int width;
int height;
if (this.Image == null)
return;
width = this.Width - (int)(Image.Width * ZoomFactor);
height = this.Height - (int)(Image.Height * ZoomFactor);
x = width < 0 ? Math.Max(Math.Min(x, 0), width) : width / 2;
y = height < 0 ? Math.Max(Math.Min(y, 0), height) : height / 2;
imageLocation = new Point(x, y);
}
/// <summary>
/// Verify that the maximum and minimum zoom are correctly set
/// </summary>
private void SetZoomFactorLimits()
{
float maximum = this.MaximumZoomFactor;
float minimum = this.minimumZoomFactor;
this.maximumZoomFactor = Math.Max(maximum, minimum);
this.minimumZoomFactor = Math.Min(maximum, minimum);
}
/// <summary>
/// Mouse button down event
/// </summary>
protected override void OnMouseDown(MouseEventArgs e)
{
switch (this.ZoomMode)
{
case ImageViewerZoomMode.OnClick:
switch (e.Button)
{
case MouseButtons.Left:
this.SetZoom(this.ZoomFactor * this.ZoomStep, e.Location);
break;
case MouseButtons.Middle:
this.panning = true;
this.Cursor = Cursors.NoMove2D;
this.imageTranslation = e.Location;
break;
case MouseButtons.Right:
this.SetZoom(this.ZoomFactor / this.ZoomStep, e.Location);
break;
}
break;
case ImageViewerZoomMode.Lens:
if (e.Button == MouseButtons.Left)
{
this.Cursor = Cursors.Cross;
this.Lens.Location = e.Location;
this.Lens.Visible = true;
}
else
{
this.Cursor = Cursors.Default;
this.Lens.Visible = false;
}
this.Invalidate();
break;
}
base.OnMouseDown(e);
}
/// <summary>
/// Mouse button up event
/// </summary>
protected override void OnMouseUp(MouseEventArgs e)
{
switch (this.ZoomMode)
{
case ImageViewerZoomMode.OnClick:
if (e.Button == MouseButtons.Middle)
{
panning = false;
this.Cursor = Cursors.Default;
}
break;
case ImageViewerZoomMode.Lens:
break;
}
base.OnMouseUp(e);
}
/// <summary>
/// Mouse move event
/// </summary>
protected override void OnMouseMove(MouseEventArgs e)
{
switch (this.ViewMode)
{
case ImageViewerViewMode.Normal:
switch (this.ZoomMode)
{
case ImageViewerZoomMode.OnClick:
if (panning)
{
LimitBasePoint(imageLocation.X + e.X - this.imageTranslation.X, imageLocation.Y + e.Y - this.imageTranslation.Y);
this.imageTranslation = e.Location;
}
break;
case ImageViewerZoomMode.Lens:
if (this.Lens.Visible)
{
this.Lens.Location = e.Location;
}
break;
}
break;
case ImageViewerViewMode.PrintSelection:
break;
case ImageViewerViewMode.PrintPreview:
break;
}
base.OnMouseMove(e);
}
/// <summary>
/// Resize event
/// </summary>
protected override void OnResize(EventArgs e)
{
LimitBasePoint(imageLocation.X, imageLocation.Y);
this.Invalidate();
base.OnResize(e);
}
/// <summary>
/// Paint event
/// </summary>
protected override void OnPaint(PaintEventArgs pe)
{
Rectangle src;
Rectangle dst;
pe.Graphics.Clear(this.BackColor);
if (this.Image != null)
{
switch (this.ViewMode)
{
case ImageViewerViewMode.Normal:
src = new Rectangle(Point.Empty, new Size(Image.Width, Image.Height));
dst = new Rectangle(this.imageLocation, new Size((int)(this.Image.Width * this.ZoomFactor), (int)(this.Image.Height * this.ZoomFactor)));
pe.Graphics.DrawImage(this.Image, dst, src, GraphicsUnit.Pixel);
this.Lens.Draw(pe.Graphics, this.Image, this.ZoomFactor, this.imageLocation);
break;
case ImageViewerViewMode.PrintSelection:
src = new Rectangle(Point.Empty, new Size(Image.Width, Image.Height));
dst = new Rectangle(this.imageLocation, new Size((int)(this.Image.Width * this.ZoomFactor), (int)(this.Image.Height * this.ZoomFactor)));
pe.Graphics.DrawImage(this.Image, dst, src, GraphicsUnit.Pixel);
break;
case ImageViewerViewMode.PrintPreview:
break;
}
}
//Debug.WriteLine("Viewer redrawn " + DateTime.Now);
base.OnPaint(pe);
}
}
EDIT 3:
Experience further graphics-related trouble when setting the height to something large. For example, if in the AreaSelection constructor I set the height to 500, dragging the control really screws up the painting.
whilst dragging the underlying image which shows through the rectangle gets lags behind
This is rather inevitable, updating the rectangle also redraws the image. And if that's expensive, say more than 30 milliseconds, then this can become noticeable to the eye.
That's a lot of milliseconds for something as simple as an image on a modern machine. The only way it can take that long is when the image is large and needs to be rescaled to fit the picturebox. And the pixel format is incompatible with the pixel format of the video adapter so that every single one of them has to be translated from the image pixel format to the video adapter's pixel format. That can indeed add up to multiple milliseconds.
You'll need to help to avoid PictureBox from having to burn that many cpu cycles every time the image gets painted. Do so by prescaling the image, turning it from a huge bitmap into one that better fits the control. And by altering the pixel format, the 32bppPArgb format is best by a long shot since that matches the pixel format of the vast majority of all video adapters. It draws ten times faster than all the other formats. You'll find boilerplate code to make this conversion in this answer.
I want to compare an image of a document with a logo type or another picture to see if the logo is in the document.
The application to do this is going to use ASP.NET MVC 3 and C#.
After more searching i finded a solution with a extension of Bitmap that using AForge:
public static class BitmapExtensions
{
/// <summary>
/// See if bmp is contained in template with a small margin of error.
/// </summary>
/// <param name="template">The Bitmap that might contain.</param>
/// <param name="bmp">The Bitmap that might be contained in.</param>
/// <returns>You guess!</returns>
public static bool Contains(this Bitmap template, Bitmap bmp)
{
const Int32 divisor = 4;
const Int32 epsilon = 10;
ExhaustiveTemplateMatching etm = new ExhaustiveTemplateMatching(0.9f);
TemplateMatch[] tm = etm.ProcessImage(
new ResizeNearestNeighbor(template.Width / divisor, template.Height / divisor).Apply(template),
new ResizeNearestNeighbor(bmp.Width / divisor, bmp.Height / divisor).Apply(bmp)
);
if (tm.Length == 1)
{
Rectangle tempRect = tm[0].Rectangle;
if (Math.Abs(bmp.Width / divisor - tempRect.Width) < epsilon
&&
Math.Abs(bmp.Height / divisor - tempRect.Height) < epsilon)
{
return true;
}
}
return false;
}
}
I think that you want to use template matching functionalities. I would suggest using opencv for that. This is similar to this question
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to catch the event when spectrum of an audio reached a specific height, like triggered event made by a loud sound?
I want to detect for a example, a beat or a loud sound from an audio file. All the modules are working except that I don't know how to code the detection. Some says, I'll iterate the data from the spectrum and record the parts where there is a load sound or a beat.
I'll show you the code of my FFT, I got this from NAudio. Can you show me if I can detect an event here.
For example:
if (waveLeft[] > amplitudeThreshold || waveleft[] < -amplitudeThreshold)
listbox.items.add(ActiveStream.CurrentTime)
That's the idea.
So here's the code.
public SampleAggregator(int bufferSize)
{
channelData = new Complex[bufferSize];
}
public void Clear()
{
volumeLeftMaxValue = float.MinValue;
volumeRightMaxValue = float.MinValue;
volumeLeftMinValue = float.MaxValue;
volumeRightMinValue = float.MaxValue;
channelDataPosition = 0;
}
/// <summary>
/// Add a sample value to the aggregator.
/// </summary>
/// <param name="value">The value of the sample.</param>
public void Add(float leftValue, float rightValue)
{
if (channelDataPosition == 0)
{
volumeLeftMaxValue = float.MinValue;
volumeRightMaxValue = float.MinValue;
volumeLeftMinValue = float.MaxValue;
volumeRightMinValue = float.MaxValue;
}
// Make stored channel data stereo by averaging left and right values.
channelData[channelDataPosition].X = (leftValue + rightValue) / 2.0f;
channelData[channelDataPosition].Y = 0;
channelDataPosition++;
volumeLeftMaxValue = Math.Max(volumeLeftMaxValue, leftValue);
volumeLeftMinValue = Math.Min(volumeLeftMinValue, leftValue);
volumeRightMaxValue = Math.Max(volumeRightMaxValue, rightValue);
volumeRightMinValue = Math.Min(volumeRightMinValue, rightValue);
if (channelDataPosition >= channelData.Length)
{
channelDataPosition = 0;
}
}
/// <summary>
/// Performs an FFT calculation on the channel data upon request.
/// </summary>
/// <param name="fftBuffer">A buffer where the FFT data will be stored.</param>
public void GetFFTResults(float[] fftBuffer)
{
Complex[] channelDataClone = new Complex[4096];
channelData.CopyTo(channelDataClone, 0);
// 4096 = 2^12
FastFourierTransform.FFT(true, 12, channelDataClone);
for (int i = 0; i < channelDataClone.Length / 2; i++)
{
// Calculate actual intensities for the FFT results.
fftBuffer[i] = (float)Math.Sqrt(channelDataClone[i].X * channelDataClone[i].X + channelDataClone[i].Y * channelDataClone[i].Y);
}
}
Thank you for the help. :)
The basic idea:
You segment your stream of wave-form samples into timeslices and convert each slice to the F-domain with your FFT.
Then you have a stream of FFT frames in which you check each channel (bin) for peaks. You'll need a lastValue and maybe even a little state-machine per bin.
FFT width and peaklevel to be configured.
I'm using the MonthCalendar control and want to programmatically select a date range. When I do so the control doesn't paint properly if Application.EnableVisualStyles() has been called. This is a known issue according to MSDN.
Using the MonthCalendar with visual
styles enabled will cause a selection
range for the MonthCalendar control to
not paint correctly
(from: http://msdn.microsoft.com/en-us/library/system.windows.forms.monthcalendar.aspx)
Is there really no fix for this other than not calling EnableVisualStyles? This seems to make that particular control entirely useless for a range of applications and a rather glaring oversight from my perspective.
While looking for a solution to the same problem, I first encountered this question here, but later I discovered a blog entry by Nicke Andersson. which I found very helpful.
Here is what I made of Nicke's example:
public class MonthCalendarEx : System.Windows.Forms.MonthCalendar
{
private int _offsetX;
private int _offsetY;
private int _dayBoxWidth;
private int _dayBoxHeight;
private bool _repaintSelectedDays = false;
public MonthCalendarEx() : base()
{
OnSizeChanged(null, null);
this.SizeChanged += OnSizeChanged;
this.DateChanged += OnSelectionChanged;
this.DateSelected += OnSelectionChanged;
}
protected static int WM_PAINT = 0x000F;
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT)
{
Graphics graphics = Graphics.FromHwnd(this.Handle);
PaintEventArgs pe = new PaintEventArgs(
graphics, new Rectangle(0, 0, this.Width, this.Height));
OnPaint(pe);
}
}
private void OnSelectionChanged(object sender, EventArgs e)
{
_repaintSelectedDays = true;
}
private void OnSizeChanged(object sender, EventArgs e)
{
_offsetX = 0;
_offsetY = 0;
// determine Y offset of days area
while (
HitTest(Width / 2, _offsetY).HitArea != HitArea.PrevMonthDate &&
HitTest(Width / 2, _offsetY).HitArea != HitArea.Date)
{
_offsetY++;
}
// determine X offset of days area
while (HitTest(_offsetX, Height / 2).HitArea != HitArea.Date)
{
_offsetX++;
}
// determine width of a single day box
_dayBoxWidth = 0;
DateTime dt1 = HitTest(Width / 2, _offsetY).Time;
while (HitTest(Width / 2, _offsetY + _dayBoxHeight).Time == dt1)
{
_dayBoxHeight++;
}
// determine height of a single day box
_dayBoxWidth = 0;
DateTime dt2 = HitTest(_offsetX, Height / 2).Time;
while (HitTest(_offsetX + _dayBoxWidth, Height / 2).Time == dt2)
{
_dayBoxWidth++;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (_repaintSelectedDays)
{
Graphics graphics = e.Graphics;
SelectionRange calendarRange = GetDisplayRange(false);
Rectangle currentDayFrame = new Rectangle(
-1, -1, _dayBoxWidth, _dayBoxHeight);
DateTime current = SelectionStart;
while (current <= SelectionEnd)
{
Rectangle currentDayRectangle;
using (Brush selectionBrush = new SolidBrush(
Color.FromArgb(
255, System.Drawing.SystemColors.ActiveCaption)))
{
TimeSpan span = current.Subtract(calendarRange.Start);
int row = span.Days / 7;
int col = span.Days % 7;
currentDayRectangle = new Rectangle(
_offsetX + (col + (ShowWeekNumbers ? 1 : 0)) * _dayBoxWidth,
_offsetY + row * _dayBoxHeight,
_dayBoxWidth,
_dayBoxHeight);
graphics.FillRectangle(selectionBrush, currentDayRectangle);
}
TextRenderer.DrawText(
graphics,
current.Day.ToString(),
Font,
currentDayRectangle,
System.Drawing.SystemColors.ActiveCaptionText,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
if (current == this.TodayDate)
{
currentDayFrame = currentDayRectangle;
}
current = current.AddDays(1);
}
if (currentDayFrame.X > 0)
{
graphics.DrawRectangle(new Pen(
new SolidBrush(Color.Red)), currentDayFrame);
}
_repaintSelectedDays = false;
}
}
}
I found a small problem in Mark Cranness's code above: On XP systems that have visual styles disabled entirely, Application.RenderWithVisualStyles is then set to False even when Application.EnableVisualStyles() is called.
So the custom paint code does not run at all in that case. To fix it, I changed the first line of the FixVisualStylesMonthCalendar constructor to
if (Application.VisualStyleState != System.Windows.Forms.VisualStyles.VisualStyleState.NoneEnabled &&
Environment.OSVersion.Version < new Version(6, 0))
Entire code is at the bottom of this answer.
I could not find any way to comment on the answer itself. Credits for below code go to the original author - (If he or anyone can verify this answer and update it I would be happy to remove this one)
/// <summary>
/// When Visual Styles are enabled on Windows XP, the MonthCalendar.SelectionRange
/// does not paint correctly when more than one date is selected.
/// See: http://msdn.microsoft.com/en-us/library/5d1acks5(VS.80).aspx
/// "Additionally, if you enable visual styles on some controls, the control might display incorrectly
/// in certain situations. These include the MonthCalendar control with a selection range set...
/// This class fixes that problem.
/// </summary>
/// <remarks>Author: Mark Cranness - PatronBase Limited.</remarks>
public class FixVisualStylesMonthCalendar : System.Windows.Forms.MonthCalendar
{
/// <summary>
/// The width of a single cell (date) in the calendar.
/// </summary>
private int dayCellWidth;
/// <summary>
/// The height of a single cell (date) in the calendar.
/// </summary>
private int dayCellHeight;
/// <summary>
/// The calendar first day of the week actually used.
/// </summary>
private DayOfWeek calendarFirstDayOfWeek;
/// <summary>
/// Only repaint when VisualStyles enabled on Windows XP.
/// </summary>
private bool repaintSelectionRange = false;
/// <summary>
/// A MonthCalendar class that fixes SelectionRange painting problems
/// on Windows XP when Visual Styles is enabled.
/// </summary>
public FixVisualStylesMonthCalendar()
{
if (Application.VisualStyleState != System.Windows.Forms.VisualStyles.VisualStyleState.NoneEnabled && //Application.RenderWithVisualStyles &&
Environment.OSVersion.Version < new Version(6, 0))
{
// If Visual Styles are enabled, and XP, then fix-up the painting of SelectionRange
this.repaintSelectionRange = true;
this.OnSizeChanged(this, EventArgs.Empty);
this.SizeChanged += new EventHandler(this.OnSizeChanged);
}
}
/// <summary>
/// The WM_PAINT message is sent to make a request to paint a portion of a window.
/// </summary>
public const int WM_PAINT = 0x000F;
/// <summary>
/// Override WM_PAINT to repaint the selection range.
/// </summary>
[System.Diagnostics.DebuggerStepThroughAttribute()]
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT
&& !this.DesignMode
&& this.repaintSelectionRange)
{
// MonthCalendar is ControlStyles.UserPaint=false => Paint event is not raised
this.RepaintSelectionRange(ref m);
}
}
/// <summary>
/// Repaint the SelectionRange.
/// </summary>
private void RepaintSelectionRange(ref Message m)
{
using (Graphics graphics = this.CreateGraphics())
using (Brush backBrush
= new SolidBrush(graphics.GetNearestColor(this.BackColor)))
using (Brush selectionBrush
= new SolidBrush(graphics.GetNearestColor(SystemColors.ActiveCaption)))
{
Rectangle todayFrame = Rectangle.Empty;
// For each day in SelectionRange...
for (DateTime selectionDate = this.SelectionStart;
selectionDate <= this.SelectionEnd;
selectionDate = selectionDate.AddDays(1))
{
Rectangle selectionDayRectangle = this.GetSelectionDayRectangle(selectionDate);
if (selectionDayRectangle.IsEmpty) continue;
if (selectionDate.Date == this.TodayDate)
{
todayFrame = selectionDayRectangle;
}
// Paint as 'selected' a little smaller than the whole rectangle
Rectangle highlightRectangle = Rectangle.Inflate(selectionDayRectangle, 0, -2);
if (selectionDate == this.SelectionStart)
{
highlightRectangle.X += 2;
highlightRectangle.Width -= 2;
}
if (selectionDate == this.SelectionEnd)
{
highlightRectangle.Width -= 2;
}
// Paint background, selection and day-of-month text
graphics.FillRectangle(backBrush, selectionDayRectangle);
graphics.FillRectangle(selectionBrush, highlightRectangle);
TextRenderer.DrawText(
graphics,
selectionDate.Day.ToString(),
this.Font,
selectionDayRectangle,
SystemColors.ActiveCaptionText,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
if (this.ShowTodayCircle && !todayFrame.IsEmpty)
{
// Redraw the ShowTodayCircle (square) that we painted over above
using (Pen redPen = new Pen(Color.Red))
{
todayFrame.Width--;
todayFrame.Height--;
graphics.DrawRectangle(redPen, todayFrame);
}
}
}
}
/// <summary>
/// When displayed dates changed, clear the cached month locations.
/// </summary>
private SelectionRange previousDisplayedDates = new SelectionRange();
/// <summary>
/// Gets a graphics Rectangle for the area corresponding to a single date on the calendar.
/// </summary>
private Rectangle GetSelectionDayRectangle(DateTime selectionDateTime)
{
// Handle the leading and trailing dates from the previous and next months
SelectionRange allDisplayedDates = this.GetDisplayRange(false);
SelectionRange fullMonthDates = this.GetDisplayRange(true);
int adjust1Week;
DateTime selectionDate = selectionDateTime.Date;
if (selectionDate < allDisplayedDates.Start
|| selectionDate > allDisplayedDates.End)
{
// Selection Date is not displayed on calendar
return Rectangle.Empty;
}
else if (selectionDate < fullMonthDates.Start)
{
// Selection Date is trailing from the previous partial month
selectionDate = selectionDate.AddDays(7);
adjust1Week = -1;
}
else if (selectionDate > fullMonthDates.End)
{
// Selection Date is leading from the next partial month
selectionDate = selectionDate.AddDays(-14);
adjust1Week = +2;
}
else
{
// A mainline date
adjust1Week = 0;
}
// Discard cached month locations when calendar moves
if (this.previousDisplayedDates.Start != allDisplayedDates.Start
|| this.previousDisplayedDates.End != allDisplayedDates.End)
{
this.DiscardCachedMonthDateAreaLocations();
this.previousDisplayedDates.Start = allDisplayedDates.Start;
this.previousDisplayedDates.End = allDisplayedDates.End;
}
Point monthDateAreaLocation = this.GetMonthDateAreaLocation(selectionDate);
if (monthDateAreaLocation.IsEmpty) return Rectangle.Empty;
DayOfWeek monthFirstDayOfWeek = (new DateTime(selectionDate.Year, selectionDate.Month, 1)).DayOfWeek;
int dayOfWeekAdjust = (int)monthFirstDayOfWeek - (int)this.calendarFirstDayOfWeek;
if (dayOfWeekAdjust < 0) dayOfWeekAdjust += 7;
int row = (selectionDate.Day - 1 + dayOfWeekAdjust) / 7;
int col = (selectionDate.Day - 1 + dayOfWeekAdjust) % 7;
row += adjust1Week;
return new Rectangle(
monthDateAreaLocation.X + col * this.dayCellWidth,
monthDateAreaLocation.Y + row * this.dayCellHeight,
this.dayCellWidth,
this.dayCellHeight);
}
/// <summary>
/// Cached calendar location from the last lookup.
/// </summary>
private Point[] cachedMonthDateAreaLocation = new Point[13];
/// <summary>
/// Discard the cached month locations when calendar moves.
/// </summary>
private void DiscardCachedMonthDateAreaLocations()
{
for (int i = 0; i < 13; i++) this.cachedMonthDateAreaLocation[i] = Point.Empty;
}
/// <summary>
/// Gets the graphics location (x,y point) of the top left of the
/// calendar date area for the month containing the specified date.
/// </summary>
private Point GetMonthDateAreaLocation(DateTime selectionDate)
{
Point monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month];
HitTestInfo hitInfo;
if (!monthDateAreaLocation.IsEmpty
&& (hitInfo = this.HitTest(monthDateAreaLocation.X, monthDateAreaLocation.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month)
{
// Use previously cached lookup
return monthDateAreaLocation;
}
else
{
// Assume the worst (Error: empty)
monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month] = Point.Empty;
Point monthDataAreaPoint = this.GetMonthDateAreaMiddle(selectionDate);
if (monthDataAreaPoint.IsEmpty) return Point.Empty;
// Move left from the middle to find the left edge of the Date area
monthDateAreaLocation.X = monthDataAreaPoint.X--;
HitTestInfo hitInfo1, hitInfo2;
while ((hitInfo1 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y))
.HitArea == HitArea.Date
&& hitInfo1.Time.Month == selectionDate.Month
|| (hitInfo2 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo2.Time.Month == selectionDate.Month)
{
monthDateAreaLocation.X = monthDataAreaPoint.X--;
if (monthDateAreaLocation.X < 0) return Point.Empty; // Error: bail
}
// Move up from the last column to find the top edge of the Date area
int monthLastDayOfWeekX = monthDateAreaLocation.X + (this.dayCellWidth * 7 * 13) / 14;
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
while (this.HitTest(monthLastDayOfWeekX, monthDataAreaPoint.Y).HitArea == HitArea.Date)
{
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
if (monthDateAreaLocation.Y < 0) return Point.Empty; // Error: bail
}
// Got it
this.cachedMonthDateAreaLocation[selectionDate.Month] = monthDateAreaLocation;
return monthDateAreaLocation;
}
}
/// <summary>
/// Paranoid fudge/wobble of the GetMonthDateAreaMiddle in case
/// our first estimate to hit the month misses.
/// (Needed? perhaps not.)
/// </summary>
private static Point[] searchSpiral = {
new Point( 0, 0),
new Point(-1,+1), new Point(+1,+1), new Point(+1,-1), new Point(-1,-1),
new Point(-2,+2), new Point(+2,+2), new Point(+2,-2), new Point(-2,-2)
};
/// <summary>
/// Gets a point somewhere inside the calendar date area of
/// the month containing the given selection date.
/// </summary>
/// <remarks>The point returned will be HitArea.Date, and match the year and
/// month of the selection date; otherwise it will be Point.Empty.</remarks>
private Point GetMonthDateAreaMiddle(DateTime selectionDate)
{
// Iterate over all displayed months, and a search spiral (needed? perhaps not)
for (int dimX = 1; dimX <= this.CalendarDimensions.Width; dimX++)
{
for (int dimY = 1; dimY <= this.CalendarDimensions.Height; dimY++)
{
foreach (Point search in searchSpiral)
{
Point monthDateAreaMiddle = new Point(
((dimX - 1) * 2 + 1) * this.Width / (2 * this.CalendarDimensions.Width)
+ this.dayCellWidth * search.X,
((dimY - 1) * 2 + 1) * this.Height / (2 * this.CalendarDimensions.Height)
+ this.dayCellHeight * search.Y);
HitTestInfo hitInfo = this.HitTest(monthDateAreaMiddle);
if (hitInfo.HitArea == HitArea.Date)
{
// Got the Date Area of the month
if (hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month)
{
// For the correct month
return monthDateAreaMiddle;
}
else
{
// Keep looking in the other months
break;
}
}
}
}
}
return Point.Empty; // Error: not found
}
/// <summary>
/// When this MonthCalendar is resized, recalculate the size of a day cell.
/// </summary>
private void OnSizeChanged(object sender, EventArgs e)
{
// Discard previous cached Month Area Location
DiscardCachedMonthDateAreaLocations();
this.dayCellWidth = this.dayCellHeight = 0;
// Without this, the repaint sometimes does not happen...
this.Invalidate();
// Determine Y offset of days area
int middle = this.Width / (2 * this.CalendarDimensions.Width);
int dateAreaTop = 0;
while (this.HitTest(middle, dateAreaTop).HitArea != HitArea.PrevMonthDate
&& this.HitTest(middle, dateAreaTop).HitArea != HitArea.Date)
{
dateAreaTop++;
if (dateAreaTop > this.ClientSize.Height) return; // Error: bail
}
// Determine height of a single day box
int dayCellHeight = 1;
DateTime dayCellTime = this.HitTest(middle, dateAreaTop).Time;
while (this.HitTest(middle, dateAreaTop + dayCellHeight).Time == dayCellTime)
{
dayCellHeight++;
}
// Determine X offset of days area
middle = this.Height / (2 * this.CalendarDimensions.Height);
int dateAreaLeft = 0;
while (this.HitTest(dateAreaLeft, middle).HitArea != HitArea.Date)
{
dateAreaLeft++;
if (dateAreaLeft > this.ClientSize.Width) return; // Error: bail
}
// Determine width of a single day box
int dayCellWidth = 1;
dayCellTime = this.HitTest(dateAreaLeft, middle).Time;
while (this.HitTest(dateAreaLeft + dayCellWidth, middle).Time == dayCellTime)
{
dayCellWidth++;
}
// Record day box size and actual first day of the month used
this.calendarFirstDayOfWeek = dayCellTime.DayOfWeek;
this.dayCellWidth = dayCellWidth;
this.dayCellHeight = dayCellHeight;
}
}
Here is a version that does work when more than one month is displayed (CalendarDimensions != (1,1)), and fixes some other problems also:
/// <summary>
/// When Visual Styles are enabled on Windows XP, the MonthCalendar.SelectionRange
/// does not paint correctly when more than one date is selected.
/// See: http://msdn.microsoft.com/en-us/library/5d1acks5(VS.80).aspx
/// "Additionally, if you enable visual styles on some controls, the control might display incorrectly
/// in certain situations. These include the MonthCalendar control with a selection range set...
/// This class fixes that problem.
/// </summary>
/// <remarks>Author: Mark Cranness</remarks>
public class FixVisualStylesMonthCalendar : System.Windows.Forms.MonthCalendar {
/// <summary>
/// The width of a single cell (date) in the calendar.
/// </summary>
private int dayCellWidth;
/// <summary>
/// The height of a single cell (date) in the calendar.
/// </summary>
private int dayCellHeight;
/// <summary>
/// The calendar first day of the week actually used.
/// </summary>
private DayOfWeek calendarFirstDayOfWeek;
/// <summary>
/// Only repaint when VisualStyles enabled on Windows XP.
/// </summary>
private bool repaintSelectionRange = false;
/// <summary>
/// A MonthCalendar class that fixes SelectionRange painting problems
/// on Windows XP when Visual Styles is enabled.
/// </summary>
public FixVisualStylesMonthCalendar() {
if (Application.RenderWithVisualStyles
&& Environment.OSVersion.Version < new Version(6, 0)) {
// If Visual Styles are enabled, and XP, then fix-up the painting of SelectionRange
this.repaintSelectionRange = true;
this.OnSizeChanged(this, EventArgs.Empty);
this.SizeChanged += new EventHandler(this.OnSizeChanged);
}
}
/// <summary>
/// The WM_PAINT message is sent to make a request to paint a portion of a window.
/// </summary>
public const int WM_PAINT = 0x000F;
/// <summary>
/// Override WM_PAINT to repaint the selection range.
/// </summary>
[System.Diagnostics.DebuggerStepThroughAttribute()]
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_PAINT
&& !this.DesignMode
&& this.repaintSelectionRange) {
// MonthCalendar is ControlStyles.UserPaint=false => Paint event is not raised
this.RepaintSelectionRange(ref m);
}
}
/// <summary>
/// Repaint the SelectionRange.
/// </summary>
private void RepaintSelectionRange(ref Message m) {
using (Graphics graphics = this.CreateGraphics())
using (Brush backBrush
= new SolidBrush(graphics.GetNearestColor(this.BackColor)))
using (Brush selectionBrush
= new SolidBrush(graphics.GetNearestColor(SystemColors.ActiveCaption))) {
Rectangle todayFrame = Rectangle.Empty;
// For each day in SelectionRange...
for (DateTime selectionDate = this.SelectionStart;
selectionDate <= this.SelectionEnd;
selectionDate = selectionDate.AddDays(1)) {
Rectangle selectionDayRectangle = this.GetSelectionDayRectangle(selectionDate);
if (selectionDayRectangle.IsEmpty) continue;
if (selectionDate.Date == this.TodayDate) {
todayFrame = selectionDayRectangle;
}
// Paint as 'selected' a little smaller than the whole rectangle
Rectangle highlightRectangle = Rectangle.Inflate(selectionDayRectangle, 0, -2);
if (selectionDate == this.SelectionStart) {
highlightRectangle.X += 2;
highlightRectangle.Width -= 2;
}
if (selectionDate == this.SelectionEnd) {
highlightRectangle.Width -= 2;
}
// Paint background, selection and day-of-month text
graphics.FillRectangle(backBrush, selectionDayRectangle);
graphics.FillRectangle(selectionBrush, highlightRectangle);
TextRenderer.DrawText(
graphics,
selectionDate.Day.ToString(),
this.Font,
selectionDayRectangle,
SystemColors.ActiveCaptionText,
TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
if (this.ShowTodayCircle && !todayFrame.IsEmpty) {
// Redraw the ShowTodayCircle (square) that we painted over above
using (Pen redPen = new Pen(Color.Red)) {
todayFrame.Width--;
todayFrame.Height--;
graphics.DrawRectangle(redPen, todayFrame);
}
}
}
}
/// <summary>
/// When displayed dates changed, clear the cached month locations.
/// </summary>
private SelectionRange previousDisplayedDates = new SelectionRange();
/// <summary>
/// Gets a graphics Rectangle for the area corresponding to a single date on the calendar.
/// </summary>
private Rectangle GetSelectionDayRectangle(DateTime selectionDateTime) {
// Handle the leading and trailing dates from the previous and next months
SelectionRange allDisplayedDates = this.GetDisplayRange(false);
SelectionRange fullMonthDates = this.GetDisplayRange(true);
int adjust1Week;
DateTime selectionDate = selectionDateTime.Date;
if (selectionDate < allDisplayedDates.Start
|| selectionDate > allDisplayedDates.End) {
// Selection Date is not displayed on calendar
return Rectangle.Empty;
} else if (selectionDate < fullMonthDates.Start) {
// Selection Date is trailing from the previous partial month
selectionDate = selectionDate.AddDays(7);
adjust1Week = -1;
} else if (selectionDate > fullMonthDates.End) {
// Selection Date is leading from the next partial month
selectionDate = selectionDate.AddDays(-14);
adjust1Week = +2;
} else {
// A mainline date
adjust1Week = 0;
}
// Discard cached month locations when calendar moves
if (this.previousDisplayedDates.Start != allDisplayedDates.Start
|| this.previousDisplayedDates.End != allDisplayedDates.End) {
this.DiscardCachedMonthDateAreaLocations();
this.previousDisplayedDates.Start = allDisplayedDates.Start;
this.previousDisplayedDates.End = allDisplayedDates.End;
}
Point monthDateAreaLocation = this.GetMonthDateAreaLocation(selectionDate);
if (monthDateAreaLocation.IsEmpty) return Rectangle.Empty;
DayOfWeek monthFirstDayOfWeek = (new DateTime(selectionDate.Year, selectionDate.Month, 1)).DayOfWeek;
int dayOfWeekAdjust = (int)monthFirstDayOfWeek - (int)this.calendarFirstDayOfWeek;
if (dayOfWeekAdjust < 0) dayOfWeekAdjust += 7;
int row = (selectionDate.Day - 1 + dayOfWeekAdjust) / 7;
int col = (selectionDate.Day - 1 + dayOfWeekAdjust) % 7;
row += adjust1Week;
return new Rectangle(
monthDateAreaLocation.X + col * this.dayCellWidth,
monthDateAreaLocation.Y + row * this.dayCellHeight,
this.dayCellWidth,
this.dayCellHeight);
}
/// <summary>
/// Cached calendar location from the last lookup.
/// </summary>
private Point[] cachedMonthDateAreaLocation = new Point[13];
/// <summary>
/// Discard the cached month locations when calendar moves.
/// </summary>
private void DiscardCachedMonthDateAreaLocations() {
for (int i = 0; i < 13; i++) this.cachedMonthDateAreaLocation[i] = Point.Empty;
}
/// <summary>
/// Gets the graphics location (x,y point) of the top left of the
/// calendar date area for the month containing the specified date.
/// </summary>
private Point GetMonthDateAreaLocation(DateTime selectionDate) {
Point monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month];
HitTestInfo hitInfo;
if (!monthDateAreaLocation.IsEmpty
&& (hitInfo = this.HitTest(monthDateAreaLocation.X, monthDateAreaLocation.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month) {
// Use previously cached lookup
return monthDateAreaLocation;
} else {
// Assume the worst (Error: empty)
monthDateAreaLocation = this.cachedMonthDateAreaLocation[selectionDate.Month] = Point.Empty;
Point monthDataAreaPoint = this.GetMonthDateAreaMiddle(selectionDate);
if (monthDataAreaPoint.IsEmpty) return Point.Empty;
// Move left from the middle to find the left edge of the Date area
monthDateAreaLocation.X = monthDataAreaPoint.X--;
HitTestInfo hitInfo1, hitInfo2;
while ((hitInfo1 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y))
.HitArea == HitArea.Date
&& hitInfo1.Time.Month == selectionDate.Month
|| (hitInfo2 = this.HitTest(monthDataAreaPoint.X, monthDataAreaPoint.Y + this.dayCellHeight))
.HitArea == HitArea.Date
&& hitInfo2.Time.Month == selectionDate.Month) {
monthDateAreaLocation.X = monthDataAreaPoint.X--;
if (monthDateAreaLocation.X < 0) return Point.Empty; // Error: bail
}
// Move up from the last column to find the top edge of the Date area
int monthLastDayOfWeekX = monthDateAreaLocation.X + (this.dayCellWidth * 7 * 13) / 14;
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
while (this.HitTest(monthLastDayOfWeekX, monthDataAreaPoint.Y).HitArea == HitArea.Date) {
monthDateAreaLocation.Y = monthDataAreaPoint.Y--;
if (monthDateAreaLocation.Y < 0) return Point.Empty; // Error: bail
}
// Got it
this.cachedMonthDateAreaLocation[selectionDate.Month] = monthDateAreaLocation;
return monthDateAreaLocation;
}
}
/// <summary>
/// Paranoid fudge/wobble of the GetMonthDateAreaMiddle in case
/// our first estimate to hit the month misses.
/// (Needed? perhaps not.)
/// </summary>
private static Point[] searchSpiral = {
new Point( 0, 0),
new Point(-1,+1), new Point(+1,+1), new Point(+1,-1), new Point(-1,-1),
new Point(-2,+2), new Point(+2,+2), new Point(+2,-2), new Point(-2,-2)
};
/// <summary>
/// Gets a point somewhere inside the calendar date area of
/// the month containing the given selection date.
/// </summary>
/// <remarks>The point returned will be HitArea.Date, and match the year and
/// month of the selection date; otherwise it will be Point.Empty.</remarks>
private Point GetMonthDateAreaMiddle(DateTime selectionDate) {
// Iterate over all displayed months, and a search spiral (needed? perhaps not)
for (int dimX = 1; dimX <= this.CalendarDimensions.Width; dimX++) {
for (int dimY = 1; dimY <= this.CalendarDimensions.Height; dimY++) {
foreach (Point search in searchSpiral) {
Point monthDateAreaMiddle = new Point(
((dimX - 1) * 2 + 1) * this.Width / (2 * this.CalendarDimensions.Width)
+ this.dayCellWidth * search.X,
((dimY - 1) * 2 + 1) * this.Height / (2 * this.CalendarDimensions.Height)
+ this.dayCellHeight * search.Y);
HitTestInfo hitInfo = this.HitTest(monthDateAreaMiddle);
if (hitInfo.HitArea == HitArea.Date) {
// Got the Date Area of the month
if (hitInfo.Time.Year == selectionDate.Year
&& hitInfo.Time.Month == selectionDate.Month) {
// For the correct month
return monthDateAreaMiddle;
} else {
// Keep looking in the other months
break;
}
}
}
}
}
return Point.Empty; // Error: not found
}
/// <summary>
/// When this MonthCalendar is resized, recalculate the size of a day cell.
/// </summary>
private void OnSizeChanged(object sender, EventArgs e) {
// Discard previous cached Month Area Location
DiscardCachedMonthDateAreaLocations();
this.dayCellWidth = this.dayCellHeight = 0;
// Without this, the repaint sometimes does not happen...
this.Invalidate();
// Determine Y offset of days area
int middle = this.Width / (2 * this.CalendarDimensions.Width);
int dateAreaTop = 0;
while (this.HitTest(middle, dateAreaTop).HitArea != HitArea.PrevMonthDate
&& this.HitTest(middle, dateAreaTop).HitArea != HitArea.Date) {
dateAreaTop++;
if (dateAreaTop > this.ClientSize.Height) return; // Error: bail
}
// Determine height of a single day box
int dayCellHeight = 1;
DateTime dayCellTime = this.HitTest(middle, dateAreaTop).Time;
while (this.HitTest(middle, dateAreaTop + dayCellHeight).Time == dayCellTime) {
dayCellHeight++;
}
// Determine X offset of days area
middle = this.Height / (2 * this.CalendarDimensions.Height);
int dateAreaLeft = 0;
while (this.HitTest(dateAreaLeft, middle).HitArea != HitArea.Date) {
dateAreaLeft++;
if (dateAreaLeft > this.ClientSize.Width) return; // Error: bail
}
// Determine width of a single day box
int dayCellWidth = 1;
dayCellTime = this.HitTest(dateAreaLeft, middle).Time;
while (this.HitTest(dateAreaLeft + dayCellWidth, middle).Time == dayCellTime) {
dayCellWidth++;
}
// Record day box size and actual first day of the month used
this.calendarFirstDayOfWeek = dayCellTime.DayOfWeek;
this.dayCellWidth = dayCellWidth;
this.dayCellHeight = dayCellHeight;
}
}
My testing shows that Windows 7 does not have the painting problem and I expect that neither does Vista, so this only attempts a fix for Windows XP.
you can try this code:
Dim StartDate As Date = New DateTime(2011, 9, 21)
Dim EndDate As Date = New DateTime(2011, 9, 25)
MonthCalendar1.SelectionRange = New SelectionRange(StartDate, EndDate)
for more information:
http://www.authorcode.com/how-to-select-a-range-of-dates-in-the-monthcalendar-control/
http://www.authorcode.com/how-to-enable-windows-xp-visual-styles-of-net-application/