Silverlight 2.0 içerisinde maskeleme (clipping)

0 dakikada yazıldı

5931 defa okundu

Düzenle

Bu yazımızda Silverlight 2.0 içerisinde maskeleme (clip) işlemlerine göz
atacağız. İlk olarak basit bir maskeleme işleminin XAML içerisinde nasıl
yapıldığına baktıktan sonra bu maskeleri nasıl anime edebileceğimize ve
programatik yoldan ulaşımına değineceğiz.

Bir maske yaratalım!

Herhangi bir nesneye maske aktarmak demek aslında o nesnenin Clip
özelliğine uygun bir şekil aktarmak demektir. Elinizde var olan bir
geometri nesnesini alarak bir element'in Clip özelliğine verdiğiniz anda
artık söz konusu Geometri nesnesi bir maske görevi görür. Expression
Blend içerisinde baktığınızda maskeler nesnelerin birer Property'sine
atandığı için ayrı birer obje olarak arayüzde gözükmez.

Expression Blend içerisinde maskelenmeye hazır kontroller.
Expression Blend içerisinde maskelenmeye hazır kontroller.

Yukarıdaki ekran görüntüsünde de görebileceğiniz üzere sahnede bir Image
ve bir de Ellipse nesnesi bulunuyor. Bir sonraki adımda amacımız bu
Ellipse nesnesini Image için bir maske haline getirmek. Yapacağımız
işlem bu iki nesneyi fare ile seçip "Objects and Timeline" kısmında sağ
tıklayıp "Path / Make Clipping Path" komutunu vermek. Böylece söz
konusu Ellipse artık Image'in Clip özelliğine bir geometri olarak
aktarılacak ve ortada Ellipse diye bir nesne kalmayacak.

Maskelenmiş Image kontrolümüz karşınızda.
Maskelenmiş Image kontrolümüz karşınızda.

Artık kontrolümüzü maskeledik ve Ellipse diye bir nesne kalmadı peki
arkaplanda XAML tarafında neler oldu? Gelin Blend'in bizim için
yarattığı XAML kodunu bir detaylıca inceleyelim.

[XAML]

        <Image
Margin
="25.032,27,84.032,54.842" Source="1080366_88011245.jpg" Stretch="Fill"
Clip
="M258.5,130 C258.5,166.17465
212.60919,195.5 156,195.5 C99.390816,195.5 53.5,166.17465 53.5,130
C53.5,93.825348 99.390816,64.5 156,64.5 C212.60919,64.5 258.5,93.825348
258.5,130 z"/>

Gördüğünüz gibi Image nesnesinin uzun bir Clip datası var. Bu data bizim
bir önceki adımda yarattığımız Ellipse'in ta kendisi. Aslında bu kodu bu
şekilde yazmak yerine daha okunaklı bir şekilde de yazabilirdik. Nasıl
mı?

[XAML]

        <Image
Margin
="7.968,-68,101.032,0" Source="1080366_88011245.jpg" Stretch="Fill"
VerticalAlignment
="Top" Height="218">

            <Image.Clip>

                <EllipseGeometry
Center
="200,100" RadiusX="90"
RadiusY
="60" />

            </Image.Clip>

        </Image>

Peki ne değişti? Nesne basında Clip vermiş olduk. Image nesnesinin
Clip özelliğine doğrudan koordinatları girmek yerine ayrı bir element
vermeye karar verdik ve bu nedenle de Image.Clip tagları açarak
içerisine bir Geometry nesnesi yerleştirdik. EllipseGeometry nesnesi
gibi GeometryGroup, LineGeometry, PathGeometry nesneleri de
mevcut. Bizdeki örnekte EllipseGeometry'nin Center özelliği
maskelenen Image nesnesinin merkez noktasına göre maskenin ne kadar
uzaklıkta olacağına dair X ve Y değerlerini verirken RadiusX ve
RadiusY'de yatay ve dikey olarak Ellipse'in yarıçapını tanımlıyor. Peki
bu iki metod arasındaki diğer farklar nelerdir? Gelin olayın animasyon
kısmına bir bakalım.

Maskelere animasyon katalım....

Herhangi bir nesnenin maskesine animasyon verebilmek için Expression
Blend içerisinde animasyon modunda sol taraftaki araç çubuğundan
Selection aracı yerine "Direct Selection" aracını
kullanmalısınız. Söz konusu aracı seçtiğiniz anda seçili nesnenin
maskesine ait tanımlı noktaları ekranda görebilirsiniz. Böylece
rahatlıkla KeyFrame'ler yaratarak noktaların pozisyonlarını
değiştirebilir ve maskeyi anime edebilirsiniz.

Maskemize animasyon verirken.
Maskemize animasyon verirken.

