Search code examples
androiddelphibitmapfiremonkeydelphi-xe8

Images not showing correctly on android


I have an issue with FMX on Android not showing images correctly.

My app, which is my first ever for Android, has a background and a splash image that are shown upon app start. The splash shows on top of everything, and then a timer makes it go away, and show the background.

Screenshot from Memu emulator:

image

Screenshot from my phone:

image

Source from replicated issue:

http://anbech.me/bgtest/bg_test.zip

I've been coding Pascal since Delphi 6, and I never asked about any coding related issues before this, but this is very new to me.

So far, I've been trying for 3 days to get it working, but without luck.

Currently I'm using RCDATA resources and loading images from there. I tried to even have the images on a different form, as other TImage components, and load from there. I also notice that if the images are set to Align=Client, and WrapMode=Center, then they all get misplaced on the MainForm, but work great on other forms. So now, I tried cropping the images, which are squares, so they'll have the same aspect ratio as the device it runs on.

I could go on, because what I've tried so far is a long talk.

procedure TForm1.FormShow(Sender: TObject);
var
  Bmp, BmpSplash: TBitmap;
  iRect: TRect;
begin
  Load_image_from_resource(Form1.Image_bg, '0_bg');
  Load_image_from_resource(Form1.Image_splash, '0_splash');

  Bmp := TBitmap.Create;
  try
    Bmp.Width := round(Form1.Image_bg.Bitmap.Width * (Form1.ClientWidth / Form1.ClientHeight));
    Bmp.Height := round(Form1.Image_bg.Bitmap.Height);
    iRect.Width := round(Form1.Image_bg.Bitmap.Width * (Form1.ClientWidth / Form1.ClientHeight));
    iRect.Height := Bmp.Height;
    iRect.Left := round((iRect.Height - iRect.Width) / 2);
    iRect.Top := 0;
    Bmp.CopyFromBitmap(Form1.Image_bg.Bitmap, iRect, 0, 0);
    //Form1.Image_bg.Bitmap := nil;
    Form1.Image_bg.Bitmap.Assign(Bmp);
    Form1.Image_bg.Align := TAlignLayout.Client;
    Form1.Image_bg.WrapMode := TImageWrapMode.Stretch;
  finally
    Bmp.DisposeOf;
    Bmp := nil;
  end;

  BmpSplash := TBitmap.Create;
  try
    BmpSplash.Width := round(Form1.Image_splash.Bitmap.Width * (Form1.ClientWidth / Form1.ClientHeight));
    BmpSplash.Height := round(Form1.Image_splash.Bitmap.Height);
    iRect.Width := round(Form1.Image_splash.Bitmap.Width * (Form1.ClientWidth / Form1.ClientHeight));
    iRect.Height := Bmp.Height;
    iRect.Left := round((iRect.Height - iRect.Width) / 2);
    iRect.Top := 0;
    BmpSplash.CopyFromBitmap(Form1.Image_splash.Bitmap, iRect, 0, 0);
    //Form1.Image_splash.Bitmap := nil;
    Form1.Image_splash.Bitmap.Assign(BmpSplash);
    Form1.Image_splash.Align := TAlignLayout.Client;
    Form1.Image_splash.WrapMode := TImageWrapMode.Stretch;
  finally
    BmpSplash.DisposeOf;
    BmpSplash := nil;
  end;

  Form1.Image_bg.SendToBack;
  Form1.Image_bg.Visible := False;
  Form1.Image_splash.BringToFront;
  Form1.Image_splash.Visible := True;
  Form1.Timer1.Enabled := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Form1.Timer1.Enabled := False;
  Form1.Image_bg.Visible := True;
  Form1.Image_splash.Visible := False;    
end;

Solution

  • You were quite close, I think the following peculiarity in the Delphi code probably gave you most headache:

    In your code you have something like this:

    iRect.Width := ...
    iRect.Height := ...
    iRect.Left := ...
    iRect.Top := ...
    

    A TRect (or TRectF) is determined by its four properties: Left, Top, Right, Bottom. When you change the dimensions using width (and height), you call this setter procedure:

    // This is the code in XE7, I do not know if it has been changed later
    procedure TRect.SetWidth(const Value: Integer);
    begin
      Self.Right := Self.Left + Value;
    end;
    

    SetHeight is very similar.

    When you change Left or Top the corresponding fields are simply changed, but not Right nor Bottom. The result is that the width or height also changes.

    Imagine a local uninitialized variable iRect, that might have whatever values for Left and Right. Your code above may end up with very strange TRect rectangles. The fix is to set Left and Top properties first, then either Right and Top or Width and Height.

    To show the image, you required it to follow aspect ratio, which means that slices need to be cut away from the sides of the image when showing it vertically (and from top and bottom when showing it horizontally). I made this with iRect so that it is initialized to the size of the screen, and then offset with half the difference between screen size and image size.

    Here is code to show the image according to screen size. Here is only vertical display, but horizontal would follow same scheme. For brevity I also did not include the splash image, use the same code and your own change logic. The comments in the code should explain my toughts. As you see it is largely very similar as yours, your calculations of iRect.Width and iRect.Left were not right, probably because of the TRect peculiarity.

    // I feel more comfortable with a bmp as temporary storage than the TImage, therefore this minor mod
    procedure Load_Bitmap_From_Resource(var bmp: TBitmap; res_name: String);
    var InStream: TResourceStream;
    begin
      InStream := TResourceStream.Create(HInstance, res_name, RT_RCDATA);
      try
        Bmp.LoadFromStream(InStream);
      finally
        InStream.Free;
      end;
    end;
    
    procedure TForm1.FormShow(Sender: TObject);
    var
      ScrHeight, ScrWidth: integer;
      bmp, bmp2, BmpSplash: TBitmap;
      iRect: TRect;
    begin
      // Form1 acted for me also as a simulation form during dev, therefore
      // these settings are not meant to be included in actual application
      // ***********
      self.Left := 0; self.ClientWidth  := 270;
      self.Top  := 0; self.ClientHeight := 540;
      // ***********
    
      // Use FireMonkey Platform Services, IFMXScreeService to get real size of screen
      ScrHeight := 2160; // just arbitrary examples, ..
      ScrWidth  := 1080; // ..for testing use size of your own phone
    
      // I feel more comfortable with a bmp as temporary storage than the TImage
      bmp := TBitmap.Create;
      try
        Load_Bitmap_From_Resource(bmp, '0_bg');
    
        iRect := Rect(Point(0, 0), Point(ScrWidth, ScrHeight));
        iRect.Offset((bmp.Width-ScrWidth) div 2, (bmp.Height-ScrHeight) div 2);
    
        bmp2 := TBitmap.Create;
        try
          bmp2.SetSize(iRect.Width, iRect.Height);
          bmp2.CopyFromBitmap(bmp, iRect, 0, 0);
          Form1.Image_bg.Bitmap.Assign(bmp2);
          Form1.Image_bg.Align := TAlignLayout.Client;
        finally
          bmp2.DisposeOf;
        end;
      finally
        bmp.DisposeOf;
      end;
    
      Form1.Image_bg.Visible := True;
    end;