Рисование части изображения WPF
От: Shiho Россия  
Дата: 11.12.11 12:15
Оценка:
Имеется большое изображение и маленькое окно, позволяющее видеть только часть изображения.
Нужно в этом окне уметь рисовать необходимую часть. Также необходима возможность перемещать окно, соответственно видимая часть изображения изменяется.

Как это реализовать с помощью Windows Presentation Foundation?
Re: Рисование части изображения WPF
От: vit_as Россия  
Дата: 11.12.11 19:18
Оценка:
Здравствуйте, Shiho, Вы писали:

S>Имеется большое изображение и маленькое окно, позволяющее видеть только часть изображения.

S>Нужно в этом окне уметь рисовать необходимую часть. Также необходима возможность перемещать окно, соответственно видимая часть изображения изменяется.

S>Как это реализовать с помощью Windows Presentation Foundation?


Можно реализовать свой контрол для отображения больших картинок

    public class ImageDataViewer:Control
    {
        Image m_Image;
        Canvas m_Canvas;
        ScrollViewer m_ScrollViewer;
        ImageData m_Source;
        WriteableBitmap m_wb;
        static ImageDataViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageDataViewer), new FrameworkPropertyMetadata(typeof(ImageDataViewer)));
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            m_Image = this.Template.FindName("PART_Image",this) as Image;
            m_Canvas = this.Template.FindName("PART_Canvas", this) as Canvas;
            m_ScrollViewer = this.Template.FindName("PART_ScrollViewer",this) as ScrollViewer;
            m_ScrollViewer.ScrollChanged += new ScrollChangedEventHandler(m_ScrollViewer_ScrollChanged);
        }

        void m_ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            Canvas.SetLeft(m_Image, e.HorizontalOffset);
            Canvas.SetTop(m_Image, e.VerticalOffset);
            m_Image.Width = e.ViewportWidth;
            m_Image.Height = e.ViewportHeight;
            updateSource();
        }
        public ImageData Source
        {
            get { return m_Source; }
            set 
            { 
                m_Source = value;
                updateSource();
            }
        }
        private void updateSource()
        {
            if (m_ScrollViewer == null)
                return;
            if (m_Source == null)
                return;
            m_Canvas.Width = m_Source.Width;
            m_Canvas.Height = m_Source.Height;
            int w = (int)m_ScrollViewer.ViewportWidth;
            int h = (int)m_ScrollViewer.ViewportHeight;
            int x0 = (int)m_ScrollViewer.HorizontalOffset;
            int y0 = (int)m_ScrollViewer.VerticalOffset;
            if (m_wb == null || m_wb.PixelWidth != w || m_wb.PixelHeight !=h)
            {
                m_wb = new WriteableBitmap(w, h, 96, 96, m_Source.Format, null);
                m_Image.Source = m_wb;
            }
            m_wb.Lock();
            IntPtr ptr = m_wb.BackBuffer;
            int stride = m_wb.BackBufferStride;
            int ch = stride/m_wb.PixelWidth;
            int srcStride = m_Source.Stride;
            for (int y = 0; y < h; y++)
            {
                Marshal.Copy(m_Source.Pixels, x0 * ch + srcStride * (y + y0), ptr, stride);
                ptr += stride ;
            }
            m_wb.AddDirtyRect(new Int32Rect(0, 0, w, h));
            m_wb.Unlock();
        }
    }

Для контрола определяем шаблон по умолчанию
<Style TargetType="{x:Type local:ImageDataViewer}">
        <Setter Property="Template" >
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ImageDataViewer}">
                    <ScrollViewer Name="PART_ScrollViewer" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                        <Canvas Name="PART_Canvas">
                            <Image Name="PART_Image"></Image>
                        </Canvas>
                    </ScrollViewer>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>


Картинку можно подсовывать как из памяти так и с диска или с любого другого Stream'a. Приведу простой пример реализации:


