Имеется большое изображение и маленькое окно, позволяющее видеть только часть изображения.
Нужно в этом окне уметь рисовать необходимую часть. Также необходима возможность перемещать окно, соответственно видимая часть изображения изменяется.
Как это реализовать с помощью Windows Presentation Foundation?
Здравствуйте, 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();
}
}
Картинку можно подсовывать как из памяти так и с диска или с любого другого 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.
Здравствуйте, vit_as, Вы писали:
_>Можно реализовать свой контрол для отображения больших картинок
_>Для контрола определяем шаблон по умолчанию
_>Картинку можно подсовывать как из памяти так и с диска или с любого другого Stream'a. _>Остается только разместить в окне контрол ImageDataViewer и назначить ему свойство Source.
Спасибо за полноценную реализацию. Есть некоторые вопросы.
Почему мы каждый раз при обновлении окошка копируем пиксели? Разве нельзя просто сказать WPF, что нужно выводить часть изображения, ограниченную рамкой(Rect)?
Предположим, что изображение нам не известно заранее, оно подгружается прямоугольными частями по мере необходимости.
Хочется иметь некий массив byte[], хранящий всю картинку, и возможность добавления в этот массив новых кусков изображения.
Как реализовать такую функциональность?
S>Спасибо за полноценную реализацию. Есть некоторые вопросы. S>Почему мы каждый раз при обновлении окошка копируем пиксели? Разве нельзя просто сказать WPF, что нужно выводить часть изображения, ограниченную рамкой(Rect)?
Можете посмотреть метод System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
S>Предположим, что изображение нам не известно заранее, оно подгружается прямоугольными частями по мере необходимости. S>Хочется иметь некий массив byte[], хранящий всю картинку, и возможность добавления в этот массив новых кусков изображения. S>Как реализовать такую функциональность?
В таком случае, если изображение подгружается отдельными плитками, лучше смотреть в сторону ItemsControl и VirtualizingPanel.
Здравствуйте, vit_as, Вы писали:
S>>Спасибо за полноценную реализацию. Есть некоторые вопросы. S>>Почему мы каждый раз при обновлении окошка копируем пиксели? Разве нельзя просто сказать WPF, что нужно выводить часть изображения, ограниченную рамкой(Rect)?
_>Можете посмотреть метод System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
Нашел у ImageBrush свойство ViewBox, это вроде то, что нужно.
S>>Предположим, что изображение нам не известно заранее, оно подгружается прямоугольными частями по мере необходимости. S>>Хочется иметь некий массив byte[], хранящий всю картинку, и возможность добавления в этот массив новых кусков изображения. S>>Как реализовать такую функциональность?
_>В таком случае, если изображение подгружается отдельными плитками, лучше смотреть в сторону ItemsControl и VirtualizingPanel.
Почему бы не хранить изображение в виде byte[], и использовать WriteableBitmap для добавления новых плиток в изображение?
S>Нашел у ImageBrush свойство ViewBox, это вроде то, что нужно.
Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память?
S>Почему бы не хранить изображение в виде byte[], и использовать WriteableBitmap для добавления новых плиток в изображение?
Если изображение помещается в памяти, то можно.
Здравствуйте, vit_as, Вы писали:
_>Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память?
В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.
S>>Почему бы не хранить изображение в виде byte[], и использовать WriteableBitmap для добавления новых плиток в изображение? _>Если изображение помещается в памяти, то можно.
Интересен вопрос о быстродействии. В принципе, копирование пикселов будет происходить не так часто, но все же хочется, чтобы работало быстро.
Здравствуйте, Shiho, Вы писали:
S>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.
Надо выбирать способ хранения большой картинки, либо целиком в виде одного Bitmap'a, либо в виде набора битмапов (как карты гугла).
S>Интересен вопрос о быстродействии. В принципе, копирование пикселов будет происходить не так часто, но все же хочется, чтобы работало быстро.
По собственному опыту могу сказать, что производительности WritableBitmap хватает, чтобы обновлять картинку размером 1280х1024 30 раз в сек.
Другое дело, что с диска копировать подольше.
Здравствуйте, Shiho, Вы писали:
_>>Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память? S>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.
Проблема в том что bitmaps в общем случае живут в GPU памяти. Т.е. те самые byte[] тебе недоступны.
Здравствуйте, vit_as, Вы писали:
_>Здравствуйте, Shiho, Вы писали:
S>>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть. _>Надо выбирать способ хранения большой картинки, либо целиком в виде одного Bitmap'a, либо в виде набора битмапов (как карты гугла).
Храниться все будет в виде одного bitmap'а, представляющего часть большой картинки. При необходимости, подгружаются нужные части картинки и этот Bitmap изменяется.
S>>Интересен вопрос о быстродействии. В принципе, копирование пикселов будет происходить не так часто, но все же хочется, чтобы работало быстро. _>По собственному опыту могу сказать, что производительности WritableBitmap хватает, чтобы обновлять картинку размером 1280х1024 30 раз в сек. _>Другое дело, что с диска копировать подольше.
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Shiho, Вы писали:
_>>>Постановка задачи какая? Отображать изображение, которое нельзя полностью держать в памяти? Или просто отображать кусок изображения загруженного в память? S>>В общем, и то и другое. Есть большое изображение, мы можем хранить небольшую его часть. Отображать нужно некоторый кусок. Если у нас он загружен в память, то мы его показываем, а если нет, то необходимо подгрузить нужную часть.
CS>Проблема в том что bitmaps в общем случае живут в GPU памяти. Т.е. те самые byte[] тебе недоступны.
Да, я уже столкнулся с такой проблемой. Доступ к UI элементу(в данном случае это WriteableBitmap) может иметь только один поток. Существуют ли способы расшарить доступ для других потоков?
Операции изменения WriteableBitmap нужны асинхронные, BackgroundWorker не помогает, у него нет доступа к картинке, поскольку он исполняется в другом потоке.
Dispatcher.BeginInvoke просто ставит операцию в очередь в нужном потоке, но она может выполняться долго, нужно ее запускать в отдельном потоке, но тогда у него не будет доступа...