Перемещение растрового изображения внутри PictureBox

у меня проблема с растровым изображением в C++/CLI. Короче говоря, мне нужно загрузить кусок растрового изображения в PictureBox, отобразить его и переместить на несколько строк ниже. Шаги, которые я делаю, следующие:

1 - Загрузите полное растровое изображение с жесткого диска и получите его параметры (ширину, высоту и т.д.).

2 - Создайте PictureBox, который сможет полностью содержать это растровое изображение.

3 - Создайте растровое изображение, которое я буду использовать в качестве области рисования, и поместите его в PictureBox.

4 - Скопируйте фрагмент (первые 150 строк) исходного растрового изображения в мою область рисования и отобразите его.

5 - Переместите этот фрагмент на несколько строк ниже. (Вот моя проблема).

Когда я перемещаю изображение, у меня возникает ошибка: изображение кажется сдвинутым влево, а справа - левая часть изображения. Я загрузил изображения:

Это фрагмент изображения перед его перемещением, все в порядке. http://i58.tinypic.com/2duf48x.png

Это изображение после перемещения, как вы видите, оно кажется сдвинутым влево. http://i61.tinypic.com/jz9ond.png

Метод, который я использую для «перемещения» изображения:

1 - LockBits и получить anIntPtr, который я конвертирую в unsigned char*. 2 - Используйте memcpy (или memmove) для перемещения чанка.

Вот код, это конструктор класса (конструктор формы Windows) и метод, который я использую для отображения фрагмента и его перемещения:

MyForm(){
    InitializeComponent();
    //Get a test srcIm and it's parameters.
    srcIm = gcnew Bitmap("./img/zhbackground.bmp");
    iWidth = srcIm->Width;
    iHeigth = srcIm->Height;
    pxF = srcIm->PixelFormat;
    Bpp = Image::GetPixelFormatSize(pxF) / 8;

    //Prepare a PictureBox, which will be given as parameter to Windows Form.
    pb = gcnew PictureBox();
    pb->SizeMode = PictureBoxSizeMode::StretchImage;
    pb->Size = Drawing::Size(iWidth, iHeigth);
    pb->Location = Drawing::Point(0, 0);

    //Create draw area and put it into the PictureBox.
    drawArea = gcnew Bitmap(pb->Width, pb->Height, pxF);
    pb->Image = drawArea;

    this->Controls->Add(this->pb);
}

void test(){
    //This is the number of lines of my image chunk.
    int nLines = 150;

    //First, load the first 150 lines of my image (that's on hard drive).
    BitmapData ^srcData = srcIm->LockBits(
        Drawing::Rectangle(0, 0, iWidth, iHeigth),
        ImageLockMode::ReadOnly,
        pxF
    );
    unsigned char *srcStream = (unsigned char*)srcData->Scan0.ToPointer();

    //Prepare the draw area for paint my image chunk.
    BitmapData ^dstData = drawArea->LockBits(
        Drawing::Rectangle(0, 0, iWidth, iHeigth),
        ImageLockMode::ReadWrite,
        pxF
    );
    unsigned char *dAhandler = (unsigned char*)dstData->Scan0.ToPointer();

    //Paint the chunk. (My image's 150 first lines)
    memcpy(dAhandler, srcStream, iWidth * Bpp * nLines);

    //Unlock and refresh to see the image.
    drawArea->UnlockBits(dstData);
    this->pb->Refresh();

    //Wait 1 second...
    Threading::Thread::Sleep(1000);

    //Unlock draw area to move the image.
    dstData = drawArea->LockBits(
        Drawing::Rectangle(0, 0, iWidth, iHeigth),
        ImageLockMode::ReadWrite,
        pxF
    );
    dAhandler = (unsigned char*)dstData->Scan0.ToPointer();

    //First, move the image from the beggining to 150 lines below.
    memcpy(&dAhandler[nLines * iWidth * Bpp], dAhandler, iWidth * nLines * Bpp);

    //Paint the "hole" with black.
    memset(dAhandler, 0, iWidth * Bpp * nLines);

    //Unlock and display the image.
    drawArea->UnlockBits(dstData);
    this->pb->Refresh();

    //Unlock the source image (image load from hard drive).
    srcIm->UnlockBits(srcData);
 }

Я пробовал много способов:

