Picture Puzzle: Set one Image to the same size and

2019-09-20 00:45发布

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!

1条回答
疯言疯语
2楼-- · 2019-09-20 01:39

You can set the HorizontalAlignment and VerticalAlignment of your mask Image to Stretch and the Stretch 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.

查看更多
登录 后发表回答