public class ImageData
    {
        public int Width;
        public int Height;
        public int BitsPerPixel;
        public byte[] Pixels;
        public PixelFormat Format;
        public int Stride
        {
            get{return ((((Width) * (BitsPerPixel) +31) & ~31) >> 3);}
        }
        ImageData(int width, int height, int bpp)
        {
            Width = width;
            Height = height;
            BitsPerPixel = bpp;
            Pixels = new byte[Stride * Height];
        }
        public static ImageData Load(string path)
        {
            BitmapImage img = new BitmapImage(new Uri(path));
            ImageData image = new ImageData(img.PixelWidth, img.PixelHeight, img.Format.BitsPerPixel);
            img.CopyPixels(image.Pixels, image.Stride, 0);
            image.Format = img.Format;
            return image;
        }
}

Остается только разместить в окне контрол ImageDataViewer и назначить ему свойство Source.
Re[2]: Рисование части изображения WPF
От: Shiho Россия  
Дата: 11.12.11 21:29
Оценка:
Здравствуйте, vit_as, Вы писали:

_>Можно реализовать свой контрол для отображения больших картинок


_>Для контрола определяем шаблон по умолчанию


_>Картинку можно подсовывать как из памяти так и с диска или с любого другого Stream'a.

_>Остается только разместить в окне контрол ImageDataViewer и назначить ему свойство Source.

Спасибо за полноценную реализацию. Есть некоторые вопросы.
Почему мы каждый раз при обновлении окошка копируем пиксели? Разве нельзя просто сказать WPF, что нужно выводить часть изображения, ограниченную рамкой(Rect)?

Предположим, что изображение нам не известно заранее, оно подгружается прямоугольными частями по мере необходимости.
Хочется иметь некий массив byte[], хранящий всю картинку, и возможность добавления в этот массив новых кусков изображения.
Как реализовать такую функциональность?
Re[3]: Рисование части изображения WPF
От: vit_as Россия  
Дата: 12.12.11 02:42
Оценка:
Здравствуйте, Shiho, Вы писали:



S>Спасибо за полноценную реализацию. Есть некоторые вопросы.

S>Почему мы каждый раз при обновлении окошка копируем пиксели? Разве нельзя просто сказать WPF, что нужно выводить часть изображения, ограниченную рамкой(Rect)?

Можете посмотреть метод System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap

S>Предположим, что изображение нам не известно заранее, оно подгружается прямоугольными частями по мере необходимости.

S>Хочется иметь некий массив byte[], хранящий всю картинку, и возможность добавления в этот массив новых кусков изображения.
S>Как реализовать такую функциональность?

В таком случае, если изображение подгружается отдельными плитками, лучше смотреть в сторону ItemsControl и VirtualizingPanel.
Re[4]: Рисование части изображения WPF
От: Shiho Россия  
Дата: 12.12.11 14:21
Оценка:
Здравствуйте, vit_as, Вы писали:

S>>Спасибо за полноценную реализацию. Есть некоторые вопросы.

S>>Почему мы каждый раз при обновлении окошка копируем пиксели? Разве нельзя просто сказать WPF, что нужно выводить часть изображения, ограниченную рамкой(Rect)?

_>Можете посмотреть метод System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap


Нашел у ImageBrush свойство ViewBox, это вроде то, что нужно.

S>>Предположим, что изображение нам не известно заранее, оно подгружается прямоугольными частями по мере необходимости.

S>>Хочется иметь некий массив byte[], хранящий всю картинку, и возможность добавления в этот массив новых кусков изображения.
S>>Как реализовать такую функциональность?

_>В таком случае, если изображение подгружается отдельными плитками, лучше смотреть в сторону ItemsControl и VirtualizingPanel.


Почему бы не хранить изображение в виде byte[], и использовать WriteableBitmap для добавления новых плиток в изображение?
Re[5]: Рисование части изображения WPF
От: vit_as Россия  
Дата: 12.12.11 15:57
Оценка:
Здравствуйте, Shiho, Вы писали:



