Lazarus

Programming => General => Topic started by: ranny on June 10, 2019, 08:15:49 am

Title: Clipboard problem...
Post by: ranny 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?
Title: Re: Clipboard problem...
Post by: wp 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).
Title: Re: Clipboard problem...
Post by: ranny 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.
Title: Re: Clipboard problem...
Post by: wp 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
Title: Re: Clipboard problem...
Post by: ranny 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"
Title: Re: Clipboard problem...
Post by: wp 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.
Title: Re: Clipboard problem...
Post by: ranny 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.
Title: Re: Clipboard problem...
Post by: wp 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.
Title: Re: Clipboard problem...
Post by: ranny 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.
Title: Re: Clipboard problem...
Post by: wp 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;
Title: Re: Clipboard problem...
Post by: ranny 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. 
Title: Re: Clipboard problem...
Post by: ranny 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!
Title: Re: Clipboard problem...
Post by: wp on June 13, 2019, 01:46:54 pm
What is your operating system?
Title: Re: Clipboard problem...
Post by: ranny 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.
Title: Re: Clipboard problem...
Post by: wp 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.
Title: Re: Clipboard problem...
Post by: ranny on June 14, 2019, 09:16:02 am
Thanks again, the clipboard fix worked perfectly and that's one ticked off my list.

Your additional code regarding text and spheres looks like it will be worth incorporating once I understand it all and get it to fit my application.  However, it's not on the list until my "not being able to calculate the solution" is resolved.

I am solving polynomials which are y:=a0.x^n.z^n+a1.x^n.z^n-1....a(n+1)^2.x^0.z^0.  Its an old routine a put together many years ago and has work perfectly since.  My current cases take 200 points and a degree of fit (n) =4.  My old pre OpenGL program solves easily even up to n=7.  The new program with OpenGL facility is the same program with the OpenGL content added.  Now it works for one case with 204 points and n=3,4,5 but the original case which worked perfectly well before now does not.  It returns nan for the coefficients.  In debugging I saw some numbers in the routine returned -inf but there is absolutely no reason for them to do so if it worked before.

Its beyond me where it has gone wrong.  I am prepared to re-write the lot from scratch if it ultimately fixes everything but thats just a risk of wasted time.

Here is the routine, it uses x,y,z data that are global arrays and returns coefficients in c as a global array.

Code: Pascal  [Select]
  1. procedure polyfit3 (var p,n,en:integer);
  2.     var
  3.     i,j,k,l,m:integer;
  4.     col,row,mat,d:integer;
  5.     aa:extended;
  6.     g:array[0..65,0..65] of double;
  7.     r:array [0..65] of double;
  8.     c1:array [0..65] of double;
  9.     begin
  10.     //p=number of data points, n=degree of fit, er=err no for failures
  11.     //x[], y[], z[] is global data from 1 to p
  12.     //c[]=coefficients returned, global, c[1] is first, i.e. y=c1+c2.x+c3.x^2
  13.     try
  14.     //set error to zero
  15.     en:= 0;
  16.     d:=(n+1)*(n+1);
  17.     //initialise the arrays
  18.     for i:=1 to d do
  19.         begin
  20.         r[i]:=0.0;
  21.         for j:=1 to d do
  22.             begin
  23.             g[i,j]:=0.0;
  24.             end;
  25.         end;
  26.     For i:= 0 To n do
  27.         begin
  28.         For j:= 0 To n do
  29.             begin
  30.             For k:= 1 To p do
  31.                 begin
  32.                 row:= i * (n+1) + j + 1;
  33.                 r[row]:= r[row] + y[k] * power(x[k],i) * power(z[k],j);
  34.                 For l:= 0 To n do
  35.                     begin
  36.                     For m:= 0 To n do
  37.                         begin
  38.                         col:= l * (n+1) + m + 1;
  39.                         g[row,col]:= g[row,col] + power(x[k],l) * power(z[k],m) *
  40.                         power(x[k],i) * power(z[k],j);
  41.                         end;
  42.                     end;
  43.                 end;
  44.             end;
  45.         end;
  46.     //solve simultaneous equations using Gaussian elimination
  47.     mat:= d;
  48.     For i:= 1 To mat - 1 do
  49.         begin
  50.         For j:= i + 1 To mat do
  51.             begin
  52.             aa:= g[j,i] / g[i,i];
  53.             For k:= i To mat do
  54.                 begin
  55.                 g[j,k]:= g[j,k] - (g[i,k] * aa);
  56.                 end;
  57.             r[j]:= r[j] - (r[i] * aa);
  58.             end;
  59.         end;
  60.     //solve constants c1[]by back substitution
  61.     c1[mat]:= r[mat]/g[mat,mat];
  62.     For k:=1 to mat-1 do
  63.         begin
  64.         i:=mat-k;
  65.         For j:= i+1 To mat do
  66.             begin
  67.             r[i]:=r[i]-(c1[j]*g[i,j]);
  68.             end;
  69.         c1[i]:=r[i]/g[i,i];
  70.         end;
  71.     for i:=1 to d do
  72.         begin
  73.         c[i]:=c1[i];
  74.         end;
  75.     exit;
  76.     except
  77.     messagedlg('Calculation error in procedure polyfit3.'+#10+'Check file structure.',mtWarning,[mbok],0);
  78.     end;
  79.     end;
  80.  
Title: Re: Clipboard problem...
Post by: wp on June 14, 2019, 10:37:31 am
Since the OpenGL output has nothing to do with your calculation this is probably a bug which has been there all the time but did not show up.

I did not study your code in detail, but I noticed that arrays are dimensioned to begin with index 0, but in the initialization routine you begin with 1, i.e. the elements g[*,0] and g[0,*], r[0] are undefined. You don't show the declaration of the x,y,z arrays, maybe the same issue exists there too. For me personally, mixing array index bases always has been a source of error.
Title: Re: Clipboard problem...
Post by: ranny on June 18, 2019, 10:25:29 am
I know that the arrays are zero based but zero is never used as an index.  I had changed them all to start from 1 but it makes no difference.  I cannot see where it is going wrong since the same code has worked for years with no issue and now it doesn't!  Looks like I am going to have to start from scratch checking as I go....
Title: Re: Clipboard problem...
Post by: wp on June 18, 2019, 10:40:33 am
I know that the arrays are zero based but zero is never used as an index.
Don't do this, I guarantee that it will go wrong at some place. When you need n elements (1..n) you still have to create the array for n+1 elements! This is very easily overlooked.