1

I am trying to combine the RotationLayer with a regular TBitmapLayer in order to be able to use an ImgView32 layer for all it can be.

So my thoughts were:

  1. I have a TBitmapLayer (I need it to be BitmapLayer because I am doing with it more than just rotating). It is loaded with a BMP image
  2. I place a TGaugeBar on my form (just like the ones from the GR32 examples)
  3. When there is a mouse down on that Gauge, I start doing my computations: I create a RotationLayer where I place the contents of the original BitmapLayer.Bitmap
  4. OnChange of the Gauge, I use the RotLayer object created in MouseDown and give it an angle, and assign the Selection as TBitmap, the RotLayer.Bitmap
  5. On MouseUp I free the temporary RotationLayer object used

So basically: move the image from the actual layer to a temporary rotation layer, do the rotating there, then, when done, move the rotated image back to the BitmapLayer...

So in the end, my logic seems to work, except that I need to do the actual rotation on the BitmapLayer manually using a function provided in another SO question (link bellow). Because it appears that the rotationLayer does not actually rotate the image inside it's Bitmap. It only seems to display it rotated...

Now my problems are:

  1. I need to be able to 'resize' the original TBitmapLayer so it wont crop the rotated image to fit the old BitmapLayer
  2. When the rotationLayer is displayed, I display it onTop of the initial BitmapLayer using BitmapCenter (I could not find another way of positioning it where I want it). However this BitmapCenter seems to have 2 usages: one is positioning the layer, and two is setting the point around which the rotation will be made. How can I still position the rotation layer exactly onTop of the original BitmapLayer and still have the BitmapCenter (center of rotation) in the middle of the Bitmap?
  3. It seems that when I start the rotation, so the rotationLayer is created and loaded with my Bitmap, something happens with the alphaChannel I think, because the image becomes a little darker and translucent I think, everytime I assign the BitmapLayer.Bitmap to the RotationLayer.Bitmap. I noticed that by commenting the MasterAlpha:=200 line the image does not lose it's brightness, but now the empty parts of the Layer's rectangle turn to black when RotationLayer is visible. So it looks bad... Also when I do the MouseUP (so when I assign the rotated Bitmap to the BitmapLayer.Bitmap, some black lines remain visible on the exterior of the rotated image, so in the empty space). Any suggestions on how to keep the original image clean?

Please assist me in solving these 3 problems.

The working code so far is:

procedure TMainForm.myrotMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  l,r,t,b:single;
  flrect:TFloatRect;
begin
    ro:=TRotlayer.Create(imgView.Layers);
    ro.Bitmap:=TBitmap32.Create;
    with ro.Bitmap do
    begin
      BeginUpdate;
      ro.Bitmap.Assign((Selection as TBitmapLayer).Bitmap);
      TLinearResampler.Create(ro.Bitmap);
      //ensure good looking edge, dynamic alternative to SetBorderTransparent
      TCustomResampler(ro.Bitmap.Resampler).PixelAccessMode := pamTransparentEdge;
      ro.BitmapCenter := FloatPoint(-(Selection as TBitmapLayer).Location.Left, -(Selection as TBitmapLayer).Location.Top);
//      MasterAlpha := 200;
      FrameRectS(BoundsRect, $FFFFFFFF);
      DrawMode := dmBlend;
      EndUpdate;
      Changed;
    end;
    ro.Scaled := True;
    (Selection as TBitmapLayer).Bitmap.Assign(ro.Bitmap);
end;

procedure TMainForm.myrotChange(Sender: TObject);
begin
  ro.Angle := myRot.Position;
  (Selection as TBitmapLayer).Bitmap.Assign(ro.Bitmap);
end;

procedure TMainForm.myrotMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
    bmx:=TBitmap32.Create;
    bmx.Assign((Selection as TBitmapLayer).Bitmap);
    RotateBitmap(bmx, -(Round(ro.Angle)), false, clWhite32, true);
    (Selection as TBitmapLayer).Bitmap.Assign(bmx);
    bmx.Free;
    ro.Free;
end;

The RotateBitmap function is picked from this SO question

Also there is a problem when rotating transparent images... Test them on your own using the above code and load some PNG with transparency and you will understand the problem.

Community
  • 1
  • 1
