Click here to Skip to main content
15,896,397 members
Articles / Desktop Programming / WPF

Image Magic - Image Levels using Custom Controls

Rate me:
Please Sign up or sign in to vote.
4.00/5 (2 votes)
26 May 2010CPOL11 min read 42.3K   2.2K   20  
Drop in WPF custom leveling controls and logic
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfCustomRGBHisto
{
    /// <summary>
    /// Control for slider with 2 thumbs to implement image levels adjustment.
    /// 2 dependency properties, WhiteValue & BlackValue ranging from 0-255.
    /// Control is resizable.
    /// </summary>
    [TemplatePart(Name="PART_TwoThumbContainer", Type=typeof(StackPanel))]
    [TemplatePart(Name = "PART_BlackThumb", Type = typeof(Thumb))]
    [TemplatePart(Name = "PART_WhiteThumb", Type = typeof(Thumb))]
    [TemplatePart(Name = "PART_RangeRect", Type = typeof(Rectangle))]
    [TemplatePart(Name = "PART_TwoThumbCanvas", Type = typeof(Canvas))]
    public class TwoThumb : Control
    {
        public static readonly DependencyProperty BlackValueProperty;    //also readonly????
        public static readonly DependencyProperty WhiteValueProperty;
        // non Routed event for external consumtion
        public event DependencyPropertyChangedEventHandler BlackValueChanged;
        public event DependencyPropertyChangedEventHandler WhiteValueChanged;

        //controls included in TwoThumb
        protected Thumb whiteThumb;
        protected Thumb blackThumb;
        private Rectangle rangeRect;
        private Canvas canvas;              //whiteThumb, blackThumb and rangeRect reside in canvas

        private double minTrack = 0.0;      //min thumb position   Inited by OnCanvasSizeChanged
        private double maxTrack = 0.0;      //max thumb position   Inited by OnCanvasSizeChanged
        private double minRangeRectX;       //rangeRect is one thumbnail width smaller than track
        private double maxRangeRectX;


        static TwoThumb()
        {
            //tell WPF providing new style which will provide control template
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TwoThumb), new FrameworkPropertyMetadata(typeof(TwoThumb)));
            BlackValueProperty = DependencyProperty.Register(
                "BlackValue", typeof(double), typeof(TwoThumb),
                new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnBlackValueChanged)));
                //new PropertyMetadata(0.0));
            WhiteValueProperty = DependencyProperty.Register(
                "WhiteValue", typeof(double), typeof(TwoThumb),
                new FrameworkPropertyMetadata(255.0, new PropertyChangedCallback(OnWhiteValueChanged)));
            //new PropertyMetadata(255.0));
        } //static ctor


        public double BlackValue
        {
            get { return (double)GetValue(BlackValueProperty); }
            set { SetValue(BlackValueProperty, value); }
        }


        public double WhiteValue
        {
            get { return (double)GetValue(WhiteValueProperty); }
            set { SetValue(WhiteValueProperty, value); }
        }


        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            //attach the Thumb drag events to the StackPanel (topmost control in twoThumb tree)
            StackPanel sp = GetTemplateChild("PART_TwoThumbContainer") as StackPanel;
            if (sp != null)
            {
                sp.AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(OnDragStarted));
                sp.AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnDragDelta));
                sp.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnDragCompleted));
            }
            //Get the parts composing the control by name
            whiteThumb = GetTemplateChild("PART_WhiteThumb") as Thumb;
            blackThumb = GetTemplateChild("PART_BlackThumb") as Thumb;
            rangeRect = GetTemplateChild("PART_RangeRect") as Rectangle;
            canvas = GetTemplateChild("PART_TwoThumbCanvas") as Canvas;
            //Allow twoThumb to change in size
            if (canvas != null)
            {
               canvas.AddHandler(SizeChangedEvent, new SizeChangedEventHandler(OnCanvasSizeChanged));
            }
        } //OnApplyTemplate()


        void OnDragStarted(object sender, DragStartedEventArgs e)
        {
            Thumb thumb = (Thumb)e.OriginalSource;  //sender is StackPanel
            thumb.Background = Brushes.Orange;
        } //OnDragStarted()


        virtual protected void OnDragDelta(object sender, DragDeltaEventArgs e)
        {
            Thumb thumb = (Thumb)e.OriginalSource;  //sender is StackPanel
            double newHPos = Canvas.GetLeft(thumb) + e.HorizontalChange;
            newHPos = ConstrainToTrack(newHPos);
            newHPos = CoerceThumbPos(thumb, newHPos);

            double value = PosToValue(newHPos);
            if (thumb.Name == "PART_BlackThumb")
                BlackValue = value;
            else
                WhiteValue = value;
            Canvas.SetLeft(thumb, newHPos);

            if (WhiteValue == BlackValue)
            {
                Canvas.SetZIndex(thumb, 1);     //moved thumb goes on top
                if (thumb.Name == "PART_BlackThumb") //other thumb goes below
                    Canvas.SetZIndex(whiteThumb, 0);
                else Canvas.SetZIndex(blackThumb, 0);
            }
        } //OnDragDelta()


        void OnDragCompleted(object sender, DragCompletedEventArgs e)
        {
            Thumb thumb = (Thumb)e.OriginalSource;  //sender is StackPanel
            thumb.Background = Brushes.BlueViolet;
        } //OnDragCompleted()


        protected double PosToValue(double pos)
        {
            if (pos > maxTrack) pos = maxTrack;
            if (pos < minTrack) pos = minTrack;
            return (255 * (pos - minTrack) / (maxTrack - minTrack));
        } // PosToValue()


        // minTrack >= pos <= maxTrack 
        protected double ConstrainToTrack(double pos)
        {
            if (pos > maxTrack) return maxTrack;
            if (pos < minTrack) return minTrack;
            return pos;
        } //ConstrainToTrack()


        //Convert thumb value to track position
        //ONLY for black and white sliders.
        protected double ValueToPos(double value)
        {
            return ConstrainToTrack(value / 255.0 * (maxTrack - minTrack) + minTrack);
        } // ValueToPos()


        //If new position of thumb is not valid, set it to a valid  max or min
        virtual protected double CoerceThumbPos(Thumb thumb, double newHpos)
        {
            if (thumb.Name == "PART_BlackThumb")
            {
                if (newHpos > ValueToPos(WhiteValue))
                    return ValueToPos(WhiteValue);
            }
            else if (newHpos < ValueToPos(BlackValue))
                return ValueToPos(BlackValue);
            return newHpos; // value ok
        } //CoerceThumbPos()


        void DrawRangeRect()
        {
            rangeRect.Width = maxRangeRectX - minRangeRectX;
        } //DrawRect()


        //Called on Canvas Size Change, assumes legitimate values already set
        virtual protected void RepositionThumbs()
        {
            double thumbPos = ValueToPos(BlackValue);
            Canvas.SetLeft(blackThumb, thumbPos);

            thumbPos = ValueToPos(WhiteValue);
            Canvas.SetLeft(whiteThumb, thumbPos);
        }


        void OnCanvasSizeChanged(object sender, SizeChangedEventArgs e)
        {
            Canvas myCanvas = sender as Canvas;
            minRangeRectX = Canvas.GetLeft(rangeRect);
            maxRangeRectX = e.NewSize.Width - minRangeRectX - blackThumb.ActualWidth/2.0;   //?????????????????????????
            minTrack = minRangeRectX - blackThumb.ActualWidth / 2.0;
            maxTrack = maxRangeRectX - blackThumb.ActualWidth / 2.0;
            DrawRangeRect();
            RepositionThumbs();
        } //OnCanvasSizeChanged()


        //This gets called BOTH when external class set BlackValue and when BlackValue is set within the class.
        static void OnBlackValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            TwoThumb tt = obj as WpfCustomRGBHisto.TwoThumb;
            if (tt.blackThumb != null)          //you may not have an instance yet
                tt.OnBlackValueChanged( args);   //call INSTANCE method passing args
        }


        void OnBlackValueChanged(DependencyPropertyChangedEventArgs args)
        {
            if (BlackValueChanged != null)
                BlackValueChanged(this, args);
            if (blackThumb.IsDragging) return;  //thumb positions already set, internal op do nothing
            //Have to decide if newValue is good
            RepositionThumbs();                 //value changed by client
        }


        //This gets called BOTH when external class set WhiteValue and when WhiteValue is set within the class.
        static void OnWhiteValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            TwoThumb tt = obj as WpfCustomRGBHisto.TwoThumb;
            if (tt.whiteThumb != null)          //you may not have an instance yet
                tt.OnWhiteValueChanged(args);   //call INSTANCE method passing args
        }


        void OnWhiteValueChanged(DependencyPropertyChangedEventArgs args)
        {
            if (WhiteValueChanged != null)
                WhiteValueChanged(this, args);
            if (whiteThumb.IsDragging) return;  //thumb positions already set, internal op do nothing
            //Have to decide if newValue is good
            RepositionThumbs();                 //value changed by client
        }

    }  //Class TwoThumb
} //namespace WpfCustomRGBHisto

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions