Reputation: 2734
i have a problem with a Bitmap in C++/CLI. In short, i have to load a Bitmap chunk to a PictureBox, display it and move it few lines below. The steps i do are these:
1 - Load the full bitmap from the hard drive, and get it's parameters (width, heigth etc.).
2 - Create a PictureBox that be able to contain this Bitmap completely.
3 - Create a Bitmap, that i will use as draw area, and put it inside the PictureBox.
4 - Copy a chunk (first 150 lines) of the initial Bitmap to my draw area and display it.
5 - Move this chunk few lines below. (Here my problem).
When I move the image, i have an error: the image appears moved to left, and the right is the left part of the image. I uploaded images:
This is the image chunk before move it, all ok. http://i58.tinypic.com/2duf48x.png
This is the image after be moved, as you see, it appears moved to left. http://i61.tinypic.com/jz9ond.png
The method i use to "move" the image is:
1 - LockBits and obtain anIntPtr, which I convert to unsigned char*. 2 - Use memcpy (or memmove) to move the chunk.
Here the code, these are a class constructor (Windows Form constructor) and the method i use to display the chunk and move it:
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);
}
I have tested many ways:
1 - I have read that if you use LockBits(), the IntPtr you can obtain is the byte array of the pixels, one followed by another. This means that i can't have problems with issues like Bitmap organization in memory etc..
2 - I have tested memcpy and memmove.
3 - I have tested to do a simple for loop.
Nothing works, I don't know what to do.
Thanks, regards.
Upvotes: 0
Views: 577
Reputation: 2734
I have found a way to do this, I post it in C# (for C++/CLI is practically identical) I don't know if it's the better solution but it works.
/**
* 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");
}
}
Upvotes: 1