user1137313
  • 2,390
  • 9
  • 44
  • 91
  • I haven't used rotation, so can't answer now to why your first attempt doesn't work. The second attempt messes up because of the constant assignment between the `bmx` and the layers bitmap. How about assign only in mouse down/up? I'm curious and will take a look at this tomorrow. – Tom Brunberg Apr 21 '15 at 20:24
  • So in my second attempt even if I move the assignment to the MouseUp, the bitmap still gets messed up... so it's not that. Please when you have time, try my code so you will understand what the actual problem is. – user1137313 Apr 21 '15 at 20:58
  • On a second thought, what about keeping the original image unrotated but keeping track of angle and only apply the rotation when displayed / printed or if finally combined with other layers? I'm sure there's no way to avoid degradation at every assigning to pixels. I'll try tomorrow. – Tom Brunberg Apr 21 '15 at 21:10
  • The thing is that now, my first approach starts to work... Except that the ro.Bitmap is apparently kept unrotated. I added a new form (StayOnTop) with a TImage32 on it, where I assign the ro.Bitmap. And no matter what angle I have when I do the assignment, the image is shown the same way it was before rotating the layer. So my assumption is that the image itself is not rotated, but it's display. – user1137313 Apr 21 '15 at 21:31
  • So my first approach starts to work, only for some reason if I load inside it a small BMP image (probably little color density and probably resolution I do not know exactly), then it behaves nasty. If I load a JPG image (a picture of some friends), the rotation layer shows up and does the job of rotating, but when I do the mouse-up, and assign the rotation layer bitmap to the BitmapLayer, it looks like nothing happened. So I decided to use a regular image32 to place the resulting rotationLayer.Bitmap to make sure the image gets rotated. And sadly it appears that the Bitmap stays unrotated... – user1137313 Apr 21 '15 at 21:36
  • I managed to trick it to work using the first approach. However I do still need some help with some easier issues: First: the rotation layer, when it shows up, it does not appear exactly on top of the original TBitmapLayer. How do I position it there? Right now it appears at Top, and someValue.Left. It looks annoying to start the rotation and have a second image appear only to be located elsewhere. And 2.: how do I do the resize of the bounds of the original BitmapLayer so when it receives the rotated bitmap it wont crop the corners off? – user1137313 Apr 21 '15 at 21:53
  • So I edited my question and now you can see the working code, and the new problems that remain. Please assist me with them – user1137313 Apr 21 '15 at 22:26
  • I have edited my answer regarding point 2 (positioning ratation layer and bitmap layer) – Tom Brunberg Apr 27 '15 at 14:58

1 Answers1

0

