Утилита сравнения для Blu-ray и DVD
Опубликовано 28 марта 2009 в 12:30:00 | Coding4Fun
В этой статье Джованни Монтроне (Giovanni Montrone) рассказывает о создании приложения для сравнения изображений, являющихся снимками экрана при воспроизведении фильмов с дисков DVD и Blu-ray. |
Джованни Монтроне (Giovanni Montrone (EN)) ASPSOFT, Inc. (EN) Сложность: для начинающих. Необходимое время: 2-3 часа. Цена: бесплатно. ПО: Visual Web Developer Express SP1 (или Visual Studio 2008 SP1), инструментальные средства Silverlight 2. Оборудование: нет. Приложение: запустить приложение. Исходные тексты : загрузить. |
Введение
Я большой любитель фильмов и телевидения высокой четкости (High Definition), но зачастую мне бывает трудно объяснить родственникам и друзьям преимущества перехода с DVD на blu-ray. Многие люди считают, что фильмы на DVD смотрятся вполне приемлемо, и отличия blu-ray можно заметить только на больших телеэкранах, да и то они не настолько впечатляющие, чтобы переходить на новую технологию. Лучший способ убедить людей в преимуществах blu-ray — одновременно воспроизвести с него и с DVD один и тот же фрагмент на экранах одинакового размера. Поскольку сделать это трудно, можно сравнить таким же способом неподвижные изображения, полученные из фильмов на этих носителях. Изображения должны в точности соответствовать одной сцене. К счастью в вебе есть такие места, как AVSForum (EN), где энтузиасты выкладывают сравнительные материалы. Как правило, это делается для того, чтобы помочь людям определить, какое улучшение они получат при переходе с DVD на blu-ray. Иногда различия очевидны, иногда их нелегко увидеть. Я разработал свой инструмент сравнения, позволяющий увидеть, какие существуют различия, и предоставляющий несколько способов более наглядной демонстрации этих отличий.
Моя утилита позволяет пользователю сравнивать два изображения в трех режимах: двойном, режиме переключения и просмотра в прямоугольнике. Двойной режим позволяет сравнивать находящиеся рядом картинки, отображая часть одной и оставшуюся часть другой. Перемещение мыши влево или вправо уменьшает долю одной картинки и увеличивает долю другой. В режиме переключения пользователь целиком видит одно изображение, а при наведении на него мыши оно заменяется вторым. Последний режим позволяет пользователю определить область, в которой отображается альтернативное изображение. С помощью мышки он может выделить прямоугольник, и приложение заменит эту область соответствующей частью второй картинки. На рисунке ниже показан пример просмотра в прямоугольнике.
Замечания
Мое приложение рассчитано на работу с изображениями с разрешением 1920×1080 (стандарт для фильмов на blu-ray). На DVD разрешение 720×480, но для непосредственного сравнения изображения приводятся к разрешению 1920×1080. Такое преобразование аналогично тому, что происходит при воспроизведении DVD на телевизоре с высокой четкостью 1080p. Хотя реализовать поддержку изображений с другим разрешением особого труда не составляло, для простоты я исходил из предположения, что пользователи будут предоставлять картинки с корректным разрешением. Использование изображений с другим разрешением приведет к некорректному поведению программы.
На момент написания этого материала только что появилась бета-версия Silverlight 3, но с ней приложение не проверялось.
Подготовка
Для работы вам потребуется Visual Studio 2008 SP1 или Visual Web Developer Express SP1. Не забудьте установить инструментальные средства Silverlight 2, они необходимы для создания проекта и его выполнения. Установив необходимое ПО, начните новое приложение Silverlight Application и назовите его BlurayDVDCompare (или как вам больше нравится). Как показано на рис. 1, необходимо выбрать параметр размещения Silverlight-приложения в проекте ASP.NET. В результате будет создан веб-проект и отдельный проект Silverlight.
Разработка интерфейса пользователя
Поскольку мы будем выводить изображения размером 1920×1080, нам надо обеспечить появление полос прокрутки, чтобы отображать весь компонент Silverlight. Для этого надо открыть страницу, содержащую наш компонент Silverlight (BlurayDVDCompareTestPage.aspx) и установить его свойства «Ширина» (Width) и «Высота» Height равными 1920 и 1200 соответственно. Высота в 1200 пикселов нам нужна для размещения дополнительных элементов управления.
1: <asp:Silverlight ID="Xaml1" runat="server"
2: Source="~/ClientBin/BlurayDVDCompare.xap"
3: MinimumVersion="2.0.31005.0" Width="1920" Height="1200" />
Интерфейс пользователя будет достаточно простым. У нас будет два текстовых поля ввода, в которых пользователь сможет задать URL сравниваемых изображений. Также у нас будет пара индикаторов выполнения, показывающих ход загрузки картинок. Кроме того, мы предоставим пользователю три переключателя для выбора одного из режимов сравнения.
Основная часть создаваемого нами интерфейса — это панель с двумя сравниваемыми изображениями. Каждое изображение имеет область вырезания, которая будет использоваться для вывода частей каждого из изображений или для скрытия/показа той или иной картинки. Например, в режиме двойного вывода DualView нам надо отображать часть одного изображения и оставшуюся часть второго. Если не применять область вырезания (clip region), по умолчанию второе изображение перекроет первое. В завершение добавим рамку, которая будет применяться в качестве разделителя между картинками в режиме DualView. В режиме прямоугольника эта рамка будет применяться для рисования прямоугольника.
1: <StackPanel>
2: <Canvas x:Name="canvas" Background="White" Width="1920" Height="1080" Margin="0,5,0,0">
3: <Image x:Name="img1" Stretch="None" >
4: <Image.Clip>
5: <RectangleGeometry Rect="0,0,960,1080" x:Name="rcImg1" />
6: </Image.Clip>
7: </Image>
8:
9: <Image x:Name="img2" Stretch="None" >
10: <Image.Clip >
11: <RectangleGeometry Rect="960,0,1920,1080" x:Name="rcImg2" />
12: </Image.Clip>
13: </Image>
14:
15: <Border x:Name="border" Width="2" Height="1920" Margin="0,0,0,0" BorderBrush="Bisque" BorderThickness="0" />
16: </Canvas>
17: </StackPanel>
Реализация
Поскольку мы хотим предоставить возможность пользователю программы загружать картинки из Веба, мы определили метод LoadImages(), принимающий два URL в качестве параметров. Данным URL соответствуют созданные нами объекты BitmapImage. Метод загрузит изображения асинхронно, и мы присвоим их свойствам Source два UIElements. После загрузки изображения появляются на панели.
C#
1: private void LoadImages(string s1, string s2)
2: {
3: brLoading.Visibility = Visibility.Visible;
4: spLoadingError.Visibility = Visibility.Collapsed;
5:
6: if (string.IsNullOrEmpty(s1) || string.IsNullOrEmpty(s2))
7: {
8: txtLoadingError.Text = "Invalid image location given";
9: brLoading.Visibility = Visibility.Collapsed;
10: spLoadingError.Visibility = Visibility.Visible;
11: return;
12: }
13:
14: prgLoading1.Value = 0;
15: prgLoading2.Value = 0;
16:
17: _bmi1 = new BitmapImage();
18: _bmi2 = new BitmapImage();
19: _bmi1.DownloadProgress += new EventHandler<DownloadProgressEventArgs>(bmi1_DownloadProgress);
20: _bmi2.DownloadProgress += new EventHandler<DownloadProgressEventArgs>(bmi2_DownloadProgress);
21:
22: _bmi1.UriSource = new Uri(s1, UriKind.Absolute);
23: _bmi2.UriSource = new Uri(s2, UriKind.Absolute);
24:
25: img1.Source = _bmi1;
26: img2.Source = _bmi2;
27: }
VB
1: Private Sub LoadImages(ByVal s1 As String, ByVal s2 As String)
2: brLoading.Visibility = Visibility.Visible
3: spLoadingError.Visibility = Visibility.Collapsed
4:
5: If String.IsNullOrEmpty(s1) OrElse String.IsNullOrEmpty(s2) Then
6: txtLoadingError.Text = "Invalid image location given"
7: brLoading.Visibility = Visibility.Collapsed
8: spLoadingError.Visibility = Visibility.Visible
9: Return
10: End If
11:
12: prgLoading1.Value = 0
13: prgLoading2.Value = 0
14:
15: _bmi1 = New BitmapImage()
16: _bmi2 = New BitmapImage()
17: AddHandler _bmi1.DownloadProgress, AddressOf bmi1_DownloadProgress
18: AddHandler _bmi2.DownloadProgress, AddressOf bmi2_DownloadProgress
19:
20: _bmi1.UriSource = New Uri(s1, UriKind.Absolute)
21: _bmi2.UriSource = New Uri(s2, UriKind.Absolute)
22:
23: img1.Source = _bmi1
24: img2.Source = _bmi2
25: End Sub
Теперь надо реализовать управление различными режимами сравнения. Начнем с нашего режима по умолчанию — двойного. Как уже говорилось, в этом режиме оба изображения отображаются рядом таким образом, что представляют единое изображение. Первая картинка видна частично, а ее продолжением является часть второй картинки. Нам необходимо отслеживать движение мыши внутри холста изображений, для чего мы устанавливаем обработчик событий MouseMove. При каждом перемещении мыши мы получаем новые значения координат x и y. Поскольку в двойном режиме нас интересует движение влево/вправо, мы передаем лишь координату х.
C#
1: void canvas_MouseMove(object sender, MouseEventArgs e)
2: {
3: double x = e.GetPosition(canvas).X;
4: double y = e.GetPosition(canvas).Y;
5:
6: if (_comparisonMode == ComparisonMode.DualView)
7: DisplayDualView(x);
8: else if (_comparisonMode == ComparisonMode.Rectangle)
9: {
10: // ...
11: }
12: }
VB
1: Private Sub canvas_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
2: Dim x As Double = e.GetPosition(canvas).X
3: Dim y As Double = e.GetPosition(canvas).Y
4:
5: If _comparisonMode = ComparisonMode.DualView Then
6: DisplayDualView(x)
7: ElseIf _comparisonMode = ComparisonMode.Rectangle Then ' .....
8: End If
9: End Sub
C#
1: private void DisplayDualView(double x)
2: {
3: // Показать первую половину (левую сторону) до xCoord
4:
5: rcImg1.Rect = new Rect(0, 0, x, IMGHEIGHT);
6: // Показать вторую половину (правую сторону) начиная с xCoord
7: rcImg2.Rect = new Rect(x, 0, IMGWIDTH, IMGHEIGHT);
8:
9: // установить разделитель в x
10: Canvas.SetLeft(border, x);
11: // установить верх разделителя
12: Canvas.SetTop(border, 0);
13: }
VB
1: Private Sub DisplayDualView(ByVal x As Double)
2: ' Показать первую половину (левую сторону) до xCoord
3: rcImg1.Rect = New Rect(0, 0, x, IMGHEIGHT)
4: ' Показать вторую половину (правую сторону) начиная с xCoord
5: rcImg2.Rect = New Rect(x, 0, IMGWIDTH, IMGHEIGHT)
6: ' установить разделитель в x
7: Canvas.SetLeft(border, x)
8: ' установить верх разделителя
9: Canvas.SetTop(border, 0)
10: End Sub
В методе DisplayDualView() устанавливается область вырезания картинки таким образом, чтобы первое изображение занимало прямоугольник с координатами от 0 до х, а второе — оставшуюся область. Наша рамка используется для представления границы между картинками, так что всегда понятно, где находится указатель мыши. Обратите внимание: для рамки задана ширина 2 пиксела и высота 1080, чтобы это была одна линия, разделяющая два изображения. Размеры рамки определяются при выборе режима сравнения.
Самый простой в реализации метод сравнения — метод переключения изображений. Мы показываем первую картинку целиком, пока пользователь не наведет на нее указатель мыши. В этот момент изображение заменяется второй картинкой, которая отображается, пока указатель мыши не уйдет за пределы холста.
C#
1: private void DisplaySwapView(bool mouseOver)
2: {
3: if (!mouseOver)
4: {
5: rcImg1.Rect = new Rect(0, 0, IMGWIDTH, IMGHEIGHT);
6: rcImg2.Rect = new Rect(0, 0, 0, 0);
7: }
8: else
9: {
10: rcImg1.Rect = new Rect(0, 0, 0, 0);
11: rcImg2.Rect = new Rect(0, 0, IMGWIDTH, IMGHEIGHT);
12: }
13: }
VB
1: Private Sub DisplaySwapView(ByVal mouseOver As Boolean)
2: If (Not mouseOver) Then
3: rcImg1.Rect = New Rect(0, 0, IMGWIDTH, IMGHEIGHT)
4: rcImg2.Rect = New Rect(0, 0, 0, 0)
5: Else
6: rcImg1.Rect = New Rect(0, 0, 0, 0)
7: rcImg2.Rect = New Rect(0, 0, IMGWIDTH, IMGHEIGHT)
8: End If
9: End Sub
Метод DisplaySwapView() отображает в области вырезания или картинку целиком или ничего, в зависимости от булевской переменной mouseOver. Когда mouseOver равно false, мы видим первую картинку, когда true — вторую. Хотя мы могли бы использовать свойство Visibility для отображения/скрытия картинки, мы используем области для единообразия.
Последний, и наиболее сложный для реализации режим — режим прямоугольника. Поскольку пользователь определяет прямоугольник мышью, нам надо отслеживать движение мыши, состояние ее кнопок и координаты прямоугольника. Кроме того, нам придется отображать прямоугольник в процессе рисования его пользователем, а также изменять в реальном времени обозначенную область. Для упрощения этого процесса мы создали класс MouseRectangle, который будет содержать важную информацию о состоянии мыши, а также размере и координатах прямоугольника.
C#
1: public class MouseRectangle
2: {
3: public bool MouseLeftButtonDown { get; set;}
4: private Rect _rtg;
5:
6: private double? _startX;
7: private double? _startY;
8:
9: public bool RectangleIsDrawn { get; set; }
10: public Rect Rectangle { get { return _rtg;} }
11:
12: public MouseRectangle()
13: {
14: RectangleIsDrawn = false;
15: }
16:
17: public void Reset()
18: {
19: _startX = null;
20: _startY = null;
21: RectangleIsDrawn = false;
22: }
23:
24: public void CalculateRectangle(double x, double y)
25: {
26: if (_startX == null)
27: _startX = x;
28: if (_startY == null)
29: _startY = y;
30:
31: // выбираем меньшую из двух
32: double left = Math.Min(_startX.Value, x);
33: double top = Math.Min(_startY.Value, y);
34:
35: _rtg = new Rect(left, top, Math.Abs(_startX.Value - x), Math.Abs(_startY.Value - y));
36: }
37:
38: public bool InBoundsOfRect(double x, double y)
39: {
40: if (x > _rtg.Left && x < _rtg.Right &&
41: y > _rtg.Top && y < _rtg.Bottom)
42: return true;
43: return false;
44: }
45:
46: }
VB
1: Public Class MouseRectangle
2: Private privateMouseLeftButtonDown As Boolean
3: Public Property MouseLeftButtonDown() As Boolean
4: Get
5: Return privateMouseLeftButtonDown
6: End Get
7: Set(ByVal value As Boolean)
8: privateMouseLeftButtonDown = value
9: End Set
10: End Property
11: Private _rtg As Rect
12:
13: Private _startX? As Double
14: Private _startY? As Double
15:
16: Private privateRectangleIsDrawn As Boolean
17: Public Property RectangleIsDrawn() As Boolean
18: Get
19: Return privateRectangleIsDrawn
20: End Get
21: Set(ByVal value As Boolean)
22: privateRectangleIsDrawn = value
23: End Set
24: End Property
25: Public ReadOnly Property Rectangle() As Rect
26: Get
27: Return _rtg
28: End Get
29: End Property
30:
31: Public Sub New()
32: RectangleIsDrawn = False
33: End Sub
34:
35: Public Sub Reset()
36: _startX = Nothing
37: _startY = Nothing
38: RectangleIsDrawn = False
39: End Sub
40:
41: Public Sub CalculateRectangle(ByVal x As Double, ByVal y As Double)
42: If Not _startX.HasValue Then
43: _startX = x
44: End If
45: If Not _startY.HasValue Then
46: _startY = y
47: End If
48:
49: ' выбираем меньшую из двух
50: Dim left As Double = Math.Min(_startX.Value, x)
51: Dim top As Double = Math.Min(_startY.Value, y)
52:
53: _rtg = New Rect(left, top, Math.Abs(_startX.Value - x), Math.Abs(_startY.Value - y))
54: End Sub
55:
56: Public Function InBoundsOfRect(ByVal x As Double, ByVal y As Double) As Boolean
57: If x > _rtg.Left AndAlso x < _rtg.Right AndAlso y > _rtg.Top AndAlso y < _rtg.Bottom Then
58: Return True
59: End If
60: Return False
61: End Function
62:
63: End Class
Свойство MouseLeftButtonDown позволяет отслеживать нажатие левой кнопки мыши. Объект Rect (_rtg) хранит координаты и размеры нарисованного прямоугольника. В переменных _startX и _startY (это числа с двойной точностью, допускающие пустые значения) хранятся координаты начальной точки, в которой была нажата кнопка мыши для рисования.
Размеры и координаты прямоугольника вычисляются методом CalculateRectangle(). В начале анализируются начальные координаты x, y; если они не установлены, значит рисование прямоугольника не начато. В этом случае создается прямоугольник из 0 пикселов. Если эти значения уже установлены, мы создаем прямоугольник, задействовав входные значения x и y.
Метод InBoundsOfRect() — это вспомогательная функция, сообщающая, находится ли точка с координатами x, y внутри прямоугольника.
При работе в режиме прямоугольника, его рисование начинается, когда пользователь нажимает левую кнопку мыши, и заканчивается, когда он ее отпускает.
C#
1: void canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
2: {
3: _mouseRectangle.Reset();
4: _mouseRectangle.MouseLeftButtonDown = true;
5: }
6:
7: void canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
8: {
9: _mouseRectangle.MouseLeftButtonDown = false;
10: _mouseRectangle.RectangleIsDrawn = true;
11: }
VB
1: Private Sub canvas_MouseLeftButtonDown(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
2: _mouseRectangle.Reset()
3: _mouseRectangle.MouseLeftButtonDown = True
4: End Sub
5:
6: Private Sub canvas_MouseLeftButtonUp(ByVal sender As Object, ByVal e As MouseButtonEventArgs)
7: _mouseRectangle.MouseLeftButtonDown = False
8: _mouseRectangle.RectangleIsDrawn = True
9: End Sub
В режиме прямоугольника при движении мыши по холсту есть два различных состояния. Если кнопка нажата, значит пользователь рисует прямоугольник, и наша задача – обновить его в соответствии с положением курсора относительно холста.
C#
1: ...
2: else if (_comparisonMode == ComparisonMode.Rectangle)
3: {
4: if (_mouseRectangle.MouseLeftButtonDown)
5: DrawRectangle(x,y);
6: else if (_mouseRectangle.RectangleIsDrawn)
7: DisplayRectangleView(x,y);
8: }
9: ...
VB
1: ...
2: ElseIf _comparisonMode = ComparisonMode.Rectangle Then
3: If _mouseRectangle.MouseLeftButtonDown Then
4: DrawRectangle(x,y)
5: ElseIf _mouseRectangle.RectangleIsDrawn Then
6: DisplayRectangleView(x,y)
7: End If
8: End If
9: ...
В методе DrawRectangle() вычисляется прямоугольник на основании текущих координат x, y. Затем, чтобы он был виден, устанавливается рамка на основании свойства Rectangle объекта _mouseRectangle. Затем мы выводим первую картинку целиком, а также ту часть второй картинки, которая соответствует положению прямоугольника и данным о ширине/высоте. В результате на экране видна первая картинка с прямоугольным фрагментом второй.
C#
1: private void DrawRectangle(double x, double y)
2: {
3: _mouseRectangle.CalculateRectangle(x,y);
4:
5: Canvas.SetLeft(border, _mouseRectangle.Rectangle.Left);
6: Canvas.SetTop(border, _mouseRectangle.Rectangle.Top);
7: border.Width = _mouseRectangle.Rectangle.Width;
8: border.Height = _mouseRectangle.Rectangle.Height;
9:
10: rcImg1.Rect = new Rect(0, 0, IMGWIDTH, IMGHEIGHT);
11: rcImg2.Rect = _mouseRectangle.Rectangle;
12: }
VB
1: Private Sub DrawRectangle(ByVal x As Double, ByVal y As Double)
2: _mouseRectangle.CalculateRectangle(x, y)
3:
4: Canvas.SetLeft(border, _mouseRectangle.Rectangle.Left)
5: Canvas.SetTop(border, _mouseRectangle.Rectangle.Top)
6: border.Width = _mouseRectangle.Rectangle.Width
7: border.Height = _mouseRectangle.Rectangle.Height
8:
9: rcImg1.Rect = New Rect(0, 0, IMGWIDTH, IMGHEIGHT)
10: rcImg2.Rect = _mouseRectangle.Rectangle
11: End Sub
Мы также реализовали эффект наведения мыши на прямоугольную область после ее прорисовки. Когда указатель находится вне этой области, все остается как есть. Если же пользователь переместит указатель в область прямоугольника, мы показываем ему исходную картинку, т. е. отображается только первая картинка.
C#
1: private void DisplayRectangleView(double x, double y)
2: {
3: rcImg1.Rect = new Rect(0, 0, IMGWIDTH, IMGHEIGHT);
4:
5: if (_mouseRectangle.InBoundsOfRect(x,y))
6: rcImg2.Rect = new Rect(0, 0, 0, 0);
7: else
8: rcImg2.Rect = _mouseRectangle.Rectangle;
9:
10: }
VB
1: Private Sub DisplayRectangleView(ByVal x As Double, ByVal y As Double)
2: rcImg1.Rect = New Rect(0, 0, IMGWIDTH, IMGHEIGHT)
3:
4: If _mouseRectangle.InBoundsOfRect(x, y) Then
5: rcImg2.Rect = New Rect(0, 0, 0, 0)
6: Else
7: rcImg2.Rect = _mouseRectangle.Rectangle
8: End If
9:
10: End Sub
Заключение и планы на будущее
В завершение нужно отметить, что утилита сравнения изображений с blu-ray и DVD — это достаточно простое Silverlight-приложение, которое вы можете использовать для нахождения отличий в изображениях. Эти изображения необязательно должны быть с blu-ray или DVD, но размеры ориентированы именно на них. Надеюсь, утилита поможет вам в переходе с DVD на blu-ray.
В дальнейшем можно добавить возможность масштабирования изображений, чтобы точнее рассмотреть различия. Еще одно возможное усовершенствование — это создание базы данных для хранения ссылок и добавление элементов управления, которые позволят пользователю переходить к различным фильмам и вариантам сравнения. Я уже начал работать над обоими усовершенствованиями, но не закончил их к моменту публикации этой статьи.
Что точно нуждается в доработке, так это интерфейс пользователя. Я не мастер по выстраиванию графических интерфейсов или созданию xaml-стилей, так что я оставил все по умолчанию.
По-настоящему полезно было бы иметь полное сравнение видео, с реализацией аналогичных режимов сравнения, но здесь главная проблема — поиск или создание материала для сравнения. Кроме того, могут возникнуть проблемы с воспроизведением, поскольку здесь требуются высокопроизводительные процессоры и видеокарты.
Сравнения
Вот несколько URL-ресурсов, которые можно использовать в моей утилите сравнения:
URL изображения стандартной четкости (sd) с DVD / HD (Blu-ray) |
|
Prince Caspian |
https://gmontrone.com/BDCompare/imgs/pc_1_sd.png https://gmontrone.com/BDCompare/imgs/pc_1_hd.png |
Prince Caspian |
https://gmontrone.com/BDCompare/imgs/pc_2_sd.png https://gmontrone.com/BDCompare/imgs/pc_2_hd.png |
Iron Man |
https://gmontrone.com/BDCompare/imgs/im_1_sd.png https://gmontrone.com/BDCompare/imgs/im_1_hd.png |
Iron Man |
https://gmontrone.com/BDCompare/imgs/im_2_sd.png https://gmontrone.com/BDCompare/imgs/im_2_hd.png |
Entrapment |
https://gmontrone.com/BDCompare/imgs/Ent_4_SD.png https://gmontrone.com/BDCompare/imgs/Ent_4_HD.png |
Предупреждение: изображения скопированы с постов в форуме A/V Science Forum: www.avsforum.com .
Благодарности
Я хочу поблагодарить Брайана Пика (Brian Peek (EN)), который ввел меня в сообщество Coding4Fun и нашел время рецензировать мою статью и проверить код программы.