Recent

Author Topic: Clipboard problem...  (Read 5695 times)

ranny

  • Jr. Member
  • **
  • Posts: 64
Clipboard problem...
« on: June 10, 2019, 08:15:49 am »
Hi,

For "graphics" I always painted into the image from my main procedure and I see many comments on this forum saying that you should always use the OnPaint event to paint to the canvas for a number of reasons.  I never had a problem before using OnPaint but now I am using the OnPaint procedure I find that the Clipboard.Assign(image1.picture) does not work, all that I get is a blank image.

How to fix?

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #1 on: June 10, 2019, 10:59:01 am »
Please post a simple project which shows the issue (pack *.pas, *.lfm, *.lpi, *.lpr files into a shared zip which you can upload under "Attachments and other options", do not include exe or other compiler-generated files in the zip).

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #2 on: June 10, 2019, 12:58:21 pm »
Hi,

Please see attached.  This demo shows an image on a panel.  Form activation paints it white, the onPaint event for the image draws a black square.  Form shows fine, button1 click copies to clipboard but onlt white panel is in the clipboard image, not he black square.

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #3 on: June 10, 2019, 03:45:25 pm »
TImage is a bit complicated. Image pixel data are loaded into the Bitmap, Icon, or Graphic properties of its Picture. It draws these data on its own canvas, and this way it can rescale or center the image data. The original image data are unchaged.