Biz örneğimizde maskedeki tüm noktaları toplu seçerek bir Ellipse
şekliden tüm maskenin pozisyonunu değiştiren bir animasyon hazırlıyoruz.
Böylece sanki bir ışık ile resme bakılıyormuş gibi resmin üzerinde
geziliyor görüntüsü yaratıyoruz. Gelin Blend'in bizim için yarattığı
XAML koduna göz atalım.

[XAML]

        <Storyboard
x
:Name="Storyboard1">

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.StartPoint)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="258.5,130"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="218.937328102718,129.064332728402"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[0].(BezierSegment.Point1)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="258.5,166.174652099609"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="218.937328102718,165.238984828011"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[0].(BezierSegment.Point2)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="212.609191894531,195.5"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="173.046519997249,194.564332728402"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[0].(BezierSegment.Point3)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="156,195.5"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="116.437328102718,194.564332728402"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[1].(BezierSegment.Point1)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="99.3908157348633,195.5"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="59.8281438375813,194.564332728402"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[1].(BezierSegment.Point2)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="53.5,166.174652099609"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="13.937328102718,165.238984828011"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[1].(BezierSegment.Point3)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="53.5,130"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="13.937328102718,129.064332728402"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[2].(BezierSegment.Point1)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="53.5,93.8253479003906"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="13.937328102718,92.8896806287922"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[2].(BezierSegment.Point2)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="99.3908157348633,64.5"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="59.8281438375813,63.5643327284016"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[2].(BezierSegment.Point3)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="156,64.5"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="116.437328102718,63.5643327284016"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[3].(BezierSegment.Point1)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="212.609191894531,64.5"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="173.046519997249,63.5643327284016"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[3].(BezierSegment.Point2)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="258.5,93.8253479003906"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="218.937328102718,92.8896806287922"/>

            </PointAnimationUsingKeyFrames>

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="image"
Storyboard.TargetProperty
="(UIElement.Clip).(PathGeometry.Figures)[0].(PathFigure.Segments)[3].(BezierSegment.Point3)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="258.5,130"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="218.937328102718,129.064332728402"/>

            </PointAnimationUsingKeyFrames>

        </Storyboard>

Animasyonun kodu gördüğünüz gibi epey uzun. Aslında Blend kendi Clip'ini
noktalardan yarattığı için mecburen bu noktaları tek tek anime ederek
noktaların pozisyonlarını değiştirmek zorunda kalıyor. Bu esnada bizim
Image'in XAML kodlarına bakarsan zaten minik bir değişiklik de
dikkatimizi çekiyor.

[XAML]

        <Image
Margin
="57,73.921,52,8.079" Source="1080366_88011245.jpg" Stretch="Fill"
x
:Name="image">

            <Image.Clip>

                <PathGeometry>

                    <PathFigure
IsClosed
="True" StartPoint="258.5,130">

                        <BezierSegment
Point1
="258.5,166.174652099609" Point2="212.609191894531,195.5" Point3="156,195.5"/>

                        <BezierSegment
Point1
="99.3908157348633,195.5" Point2="53.5,166.174652099609" Point3="53.5,130"/>

                        <BezierSegment
Point1
="53.5,93.8253479003906" Point2="99.3908157348633,64.5" Point3="156,64.5"/>

                        <BezierSegment
Point1
="212.609191894531,64.5" Point2="258.5,93.8253479003906" Point3="258.5,130"/>

                    </PathFigure>

                </PathGeometry>

            </Image.Clip>

        </Image>

Söz konusu Clip'in içindeki noktaları anime edebilmek için Blend de
kısmen bizim taktiğe geri dönmüş ve bir PathGeometry yerleştirmiş. Oysa
biz zamanında Ellipse koymuştuk :) Neden doğrudan o esnada
EllipseGeometry koymadın? diye Blend'e sorarsak eminim cevabı basit
olacaktır. "Nereden bilebilirim ki bu noktaları ayrı ayrı anime
etmeyeceğinizi?" Aslında Blend haklı. Blend her ihtimali düşünmek
zorunda çünkü o bir GUI :) Oysa biz kendi örneğimizde maske olarak
Ellipse'in şeklini değiştirmeyeceğiz, sadece konumunu değiştireceğiz o
nedenle bu sistem bize çok da uygun değil.

Yukarıdaki PointAnimation taglarına bakarsan tek tek Image
nesnesinin Clip özelliğindeki değerin alınıp bu değerdeki noktaların
Index numarası verilerek bulunduğunu ve pozisyonlarının değiştirildiğini
görebilirsiniz. Performans açısından bir sıkıntısı olmasa da oluşan
kodun okunabilirliliği çok zayıf olmakla beraber bu gibi bir Clip
nesnesinin programatik olarak anime edilmesi de neredeyse imkansız. C#
veya VB kodu ile tek tek bu noktaları bulup anime etmek işkenceden
farksız olacaktır.