Answers (these questions should really have been separate questions, but since they are closely related I deal with them. Hope I don't get nailed.)

  1. Resizing the TBitmapLayer to accommodate a rotated image. You can do that in your MyRotMouseUp procedure by setting TBitmapLayer.Location. The rotated bitmap has the correct Widthand Height properties. See the code below for an example. Setting the location as shown also maintains it in the center of the ImgView

  2. How to position the TRotationLayer exactly on top of TBitmapLayer and have rotation center in the center of the image. A TRotationLayer is positioned with the Position property. The bitmap rotation center is separately set with the BitmapCenter property.
    Positioning a TRotLayer differs from positioning other layers (f.ex. TBitmapLayer) in that it uses the Position: TFloatPoint poperty vs. Location: TFloatRect for the others. Position defines the centerpoint of the layer.
    As with other layers, the reference coordinate system depends on the Scaled property. If Scaled=False (the default) the reference is to the TImgView32 bounds. If you then have a larger image loaded into the TimgView32.Bitmap, and scroll the image with the scrollbars, the TRotLayer doesn't move with the image. If, on the other hand, Scaled=True the reference is to the TImgView32.Bitmap and the TRotLayer follows the image when scrolling.
    To tie the rotation layer to a bitmaplayer so that they scroll together, set both layers Scaled property to True and change the way you set Location and Position. I have added required modifications to the code.

  3. Image colors change when assigning the bitmap back and forth between the rotation layer and original bitmap.

Yes, I clearly see what you mean with your original code. That is why I suggested in a comment to keep the original image clean (unrotated) and keep track of the angle so that the original image can be used for displaying with only one transformation. There are still some degradation visible, but it doesn't pile up to unusable.

At myRotMouseDown(): The original image (bmo: TBitmap32 in the code) is assigned to the rotation layer (rol: TRotationLayer) bitmap. The bml: TBitmapLayer is hidden (Visible := False). No assigning of any other bitmaps here.

At myRotChange(): The rotation layer angle is changed and a form global variable is updated with the same angle data. No assigning of any other bitmaps here.

At myRotMouseUp(): The original image is assigned to the bitmap layer (bml: TBitmapLayer.Bitmap) and that bitmap is rotated with the RotateBitmap() procedure using the angle that is stored in the form. The bml.Location is updated to accommodate the rotated image. No assigning of any other bitmaps here (bmx: TBitmap32 is not needed). The bml is made visible again.

I also suggest not to save the rotated image, instead just save the angle. That way the original image can remain without any degradation and can simply be displayed at the saved angle when needed.

The code (There was no need to change RotateBitmap() so I don't include that here)
Form fields

  private
    bmo: TBitmap32;     // original bitmap
    bml: TBitmapLayer;
//    bmx: TBitmap32;
    rol: TRotLayer;
    roa: single;        // rotation angle

Methods

procedure TForm9.FormCreate(Sender: TObject);
var
  dstr, srcr: TRect;
  png: TPortableNetworkGraphic32;
begin
  png := TPortableNetworkGraphic32.Create;
  png.LoadFromFile('c:\tmp\imgs\arr-2.png');
  bmo:= TBitmap32.Create;
  bmo.Assign(png);
//  bmo.LoadFromFile('c:\tmp\imgs\arr.bmp');
  png.Free;
  bml := TBitmapLayer.Create(ImgView.Layers);
  bml.Bitmap.SetSize(bmo.Width, bmo.Height);
  bml.Scaled := True;  // !!! Changed to True for synching with rol !!!
  //bml.Location := FloatRect(
  //  (ImgView.Width  - bml.Bitmap.Width) * 0.5,
  //  (ImgView.Height - bml.Bitmap.Height)* 0.5,
  //  (ImgView.Width  + bml.Bitmap.Width) * 0.5,
  //  (ImgView.Height + bml.Bitmap.Height)* 0.5);
  // !!! Change follows to synch scrolling of bml and rol
  bml.Location := FloatRect(
    (ImgView.Bitmap.Width  - bml.Bitmap.Width) * 0.5,
    (ImgView.Bitmap.Height - bml.Bitmap.Height)* 0.5,
    (ImgView.Bitmap.Width  + bml.Bitmap.Width) * 0.5,
    (ImgView.Bitmap.Height + bml.Bitmap.Height)* 0.5);
  dstr := Rect(0, 0, bmo.Width, bmo.Height);
  srcr := Rect(0, 0, bmo.Width, bmo.Height);
  bml.Bitmap.DrawMode := dmBlend;
  bml.Bitmap.Draw(dstr, srcr, bmo.Handle);
end;

procedure TForm9.FormDestroy(Sender: TObject);
begin
  bmo.Free;
end;

procedure TForm9.myRotChange(Sender: TObject);
begin
  rol.Angle := myRot.Position * 3.6;
  roa := rol.Angle;
//  (Selection as TBitmapLayer).Bitmap.Assign(ro.Bitmap);
end;

procedure TForm9.myRotMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  l, r, t, b: single;
  flrect: TFloatRect;
begin
  rol := TRotLayer.Create(ImgView.Layers);
  rol.Scaled := True; // !!! Added for synching with bml
  with rol.Bitmap do
  begin
    BeginUpdate;
    Assign(bmo);
    // rol.Position := FloatPoint(ImgView.Width * 0.5, ImgView.Height* 0.5);
    // !!! Change follows to synch scrolling of bml and rol
    rol.Position := FloatPoint(
      (bml.Location.Right  + bml.Location.Left)*0.5,
      (bml.Location.Bottom + bml.Location.Top)*0.5);
//    rol.Bitmap.Assign((Selection as TBitmapLayer).Bitmap);
    TLinearResampler.Create(rol.Bitmap);
    // ensure good looking edge, dynamic alternative to SetBorderTransparent
    TCustomResampler(rol.Bitmap.Resampler).PixelAccessMode := pamTransparentEdge;
//    ro.BitmapCenter := FloatPoint(-(Selection as TBitmapLayer).Location.Left,
//      -(Selection as TBitmapLayer).Location.Top);
    rol.BitmapCenter := FloatPoint(Width * 0.5, Height * 0.5);
    // MasterAlpha := 200;
    FrameRectS(BoundsRect, $FFFFFFFF);
    DrawMode := dmBlend;
    rol.Angle := roa;
    EndUpdate;
    Changed;
  end;
  bml.Visible := False;
//  (Selection as TBitmapLayer).Bitmap.Assign(ro.Bitmap);
end;

procedure TForm9.myRotMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  bml.Bitmap.Assign(bmo);
  RotateBitmap(bml.Bitmap, -(Round(roa)), True, clWhite32, True);
  //bml.Location := FloatRect(
  //  (ImgView.Width  - bml.Bitmap.Width) * 0.5,
  //  (ImgView.Height - bml.Bitmap.Height)* 0.5,
  //  (ImgView.Width  + bml.Bitmap.Width) * 0.5,
  //  (ImgView.Height + bml.Bitmap.Height)* 0.5);
  // !!! Change follows to synch scrolling of bml and rol
  bml.Location := FloatRect(
    (ImgView.Bitmap.Width  - bml.Bitmap.Width) * 0.5,
    (ImgView.Bitmap.Height - bml.Bitmap.Height)* 0.5,
    (ImgView.Bitmap.Width  + bml.Bitmap.Width) * 0.5,
    (ImgView.Bitmap.Height + bml.Bitmap.Height)* 0.5);
  bml.Bitmap.DrawMode := dmBlend;
  rol.Free;
  bml.Visible := True;
end;

Finally a few screenshots: First, no rotation image as good as it is. Second rotated, the edges are not pixelperfect, but reasonable.

enter image description here

enter image description here

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54