When you assign Image.Picture to the clipboard you copy the original image data. But you paint on the canvas of the Image (not the canvas of the bitmap, icon or graphic!). Therefore, your drawing does not arrive in the clipboard (I don't understand, though, why the clipboard image is white, not black as I would expect).

Let's make an experiment:
The idea is to paint onto the canvas of Image.Picture.Bitmap. It is created automatically when it is first accessed. The first operation to do is setting the size of the bitmap. Add this code to the OnResize event of the Image because size rarely changes:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Image1Resize(Sender: TObject);
  2. begin
  3.   Image1.Picture.Bitmap.SetSize(Image1.Width, Image1.Height);
  4. end;

Then in the image's OnPaint add your code (I include also the background color of FormActivate, remove FormActivate):
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Image1Paint(Sender: TObject);
  2. begin
  3.   image1.picture.bitmap.canvas.brush.color:=clWhite;
  4.   image1.picture.bitmap.canvas.rectangle(0,0,image1.Width,image1.height);
  5.  
  6.   image1.picture.bitmap.canvas.brush.color:=clBlack;
  7.   image1.picture.bitmap.canvas.rectangle(0,0,50,50);
  8. end;  

Compile, run and click ok. Check the clipboard - voila, the image is as expected.

But there is a severe issue: try to close the program by clicking on the "x" button -- no reaction!

This is because in the special case of TImage a change in the canvas of the bitmap (for example when the brush color is set) calls the PictureChanged method of the image, and this, of course, repaints the image again - which calls your code - which calls another PictureChanged - and so on forever...

Solution:
Do not paint on a TImage. This is a versatile component for displaying images not for painting images. Paint on a bitmap and display the bitmap in a TPaintbox or on a TPanel using their OnPaint events. Look at attached demo. It has a form-global variable FBitmap and a flag FBitmapValid. The flag is always set to false when a new bitmap has to be drawn, for example because the size of the paintbox changes, or another kind of image is to be painted. In the OnPaint event of the paintbox the flag is checked: when it is false a new bitmap is created if it does not yet exist; the size is set accordingly, and the background is painted in the background color.  Then the OnPaint handler jumps to a DrawToCanvas routine which draws the image requested on the canvas of the bitmap. Finally the bitmap is painted onto the Paintbox.

[EDIT]
Replaced the attachment by version which loads also with non-trunk versions of Lazarus
« Last Edit: June 10, 2019, 05:37:56 pm by wp »

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #4 on: June 10, 2019, 04:35:10 pm »
Thanks very much for the time you spent on this.  Unfortunately after unzipping the file the project does not load completely, are there some extra files missing?  I can read the text in a text editor so  I can work it out.

I have always drawn to an image.canvas and never had a problem with previous programs.  I moved to the Image.OnPaint event this time to see if it was faster because I am drawing a lot of filled polygons (>6000) and it becomes sluggish to rotate on the screen.  I can try your suggestion and see if it improves.

I did try a Paintbox and the OnPaint event produced a blank screen.  If I took the drawing out of the OnPaint event it became jittery when moving the image.  So I need to try a couple of different things including your suggestions.

To give you an idea of what I am drawing, take a look at the post on Matrix Calculations I posted a couple of weeks ago, there are some images in there that show my end result.   

[UPDATE] Post is "Matrix unit not giving expected results"
« Last Edit: June 10, 2019, 05:03:28 pm by ranny »

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #5 on: June 10, 2019, 05:47:58 pm »
Sorry. By default I a working with Lazarus trunk where the file structure of some project files has been changed, and I always forget to reset to the old format. Please try again, I repalced the attachment of the previous post with a version that should work.

When rotation and drawing of several thousands of polygons become too slow and sluggish youi should consider switching to OpenGL. It requires some learning, but is worth the effort.

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #6 on: June 11, 2019, 03:22:22 pm »
@WP

I tried your code and struggled a bit to get it to work, but eventually got something to work.  At the end of the day, clipboard functionality or not, the result was drawing in a paintbox with the onPaint event.  The displayed result was not very good.  If the image was rotated you can see every polygon getting drawn on the screen and with lots of polygons, it just wasn't any good.  So I have reverted to drawing on the image and will ignore the clipboard at the moment.

As for OpenGL, I cannot find any good tutorials anywhere for Pascal, the demo that comes with the Lazarus install is way to complicated to pick through and the wiki.freepascal.org/OpenGL_Tutorial is of little help where it talks about OpenGL LCL program then jumps into GLUT.  Plus the example code is not really complete and hard to know (for me anyway) where it should be positioned.

I would like to try it, but there is really no help for such a powerful feature.

Thanks again for your help.

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #7 on: June 12, 2019, 01:29:53 am »
If the image was rotated you can see every polygon getting drawn on the screen and with lots of polygons, it just wasn't any good. 
Then you are doing it differently from what I intended. My intention was to paint everything into a bitmap and draw the bitmap to the paintbox canvas only when the bitmap is complete.  I cannot imagine how this strategy, if drawing gets slow, should show painting of the individual polygons. I do expect, though, that rotation with the mouse will be sluggish.

As for OpenGL, I cannot find any good tutorials anywhere for Pascal, the demo that comes with the Lazarus install is way to complicated to pick through and the wiki.freepascal.org/OpenGL_Tutorial is of little help where it talks about OpenGL LCL program then jumps into GLUT.  Plus the example code is not really complete and hard to know (for me anyway) where it should be positioned.

I would like to try it, but there is really no help for such a powerful feature.
I know this is a bit complicated. I once had found a book in the library, I cannot rember its title, maybe "OpenGL for Delphi", and this was quite good. The most difficult part is to get started, I use the TOpenGLControl which comes with Lazarus in the LazOpenGLContext package, it does all the initialization for embedding an OpenGL control in a form and leaves you with the OnPaint where you can place the OpenGL code.

Of course I cannot give an introduction to OpenGL here, and I am not even very experienced with OpenGL, just wrote a few programs taking advantage of it. To help you I extracted a few routines from these programs and built a 3d function plotter with some simulated data points which might be interesting to you. To modify it for other functions assign the function (x, y) to the FOnCalculate "event"; the x and y ranges are set up by the variables FXMin, FXMax, FYMin and FYMax. The program maps these x/y ranges to the intervals of -1..+1 and the z range to -0.5..0.5 which would free you from adjusting the initial view point ("FDistance", "FRotAngleX" and "FRotAngleY" in FormCreate). At runtime, you can adjust the viewpoint by dragging the mouse/moving the mouse wheel. This demo runs with 200x200 grid points, and even on my poor built-in graphics card there is not slaggisch movement when the figure is rotated or zoomed.

Since this demo is still too complicated to get started I am attaching another demo, simple_opengl_demo, which is based on the 3d-func plotter but is stripped down to the very elemental parts including mouse control. In order to play with OpenGL you just put your own OpenGL code into the method "DrawScene" and the program will take care of the rest.

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #8 on: June 12, 2019, 08:38:28 am »
@WP

Thank you so much for the demos.  It looks like these will lead me to being able to incorporate something into my program that should give excellent results, I'm looking forward to getting stuck in!

I'm not much of a programmer, but I know what I want and generally can handle the maths, but typically the maths is the easy bit and showing the image is the hard bit.

So this is very helpful and I thank you for your time supporting me.

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #9 on: June 12, 2019, 11:27:25 am »
Ah, I forgot to mention the clipboard, the original question... Saving an OpenGL window to a bitmap is not readily found in the usual tutorials. Here is code for it:
Code: Pascal  [Select][+][-]
  1. uses
  2.   gl, Clipbrd, IntfGraphics, GraphType;  
  3.  
  4. procedure OpenGLToBitmap(ABitmap: TBitmap);
  5. const
  6.   GL_BGRA = $80E1;
  7. var
  8.   intfImg: TLazIntfImage;
  9.   viewport: array[0..3] of GLInt;
  10.   rawImg: TRawImage;
  11.   bmpHandle, maskHandle: THandle;
  12. begin
  13.   if ABitmap = nil then
  14.     raise Exception.Create('[OpenGLToBitmap] Bitmap must not be nil.');
  15.  
  16.   // Query size of the viewport
  17.   glGetIntegerv(GL_VIEWPORT, @viewport);
  18.  
  19.   // Prepare a raw image
  20.   rawImg.Init;
  21.   rawImg.Description.Init_BPP32_B8G8R8A8_M1_BIO_TTB(viewport[2], viewport[3]);
  22.   rawImg.CreateData(false);
  23.  
  24.   // Query image data from OpenGL
  25.   glReadPixels(0, 0, viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, rawImg.Data);
  26.  
  27.   // Create LazIntfImage from raw image
  28.   intfImg := TLazIntfImage.Create(viewport[2], viewport[3]);
  29.   try
  30.     intfImg.SetRawImage(rawImg);
  31.  
  32.     // Convert LazIntfImage to Bitmap
  33.     intfImg.CreateBitmaps(bmpHandle, maskHandle);
  34.     ABitmap.Handle := bmpHandle;
  35.     ABitmap.MaskHandle := maskHandle;
  36.   finally
  37.     intfImg.Free;
  38.   end;
  39. end;
  40.  
  41. procedure TForm1.btnCopyToClipboardClick(Sender: TObject);
  42. var
  43.   bmp: TBitmap;
  44. begin
  45.   bmp := TBitmap.Create;
  46.   try
  47.     OpenGLToBitmap(bmp);
  48.     Clipboard.Assign(bmp);
  49.   finally
  50.     bmp.Free;
  51.   end;
  52. end;

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #10 on: June 13, 2019, 08:24:49 am »
@WP

Again, thanks for the support and the clipboard routine will be incorporated.

I made good progress with using OpenGL in my program, and with success as well.  Very pleased with the results and it is very quick to respond to mouse movement.  Thanks for your demo code which was most helpful.

Now my remaining challenge is to try to get text on screen which I see is a bit of a headache but has to be done, and also drawing spheres which without glut seems to be involved as well.  However everything is a learning opportunity.

Many thanks. 

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #11 on: June 13, 2019, 01:29:30 pm »
Interesting.....

The clipboard routine worked in principle, but the image is inverted, like flipped around the horizontal axis.  I assume this is because the y origin is inverted from opengl image and a bitmap.  I found a supposed routine to  invert a bitmap but it came up with an overload error during compile which  I don't know how to fix. Still got further than expected!

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #12 on: June 13, 2019, 01:46:54 pm »
What is your operating system?

ranny

  • Jr. Member
  • **
  • Posts: 64
Re: Clipboard problem...
« Reply #13 on: June 13, 2019, 04:53:05 pm »
Windows 10.

And for some reason where my original program would carry out polynomial fit without any problems, the same file in the new program which uses exactly the same routines now fails and produces nan for the coefficients.  That doesn't make sense to me.  Might have to raise a separate post on that one.

wp

  • Hero Member
  • *****
  • Posts: 11853
Re: Clipboard problem...
« Reply #14 on: June 13, 2019, 06:27:22 pm »
The clipboard routine worked in principle, but the image is inverted, like flipped around the horizontal axis.
Ah, I had too many false pictures yesterday and finally was happy that the colors appeared correctly with one set of parameters so that I did not look at the alignment. But it works correctly when you add the line
Code: Pascal  [Select][+][-]
  1.   rawImg.Description.LineOrder := riloBottomToTop;  
after rawImg.Description.Init_... in "OpenGLToBitmap".

Now my remaining challenge is to try to get text on screen which I see is a bit of a headache but has to be done
Yes, drawing text by itself is not supported by OpenGL directly. Since you are on Windows you can use the routines in the attached "OpenGL_utils" units. The font routines work only on Windows; in other operating systems you'd have to access the fonts directly, for example by using the FreeType lib which comes with FPC/Laz - in this case the OpenGL support in TAChart could be helpful (units TAOpenGL and TAFonts).

But even when the fonts can be accessed the path to writing something into an OpenGL image still is stoney. I am attaching another modfied demo which shows how to write some 2D text into the 3D image. For this purpose OpenGL must temporarily be switched to an orthographic projection. The basic text out routine in the OpenGL_utils uses screen pixels (with the specialty that the y coordinate runs from bottom to top, not the other way as usual). In this mode you can add titles, legends or whatever (see procedure "DrawTitle")

An interesting case would be to attach the text to the drawing so that it follows the model when it is rotated, shifted or zoomed. An excample for the conversion from world coordinates to screen pixels is found in "DrawTextAtCubeCorners".

Note that the OpenGL system is in 2D mode when drawing this kind of text. Therefore labels are drawn even when the associated cube corner is not visible. I am sure that there is a way to fix this...

You can also draw the text in 3D mode (by using "Outline fonts" which give access to the individual nodes of the glyphs), but this requires a lot of additional work because the text can become almost illegible (upside down, mirrored) when the model is rotated.

and also drawing spheres which without glut seems to be involved as well.
I had to deal with spheres in a recent project and am adding the related procedure to the "OpenGL_Tools" as well. It creates the vertices on longitude and latitude circles and stores them in a "DisplayList" which is very useful because then the calculation of the vertices must be done only once. The calculated sphere has unit radius and sits in the origin. To give it the size required you must apply a Scaling operation ("glScalef(..)'), and a translation to move it to the desired location ("glTranslate(...)"). The way a lot of spheres can be drawn! Be careful to apply these operations in the right order - scale first, then translate. BUT: in OpenGL you write them in the opposite order - you must read OpenGL code from bottom upward! This is due to the way the transformation matrices are multiplied to the current overall transformation matrix.

Code: Pascal  [Select][+][-]
  1.       glPushMatrix;
  2.         glTranslatef(x, 0, y);     // Move sphere to correct place at x, y and z=0
  3.         glScalef(0.5, 0.5, 0.5);   // Make radius = 0.5
  4.         glCallList(FSphereList);   // use the stored sphere vertices from the displaylist
  5.       glPopMatrix;

The surrounding calls to "glPushMatrix" and "glPopMatrix" are needed to decouple these operations from the overal transformation matrix given by the mouse and mouse wheel opeations.

A further remark on DisplayLists: You should use them also to store the vertices of the polynomial surface to which you want to fit; this avoids recalculation of the vertices with every tiny mouse movement. And in particular, it avoids time-consuming calculation of the surface normal vectors. In case of a polynomial you can do this analytically, but for more general surfaces you can calculate the needed derivatives only numerically.

Spheres appear very simplistic when OpenGL does not use lighting. Therefore, the demo also shows how to "turn on the light". These are the essential ingredients: activate lights by calling "glEnable(GL_LIGHTING)", and define the parameters of one or several light sources (see "InitLight"). And every vertex must have a normal - for a sphere with r = 1 it is easy: it is just equal to the radius vector to a surface point. The surface normal is needed to calculate the angle to the light source which determines the brightness of each surface element due to diffuse or specular scattering of light.

In total: a lot to play with...

P.S.
The OpenGL_Tools contain also the corrected OpenGLToBitmap routine.

 

TinyPortal © 2005-2018