S>Нашел у ImageBrush свойство ViewBox, это вроде то, что нужно.


Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память?

S>Почему бы не хранить изображение в виде byte[], и использовать WriteableBitmap для добавления новых плиток в изображение?

Если изображение помещается в памяти, то можно.
Re[6]: Рисование части изображения WPF
От: Shiho Россия  
Дата: 13.12.11 15:02
Оценка:
Здравствуйте, vit_as, Вы писали:

_>Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память?

В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.

S>>Почему бы не хранить изображение в виде byte[], и использовать WriteableBitmap для добавления новых плиток в изображение?

_>Если изображение помещается в памяти, то можно.
Интересен вопрос о быстродействии. В принципе, копирование пикселов будет происходить не так часто, но все же хочется, чтобы работало быстро.
Re[7]: Рисование части изображения WPF
От: vit_as Россия  
Дата: 13.12.11 16:21
Оценка:
Здравствуйте, Shiho, Вы писали:

S>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.

Надо выбирать способ хранения большой картинки, либо целиком в виде одного Bitmap'a, либо в виде набора битмапов (как карты гугла).

S>Интересен вопрос о быстродействии. В принципе, копирование пикселов будет происходить не так часто, но все же хочется, чтобы работало быстро.

По собственному опыту могу сказать, что производительности WritableBitmap хватает, чтобы обновлять картинку размером 1280х1024 30 раз в сек.
Другое дело, что с диска копировать подольше.
Re[7]: Рисование части изображения WPF
От: c-smile Канада http://terrainformatica.com
Дата: 14.12.11 04:55
Оценка:
Здравствуйте, Shiho, Вы писали:

_>>Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память?

S>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.

Проблема в том что bitmaps в общем случае живут в GPU памяти. Т.е. те самые byte[] тебе недоступны.
Re[8]: Рисование части изображения WPF
От: Shiho Россия  
Дата: 18.12.11 09:56
Оценка:
Здравствуйте, vit_as, Вы писали:

_>Здравствуйте, Shiho, Вы писали:


S>>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.

_>Надо выбирать способ хранения большой картинки, либо целиком в виде одного Bitmap'a, либо в виде набора битмапов (как карты гугла).

Храниться все будет в виде одного bitmap'а, представляющего часть большой картинки. При необходимости, подгружаются нужные части картинки и этот Bitmap изменяется.

S>>Интересен вопрос о быстродействии. В принципе, копирование пикселов будет происходить не так часто, но все же хочется, чтобы работало быстро.

_>По собственному опыту могу сказать, что производительности WritableBitmap хватает, чтобы обновлять картинку размером 1280х1024 30 раз в сек.
_>Другое дело, что с диска копировать подольше.

Отлично, такой производительности вполне хватит.

Большое спасибо за помощь!
Re[8]: Рисование части изображения WPF
От: Shiho Россия  
Дата: 18.12.11 10:01
Оценка:
Здравствуйте, c-smile, Вы писали:

CS>Здравствуйте, Shiho, Вы писали:


_>>>Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память?

S>>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.

CS>Проблема в том что bitmaps в общем случае живут в GPU памяти. Т.е. те самые byte[] тебе недоступны.


Да, я уже столкнулся с такой проблемой. Доступ к UI элементу(в данном случае это WriteableBitmap) может иметь только один поток. Существуют ли способы расшарить доступ для других потоков?
Операции изменения WriteableBitmap нужны асинхронные, BackgroundWorker не помогает, у него нет доступа к картинке, поскольку он исполняется в другом потоке.
Dispatcher.BeginInvoke просто ставит операцию в очередь в нужном потоке, но она может выполняться долго, нужно ее запускать в отдельном потоке, но тогда у него не будет доступа...

Есть ли какие-нибудь решения?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.