Peki ya bizim teknikle yaparsak?

Bizim esas istediğimiz maskenin konumunun değişmesiydi. Daha önceki
kodlarımızda bir EllipseGeometry'yi maske olarak verebilmiştik. Bu
EllipseGeometry nesnesinin Center özelliği maskenin konumunu
belirliyordu. Bu durumda bizim kodumuzda bu özelliklerin anime edilmesi
yeterli olacaktır. Fakat eğer Blend içerisinde bu animasyonu yapacak
olursanız bizim EllipseGeometry'yi ısrarlı bir şekilde yukarıdaki gibi
bir PathGeometry'ye çevirecek ve yine aynı animasyon kodunu
üretecektir. Bu durumda bizim de programatik olarak bu maskeyi anime
etmemiz yine zorlaşacaktır.

Sonuç olarak eğer bir nesnenin maskesinin pozisyonunu çok uğraşmadan kod
ile anime edebilmek istiyorsanız Blend'in arayüzünden anime etmemeniz
gerekiyor.

Programatik olarak maskeye erişmek...

Bir nesnenin maskesine programatik olarak erişmek için söz konusu
nesnenin Clip özelliğini alıp uygu Geometry tipine cast edebilirsiniz.
Sonrasında elinizde Geometry nesnesi ile ilgilenmeniz yeterli olacaktır.
Oysa bir diğer seçenek de XAML içerisinde bu Geometry nesnesine el ile
bir isim vermektir.

[XAML]

        <Image
Margin
="7.968,-68,101.032,0" Source="1080366_88011245.jpg" Stretch="Fill"
VerticalAlignment
="Top" Height="218">

            <Image.Clip>

                <EllipseGeometry
x
:Name="Maskesi"
Center
="200,100" RadiusX="90"
RadiusY
="60" />

            </Image.Clip>

        </Image>

Gördüğünüz gibi basit bir şekilde bizim EllipseGeometry nesnesine bir
isim verdik. Artık kod tarafında bu isim ile EllipseGeometry'ye
ulaşabilir ve Center veya RadiusX ve RadiusY özelliklerini
değiştirebiliriz.

[VB]

Maskesi.Center = New Point(200, 200)

Böylece tamamen kod ile bu maskeyi anime etmek istediğinizde de
rahatlıkla bu noktayı anime ederek maskenin pozisyonunun değiştiği bir
animasyon üretebilirsiniz. Örnek bir kodu aşağıda inceleyebilirsiniz.

[VB]

Dim DBL As New
PointAnimation

DBL.From = New Point(100, 100)

DBL.To = New Point(200, 200)

DBL.Duration = New TimeSpan(0, 0, 2)

Storyboard.SetTarget(DBL, Maskesi)

Storyboard.SetTargetProperty(DBL, New
PropertyPath(EllipseGeometry.CenterProperty))

Dim SB As New
Storyboard

SB.Children.Add(DBL)

SB.Begin()

[C#]

PointAnimation DBL = new
PointAnimation();

DBL.From = new Point(100, 100);

DBL.To = new Point(200, 200);

DBL.Duration = new TimeSpan(0, 0, 2);

Storyboard.SetTarget(DBL, Maskesi);

Storyboard.SetTargetProperty(DBL, new
PropertyPath(EllipseGeometry.CenterProperty));

Storyboard SB = new Storyboard();

SB.Children.Add(DBL);

SB.Begin();

Eğer bu animasyonu XAML tarafında temiz olarak yazmak isterseniz aslında
pratik bir şekilde elle de yazabilirsiniz.

[XAML]

        <Storyboard
x
:Name="Storyboard1">

            <PointAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName
="Maskesi"
Storyboard.TargetProperty
="(EllipseGeometry.Center)">

                <SplinePointKeyFrame KeyTime="00:00:00"
Value
="100,100"/>

                <SplinePointKeyFrame KeyTime="00:00:01"
Value
="200,200"/>

            </PointAnimationUsingKeyFrames>

        </Storyboard>

Animasyonumuz doğrudan Maskesi adındaki EllipseGeometry'yi hedef
alarak onun Center property'sini anime ediyor.

Unutmayın ki bizim örneğimizde böyle bir optimizasyon yapabilmemizin
nedeni maskenin sadece pozisyonunu değiştirmek istememiz. Eğer maskedeki
her bir noktanın konumunu ayrı ayrı birbirinden bağımsız olarak anime
etmek isterseniz Blend'in yaptığı teknikten farklı bir seçeneğiniz zaten
yok.

Hepinize kolay gelsin.