Kind of canvas component


#1

Hi there
In my smal app I’d like to have a seperate part on the main component just for drawing. I could draw directly on the main component. But I’d like to have a seperate coordinate-system.
So I tried to derive a class called canvas from the component-class.
But somehow it’s not possible to paint onto my canvas.
I try to write my paintig-code into canvas::paint(). But nothing happens.
It’s possible to create and add other components on my canvas. But it can’t be painted onto it with the Graphics context.
I tried to run canvas->repaint() in maincomponent::paint() but nothing happens.
Did I forget something? Is it only possible tor draw on the maincomponent?
Can someone give me some advice?
Thanx
Luke


#2

paint() is the function that draws everything on the Component. You’re right to have a sub-component on your mainComponent, because it will be a separate element, and indeed it will have all its coordinates automatically relative to its own top-right corner.

I presume that you know how to add this sub-component to maincomponent, and have made it visible and set its bounds (so that it is positioned on the main component).

What paint() does is provide you with a context to draw on, that being a rectangle of the component’s area. You would use the draw functions in the graphics class on the provided context to place graphics on it. There is no in-built ‘drawing with the mouse’ functionality.

You would need to consider what you want to do: firstly you know it needs to respond to mouse clicks; your Component can do that with the mouseDown(const MouseEvent& e) function. In this function you define the behaviour for when a mouse click happens.

Let’s say you want to just draw a pixel at this point. You know where this point is (the mouseEvent gives you coordinates for the event relative to this component). You want to place a pixel there. That means you also need to have a ‘there’ to put it- which means you need an Image object in your class to represent the canvas itself. Therefore, you want to create an Image member, which you set to the size of your choosing (simplest is your Component size) on instantiation. You would also create a Graphics member, which you would set to paint onto the image.

In your mouseDown function, you can perform draw operations using the Graphics member (thus, performed on your image). It will keep adding to it with each one. To just draw a pixel, you’d call myGraphicsContext.setPixel(e.x, e.y);

Then, in your paint() function, you want to display the contents of the current image to the Component face- this is done using the context provided in the function (g). Thus, g.drawImageAt (&myImage,0,0); would be all you need.

This has no optimisation of course!


#3

just realised that i may have got the wrong end of the stick.

still, i decided to make my suggestion just for fun…

[size=150]Canvas.h[/size]





class Canvas	:	public Component
{
public:

	Canvas ();
	~Canvas ();

	void resized ();
	void paint (Graphics& g);

	void mouseDown (const MouseEvent& e);
	void mouseDrag (const MouseEvent& e);

	void drawPixel (int x, int y);

private:

	Image*		imageData;
	Graphics*	drawContext;

	int previousX, previousY;

	void createNewImage (int width, int height, bool copyExistingData);
	void copyImageToCurrentImageData (Image* sourceImage);

};

[size=150]Canvas.cpp[/size]



Canvas::Canvas ()
{
	imageData = 0;
	drawContext = 0;
}

Canvas::~Canvas ()
{
	if (imageData) deleteAndZero (imageData);
	if (drawContext) deleteAndZero (drawContext);
}

void Canvas::paint (Graphics& g)
{
	if (imageData)
		g.drawImageAt (imageData, 0,0);
}

void Canvas::resized ()
{
	createNewImage (getWidth(), getHeight(), true);
}

void Canvas::mouseDown (const MouseEvent& e)
{
	previousX = e.x;
	previousY = e.y;
	drawPixel (e.x, e.y);
}

void Canvas::drawPixel (int x,int y)
{
	if (imageData && drawContext)
	{
		drawContext->setPixel (x, y);
	}
	repaint ();
}

void Canvas::mouseDrag (const MouseEvent& e)
{
	if (drawContext && imageData)
	{
		drawContext->drawLine (
			(float) previousX, 
			(float) previousY, 
			(float) e.x, 
			(float) e.y );
	}

	previousX = e.x;
	previousY = e.y;

	repaint ();
}

void Canvas::createNewImage (int width, int height, bool copyExistingData)
{
	// delete the drawContext...
	if (drawContext) deleteAndZero (drawContext);

	// do we currently have NO image...?
	if (!imageData)
	{
		// no image, so just create a new one...
		imageData = new Image (Image::RGB,width,height,true);
		// create context for it...
		drawContext = new Graphics (*imageData);
		drawContext->fillAll (Colours::white);
	}
	else
	{
		// store the old one for a moment...
		Image* oldImage = imageData;
		// create a new image in our data pointer...
		imageData = new Image (Image::RGB,width,height,true);
		// create context for it...
		drawContext = new Graphics (*imageData);
		drawContext->fillAll (Colours::white);
		
		// copy old data?
		if (copyExistingData)
		{
			// perform the copy (old into new...)
			copyImageToCurrentImageData (oldImage);
		}
		// delete the old one
		deleteAndZero (oldImage);
	}
}

void Canvas::copyImageToCurrentImageData (Image* sourceImage)
{
	if (imageData && sourceImage)
	{
		int lineStride, pixelStride = 0;

		const uint8* sourceData =
			sourceImage->lockPixelDataReadOnly (
			0,0, jmin (sourceImage->getWidth(), imageData->getWidth()),
			jmin (sourceImage->getHeight(), imageData->getHeight()),
			lineStride, pixelStride);

		imageData->setPixelData (
			0,0,jmin (sourceImage->getWidth(), imageData->getWidth()),
			jmin (sourceImage->getHeight(), imageData->getHeight()), 
			sourceData, lineStride);

		sourceImage->releasePixelDataReadOnly (sourceData);
	}
}

just drawing the pixel leaves big gaps between events, so it draws a line from the previous point to the new one in mouseDrag(). it also renews the image data when it’s resized, copying any existing data.

this isn’t in any way optimised of course, and is quite inefficient. however, it may give you a starting point for developing a system appropriate to your needs.


#4

Thanks for your suggestion!
But I don’t want to draw with the mouse on my canvas. Sory if this wasn’t clear.
I just write my drawing-code into Canvas::paint(Graphics& g). For example in Canvas.cpp:

Canvas::paint(Graphics& g) { g.fillRect(0,0,20,20); }
but it doesent work…
I’ll have a look at your code anyway. Probably I’ll discover something in it that helps me to solve my problem. If not… I’ll still learn something new.

Thanks for your help!!!

Cheers Luke


#5

yeah, i realised after i made my first reply that you never said anything about using the mouse to draw, but the idea sounded quite fun and simple so i thought i’d give it a go regardless! :slight_smile:

any component has the ability to draw upon itself in the paint() function. it needs to have been created (myComp = new MyComp();), added (addAndMakeVisible(myComp):wink: and positioned (myComp->setBounds(x,y,w,h):wink: for it to work though!


#6

I found the failure!

It was this simple:
In Canvas.cpp i wrote

void paint(Graphics& g) { ... }
instead of

void Canvas::paint(Graphics& g) { ... }

Strangely I didn’t get any compiler-errors…
I had a long time to find this smal failure. I was working on it for several hours before I started this thread. But I found it quite fast after I posted my question here.

@haydxn
Thanks a lot for your great help (even if it wasn’t exactly what I needed). I’ll have a look at your mous-painting code lateron…

And thanks a lot for your Juce-Tutorial!!! It’s great! I’m looking forward to oncomming parts. Would be nice to read something about threads.

Cheers Luke


#7