private void InputFileChoose_OnFilenameChanged(object sender, EventArgs e)
        {
            if (InputImage?.Source == null)
            {
                return;
            }
            var w = new WriteableBitmap((BitmapSource)InputImage.Source);

            unsafe {
                uint *        pixel  = (uint *)w.BackBuffer;
                MessageHeader header = new MessageHeader {
                    ChecksumType = new XorChecksumCalculator()
                };
                var headerData = new byte[header.Length];
                for (int i = 0; i < headerData.Length; i++)
                {
                    headerData[i] = ByteHider.RecoverByte(*pixel);
                    pixel++;
                }
                try {
                    header = MessageHeader.FromBytes(headerData);
                }
                catch (FormatException) {
                    ShowImageInvalidMessage();
                    return;
                }
                pixel++;
                int startIndex = FillStartUtility.GetFillStartIndex(header.Fill, w.PixelWidth, w.PixelHeight, header.DataLength * (header.DataSpacing + 1));

                pixel += startIndex - header.Length - 1;
                var dataBuffer = new byte[header.DataLength];
                for (int i = 0; i < dataBuffer.Length; i++)
                {
                    dataBuffer[i] = ByteHider.RecoverByte(*pixel);
                    pixel++;
                    // skip spacing pixels
                    for (int j = 0; j < header.DataSpacing; j++)
                    {
                        pixel++;
                    }
                }

                // check the checksum
                var checksumMatches = header.Checksum.SequenceEqual(_checksumCalculator.GetChecksum(dataBuffer));
                if (!checksumMatches)
                {
                    ShowImageInvalidMessage();
                }
                else
                {
                    OutputTextBox.Text = Encoding.UTF8.GetString(dataBuffer);
                }
            }
        }
        private void MessageTextChanged(object sender, TextChangedEventArgs e)
        {
            if (OutputImage?.Source == null)
            {
                return;
            }
            var w = (WriteableBitmap)OutputImage.Source;

            var textBox = sender as TextBox;

            IsOverCapacity = false;
            var data   = Encoding.UTF8.GetBytes(textBox.Text);
            var header = new MessageHeader {
                Fill         = (FillStart)FillComboBox.SelectedIndex,
                ChecksumType = new XorChecksumCalculator(),
                DataLength   = checked ((ushort)data.Length)
            };

            try {
                header.DataSpacing = byte.Parse(SpaceTextBox.Text);
                if (header.DataSpacing > 254)
                {
                    throw new Exception();
                }
            }
            catch {
                MessageBox.Show("Invalid spacing, allowed range is 0-254");
                return;
            }

            var capacity = Math.Min(ushort.MaxValue,
                                    (w.PixelWidth * w.PixelHeight - header.Length) / (header.DataSpacing + 1));

            ProgressBar.Maximum = capacity;
            var textDataLength = Encoding.UTF8.GetByteCount(textBox.Text);

            ProgressBar.Value = textDataLength;
            if (textDataLength > capacity)
            {
                IsOverCapacity = true;
                return;
            }

            header.SetChecksumFromData(data);
            int dataStartPixelIndex =
                FillStartUtility.GetFillStartIndex(header.Fill, w.PixelWidth, w.PixelHeight, data.Length * (header.DataSpacing + 1));
            var original = new WriteableBitmap((BitmapSource)InputImage.Source);

            w.Lock();
            long counter = 0;

            unsafe {
                uint *originalPixel = (uint *)original.BackBuffer;
                uint *pixel         = (uint *)w.BackBuffer;
                var   headerBytes   = header.GetBytes();
                // place header bytes
                foreach (byte b in headerBytes)
                {
                    *pixel = ByteHider.HideByte(*originalPixel, b);
                    pixel++;
                    originalPixel++;
                    counter++;
                }
                // copy unmodified bytes
                while (counter < dataStartPixelIndex)
                {
                    *pixel = *originalPixel;
                    pixel++;
                    originalPixel++;
                    counter++;
                }
                // place data
                foreach (byte b in data)
                {
                    *pixel = ByteHider.HideByte(*originalPixel, b);
                    pixel++;
                    originalPixel++;
                    counter++;
                    // copy spacing pixels
                    for (int i = 0; i < header.DataSpacing; i++)
                    {
                        *pixel = *originalPixel;
                        pixel++;
                        originalPixel++;
                        counter++;
                    }
                }
                // copy unmodified bytes
                while (counter < w.PixelWidth * w.PixelHeight)
                {
                    *pixel = *originalPixel;
                    pixel++;
                    originalPixel++;
                    counter++;
                }
            }

            w.AddDirtyRect(new Int32Rect(0, 0, w.PixelWidth, w.PixelHeight));
            w.Unlock();
        }