1 - я читал, что если вы используете LockBits(), IntPtr, который вы можете получить, представляет собой массив байтов пикселей, один за другим. Это означает, что у меня не может быть проблем с такими проблемами, как организация растрового изображения в памяти и т. д.

2 - я протестировал memcpy и memmove.

3 - я протестировал простой цикл for.

Ничего не работает, не знаю что делать.

С уважением.


person Dan    schedule 21.10.2014    source источник


Ответы (1)


Я нашел способ сделать это, я публикую его на C# (для C++/CLI практически идентичны). Я не знаю, лучшее ли это решение, но оно работает.

/**
 * Number of lines that will be displaced down. This match with the
 * Height that the Bitmap returned by {@code getNextLines} must have.
 */
int displLines;

/**
 * Destination coordinates of the image chunk that will be 'moved'
 */
Point[] displaceCoords;

/**
 * Source image chunk that will me 'moved'
 */
Rectangle displaceArea;

/**
 * Destination coordinates for the new lines.
 */
Point[] newLineCoords;

/**
 * Dimensions of the rectangle formed by the new lines.
 */
Rectangle newLineArea;

/**
 * Auxiliar buffers for paint the displaced image and the new lines.
 */
Bitmap canvas1, canvas2;

/**
 * Graphics for each buffer (canvas1 and canvas2).
 */
Graphics canvas1Graphics, canvas2Grapchics;

/**
 * Flag indicating which buffer have to be used in a determined time.
 */
bool useCanvas1Graphics;

/**
 * Timer used to refresh the image.
 */
Timer timer;

/**
 * Retrieves the next lines to be painted at the top of
 * the image.
 */
abstract Bitmap getNextLines();

/**
 * Initializes the parameters which will be used to displace
 * the current bitmap in the PictureBox 'displLines' down.
 * 
 */
void InitDisplacingParameters() {
    displaceArea = new Rectangle(0, 0, Width, Height - displLines);
    displaceCoords = new Point[3];
    displaceCoords[0].X = 0;
    displaceCoords[0].Y = displLines;

    displaceCoords[1].X = Width;
    displaceCoords[1].Y = displLines;

    displaceCoords[2].X = 0;
    displaceCoords[2].Y = Height;
}

/**
 * Initializes the parameters use to insert the new lines
 * in the image.
 */
void InitNewLinesParameters() {
    newLineCoords = new Point[3];
    newLineCoords[0].X = 0;
    newLineCoords[0].Y = 0;

    newLineCoords[1].X = Width;
    newLineCoords[1].Y = 0;

    newLineCoords[2].X = 0;
    newLineCoords[2].Y = displLines;   

    newLineArea = new Rectangle(0, 0, Width, displLines);
}

/**
 * WARNING: in .NET framework version 3.0 or less, there is no possible
 * to use Grapchis.DrawImage for paint in the same image that have been used to obtain
 * the Graphics object.
 * 
 * Example, this is not possible:
 * {@code
 *      Graphics g = Graphics.fromImage(image);
 *      f.DrawImage(image, ....);
 *      // ERROR! "Grpahics" can not paint in the same image which has been generated him (Graphics).
 * }
 */
void DisplaceAndPaint(Object sender, EventArgs e) {
    try {
        // Stop paint.
        SuspendLayout();

        // Obtain the proper graphics object.
        Graphics g = useCanvas1Graphics ? canvas1Graphics : canvas2Grapchics;

        // Displace the current image 'desplLines' down.
        g.DrawImage(Image, displaceCoords, displaceArea, GraphicsUnit.Pixel);

        // Get the new lines.
        Bitmap bmp = getNextLines();

        // Draw the new lines in the top of the image.
        g.DrawImage(bmp, newLineCoords, newLineArea, GraphicsUnit.Pixel);

        // Set the just painted image as the display Image.
        Image = useCanvas1Graphics ? canvas1 : canvas2;

        // Next time, the other canvas and graphics will be used.
        useCanvas1Graphics = !useCanvas1Graphics;

        // Paint all.
        ResumeLayout();

        //NOTE: I don't know what is a better idea, do Suspend/Resume layout or 
        // Refresh() at the end.
    } catch(Exception) {
        if (IsRunning)
            Stop();
        MessageBox.Show("Error in LiveView");
    }
}
person Dan    schedule 07.07.2015