today I already asked you a question for my picture puzzle (Original Question).
I started to rewrite my code for better performance. And I got the most important part done!
But I have another problem.. I generate a gray overlay image to hide the image, but because I want to handle dynamic size of pictures I cannot set fixed width and height values for the "mask". So probably not the whole picture is overlayed with my "mask".
Does somebody have a solution for this? I don't know how to set the position and size of my mask exactly to the position and size of the picture.
I attached a screencast for demonstration.
XAML:
<Window x:Class="PicturePuzzle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Loaded="WindowLoaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<Grid x:Name="grid"
Margin="5"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<Image x:Name="imgPicture"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Source="Images/puzzle.gif"
Stretch="Uniform" />
<Image x:Name="imgMask" RenderOptions.EdgeMode="Aliased" />
</Grid>
<StackPanel Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
<Button x:Name="btnStart"
Width="60"
Margin="0,0,5,0"
Click="BtnStartClick"
Content="Start" />
<Button x:Name="btnStop"
Width="60"
Click="BtnStopClick"
Content="Stop"
IsEnabled="False" />
<ToggleButton x:Name="btnSolution"
Margin="5,0,0,0"
Checked="btnSolution_Checked"
Content="Lösung anzeigen"
Unchecked="btnSolution_Unchecked" />
</StackPanel>
<Slider x:Name="slSpeed"
IsDirectionReversed="True"
Maximum="10"
Minimum="1"
ValueChanged="SlSpeedValueChanged"
Value="10" />
</StackPanel>
</Grid>
</Window>
Codebehind:
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace PicturePuzzle
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
_positionAlphaValues = new Dictionary<Point, byte>();
_images = new List<FileInfo>();
using (var s = new StreamReader("Images.txt"))
{
while (!s.EndOfStream)
{
var line = s.ReadLine();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
var fi = new FileInfo(line);
if (!fi.Exists)
{
continue;
}
_images.Add(fi);
}
}
}
private const int MaxQuadsX = 5;
private const int MaxQuadsY = 5;
private readonly List<FileInfo> _images;
private DispatcherTimer _timer;
private DispatcherTimer _alphaTimer;
private WriteableBitmap _bitmap;
private Size _size;
private List<Point> _positions;
private Dictionary<Point, byte> _positionAlphaValues;
private int _tickCounter;
private int _imageCounter;
private int _quadWidth;
private int _quadHeight;
private void WindowLoaded(object sender, RoutedEventArgs e)
{
_size = imgPicture.RenderSize;
var width = (int)Math.Ceiling(_size.Width);
var height = (int)Math.Ceiling(_size.Height);
_quadWidth = width / MaxQuadsX;
_quadHeight = height / MaxQuadsY;
imgPicture.Width = _quadWidth * MaxQuadsX - 5;
imgPicture.Height = _quadHeight * MaxQuadsY - 5;
_bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null);
imgMask.Source = _bitmap;
}
#region Click handlers
private void BtnStartClick(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = false;
btnStop.IsEnabled = true;
btnSolution.IsChecked = false;
// set the real picture
_imageCounter = 0;
_images.Shuffle();
SetPuzzlePicture();
_timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(slSpeed.Value / 10) };
_timer.Tick += TimerTick;
_timer.Start();
_alphaTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(0.1) };
_alphaTimer.Tick += AlphaTimerOnTick;
_alphaTimer.Start();
}
private void BtnStopClick(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
_timer.Stop();
_alphaTimer.Stop();
}
private void SlSpeedValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (_timer != null)
{
_timer.Interval = TimeSpan.FromSeconds(slSpeed.Value / 10);
}
}
private void btnSolution_Checked(object sender, RoutedEventArgs e)
{
btnStop.IsEnabled = false;
StopTimers();
imgMask.Visibility = Visibility.Hidden;
}
private void btnSolution_Unchecked(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
ResetMaskImage();
}
#endregion
private void SetPuzzlePicture()
{
_positionAlphaValues.Clear();
ResetMaskImage();
var imgFile = _images[_imageCounter++];
var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(imgFile.FullName, UriKind.Absolute);
image.EndInit();
imgPicture.Source = image;
}
private void TimerTick(object sender, EventArgs e)
{
if (_tickCounter >= _positions.Count)
{
if (_imageCounter >= _images.Count)
{
_timer.Stop();
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
return;
}
SetPuzzlePicture();
}
var randomPoint = _positions[_tickCounter++];
_positionAlphaValues.Add(randomPoint, 255);
}
private void AlphaTimerOnTick(object sender, EventArgs eventArgs)
{
var updatedList = new Dictionary<Point, byte>();
foreach (var e in _positionAlphaValues)
{
var newValue = e.Value - (11 - slSpeed.Value) * 5;
if (newValue <= 0)
{
continue;
}
SetAlphaChannel(e.Key, (byte)newValue);
updatedList.Add(e.Key, (byte)newValue);
}
_positionAlphaValues = updatedList;
}
private void StopTimers()
{
if (_timer != null)
{
_timer.Stop();
}
if (_alphaTimer != null)
{
_alphaTimer.Stop();
}
}
private void ResetMaskImage()
{
imgMask.Visibility = Visibility.Visible;
var width = _quadWidth * MaxQuadsX;
var height = _quadHeight * MaxQuadsY;
var size = width * height * 4;
var buffer = new byte[size];
for (int i = 0; i < size; i++)
{
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i] = 255;
}
var area = new Int32Rect(0, 0, width, height);
_bitmap.WritePixels(area, buffer, width * 4, 0);
_positions = GetPositions();
_tickCounter = 0;
}
private void SetAlphaChannel(Point point, byte alpha)
{
var size = _quadWidth * _quadHeight * 4;
var buffer = new byte[size];
for (int i = 0; i < size; i++)
{
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i++] = 128;
buffer[i] = alpha;
}
var startX = (int)point.X * _quadWidth;
var startY = (int)point.Y * _quadHeight;
var area = new Int32Rect(startX, startY, _quadWidth, _quadHeight);
_bitmap.WritePixels(area, buffer, _quadWidth * 4, 0);
}
private List<Point> GetPositions()
{
var generated = new List<Point>();
for (int y = 0; y < MaxQuadsY; y++)
{
for (int x = 0; x < MaxQuadsX; x++)
{
var point = new Point(x, y);
generated.Add(point);
}
}
generated.Shuffle();
return generated;
}
}
}
Extensions.cs
using System;
using System.Collections.Generic;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace PicturePuzzle
{
public static class Extensions
{
public static Color GetPixel(this WriteableBitmap wbm, int x, int y)
{
if (y > wbm.PixelHeight - 1 || x > wbm.PixelWidth - 1)
return Color.FromArgb(0, 0, 0, 0);
if (y < 0 || x < 0)
return Color.FromArgb(0, 0, 0, 0);
if (!wbm.Format.Equals(PixelFormats.Bgra32))
return Color.FromArgb(0, 0, 0, 0);
IntPtr buff = wbm.BackBuffer;
int stride = wbm.BackBufferStride;
Color c;
unsafe
{
var pbuff = (byte*)buff.ToPointer();
int loc = y * stride + x * 4;
c = Color.FromArgb(
pbuff[loc + 3],
pbuff[loc + 2], pbuff[loc + 1],
pbuff[loc]);
}
return c;
}
public static void Shuffle<T>(this IList<T> list)
{
var rng = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}
Example Images.txt (has to be in your output folder, the images also):
Desert.jpg
Hydrangeas.jpg
Jellyfish.jpg
Koala.jpg
Lighthouse.jpg
Penguins.jpg
Tulips.jpg
androids.gif
Chrysanthemum.jpg
And the screencast: Screencast Link
Thanks for any help!
You can set the
HorizontalAlignment
andVerticalAlignment
of your mask Image toStretch
and theStretch
to Fill like so:<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="imgMask" Stretch="Fill" RenderOptions.EdgeMode="Aliased" />
This will cause the mask image to fill the grid, which will be sized to the other image.