Jump to content

Alexander Halser

Members
  • Content Count

    70
  • Joined

  • Last visited

  • Days Won

    1

Posts posted by Alexander Halser


  1. We use DropDMG (https://c-command.com/dropdmg/). It's inexpensive and very convenient. It has multiple templates for different installers and creates a signed DMG package by just dropping the app on its window.

    • Like 1

  2. 3 minutes ago, Anders Melander said:

    The problem is that it doesn't give us any way of knowing that the image needs to be rotated be display. 

    Yes, that's the core issue. When loading an image, we must know - or be able to know - if it requires extra handling. Then we can make an informed decision. For myself, the issue is solved with the Jpeg-load-helper unit. Now I have to implement that in VCL as well... 🙂

    • Like 1

  3. I wonder if anyone is interested in a solution... 😉

     

    Just in case you are, here is what I did.

     

    • Neither FMX nor VCL will honor the orientation information in the EXIF header inside the JPEG.
    • For FMX: neither on Windows nor on macOS. And probably not on iOS, either. For Android and Linus, you need to test for yourself, maybe they do.
    • The only solution is to read the EXIF header, extract the Orientation tag and apply the necessary transformations to the image after loading.
    • There are several components for Delphi to read EXIF headers, some do work, some don't. The best known unit is CCR-Exif (https://github.com/Wolfcast/ccr-exif), which does everything and works quite well. It can read and write EXIF tags.
    • I've created my own, because CCR-Exif seems like overkill (after all, I just want to know 1 single byte in the JPEG)


    If you want to display JPEG images correctly in your FMX application, I have published my little unit on SourceForge, It's as small and fast as possible with really minimal overhead.

     

    The first function JPEGRotationFromStream() will work for VCL as well! So you can adapt it and use it for your VCL app, too.

    But FMX has this nice little class called TBitmapSurface, which easily does the Rotate, Mirror and Flip transformations that are required to display the JPEG correctly. So, for FMX this unit goes the full length and delivers a TBitmap that's just right. The unit might be useful for VCL as well, but you have to go an extra mile to apply the transformations.

     

    SourceForge Download:

    https://sourceforge.net/projects/delphi-fmx-jpeg-loader-exif/

     

     

    delphi-fmx-tbitmap-load-jpeg.png

    • Like 4

  4. This is a blank FMX form in Delphi 11 with a TImage control, that loads a JPEG. The JPEG picture was created with a Samsung smart phone in portrait mode. Hence, the picture has a width of 2268 and a height of 4032 pixels. The JPEG's EXIF header states that orientation is portrait.

     

    That's obvious for everyone who looks at the picture, except for Delphi Firemonkey. TBitmap.LoadfromFile() ignores the EXIF orientation altogether and loads the JPEG with its physical dimensions (which are width 4032 and height 2268 pixels). It doesn't rotate the picture after loading.

     

    Has anyone solved this nasty problem for FMX?

    Untitled.png


  5. I have a Delphi FMX app that runs fine on anything from macOS Big Sure to Sonoma. Just on the latest Sequoia release, it doesn't start. It seems, that this has to do with new sandbox restrictions. Any ideas what to change in the entitlements to make it run on Sequoia? The app is signed and notarized, compiled with Delphi 11.3.

     

    This is the crash log I get on Sequoia (from a different mac, not my development machine):

    2024-11-07 08:43:02.786318 <Notice>: Last log repeated 1 times
    2024-11-07 08:43:02.785852 (gui/501/application.SnipSVG.16305011.16305018) <Notice>: internal event: WILL_SPAWN, code = 0
    2024-11-07 08:43:02.785996 (gui/501/application.SnipSVG.16305011.16305018) <Notice>: service state: spawn scheduled
    2024-11-07 08:43:02.785999 (gui/501/application.SnipSVG.16305011.16305018) <Notice>: service state: spawning
    2024-11-07 08:43:02.786053 <Notice>: Coalition Cache Hit: app<application.SnipSVG.16305011.16305018(501)> [985]
    2024-11-07 08:43:02.786105 (gui/501/application.SnipSVG.16305011.16305018) <Notice>: launching: launch job demand
    2024-11-07 08:43:02.787814 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: xpcproxy spawned with pid 1131
    2024-11-07 08:43:02.787872 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: internal event: SPAWNED, code = 0
    2024-11-07 08:43:02.787875 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: service state: xpcproxy
    2024-11-07 08:43:02.787956 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: internal event: SOURCE_ATTACH, code = 0
    2024-11-07 08:43:02.813870 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: service state: running
    2024-11-07 08:43:02.813881 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: internal event: INIT, code = 0
    2024-11-07 08:43:02.813884 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: job state = running
    2024-11-07 08:43:02.814097 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: Successfully spawned SnipSVG[1131] because launch job demand
    2024-11-07 08:43:02.845832 (pid/1131 [SnipSVG]) <Notice>: uncorking exec source upfront
    2024-11-07 08:43:02.845882 (pid/1131 [SnipSVG]) <Notice>: created
    2024-11-07 08:43:03.355264 (pid/1131 [SnipSVG]) <Notice>: shutting down
    2024-11-07 08:43:03.355286 (pid/1131 [SnipSVG]) <Notice>: cleaning up
    2024-11-07 08:43:03.355364 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: exited due to exit(217), ran for 567ms
    2024-11-07 08:43:03.355370 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: service state: exited
    2024-11-07 08:43:03.355381 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: internal event: EXITED, code = 0
    2024-11-07 08:43:03.355386 (gui/501/application.SnipSVG.16305011.16305018 [1131]) <Notice>: job state = exited
    ...

     

    My entitlements list:

     

    app-entitlements.png


  6. Bringing up this topic again...  I have managed to get a stack trace report on macOS, but I am not entirely sure what to do with it.

     

    There is a Windows app called "addr2line.exe", which I assume is supposed to parse the file that contains the debug symbols (for macOS that would be MyAppName.dSYM, right?). Then I have to feed it the addresses that I got from the error log. And it should return unit and line number where the error actually occurred.

     

    However, addr2line.exe doesn't seem to understand the format of the .dSYM file. Is this because it expects a Windows executable? Probably yes. Do I have to do this on the Mac with a macOS version of this tool? If yes, which is it?

     

    My stack trace looks like this:
     

    OS Version: macOS (Version 11.6.7)
    Product Version: 1.0.0
    Product Name: FmxApp
    Platform: OSXARM64
    Config: Debug
    DateTime: 31_10_24 10_41_00
    Log-File: FmxApp-31_10_24-10_41_00.log
    
    Stack trace: 
    00000001024C8000:000000010254298C: FmxApp
    00000001024C8000:0000000102D1EC24: FmxApp
    00000001024C8000:0000000102A20750: FmxApp
    00000001024C8000:0000000102A85428: FmxApp
    00000001024C8000:0000000102A20D98: FmxApp
    00000001024C8000:0000000102C8A50C: FmxApp
    00000001024C8000:0000000102C2A308: FmxApp
    00000001024C8000:0000000102C2A66C: FmxApp
    00000001024C8000:0000000102C17394: FmxApp
    00000001024C8000:00000001024CB438: FmxApp
    0000000192AA5000:0000000192C69DC8: AppKit
    0000000192AA5000:0000000192C6915C: AppKit
    0000000192AA5000:0000000192C68034: AppKit
    0000000192AA5000:0000000192F35194: AppKit
    0000000192AA5000:0000000192AD78A0: AppKit
    00000001024C8000:00000001024CB260: FmxApp
    00000001024C8000:0000000102C10CCC: FmxApp
    00000001024C8000:0000000102C10C14: FmxApp
    00000001024C8000:0000000102C7E464: FmxApp
    00000001024C8000:0000000102D1EF24: FmxApp
    
    Modules: 
    AppKit:
    FmxApp:304f03e2f8d59eb00757a868fa075d26
    
    Title: Application Error
    
    Message: Access violation at address 0000000102D1EE30, accessing address 0000000000000000

     


  7. The Windows D2D implementation in FMX in FMX.Canvas.D2D.pas always assumes a premultiplied alpha. This is probably the source of the problem. Anders Melander can perhaps comment on that with more expertise than I have.

    D2D1_ALPHA_MODE (dcommon.h) - Win32 apps | Microsoft Learn

     

    One option to solve it would be to write a TCustomBitmapCodec for raw PNG data and load/save your PNGs through this codec exclusively. 

     

    If SKIA is an option for you, install SKIA and enable it for your app. SKIA effectively implements its own canvas and its own image codecs, leaving your PNG file as it is. I tested your example image with SKIA enabled and the bitmap stays the same, no matter how often it is loaded and saved. Obviously, the SKIA codecs do a better job.

    • Like 1

  8. IMO the gestures are designed for real touchscreens and not for touchpads. I believe this has never worked with touchpads.

     

    Anyway, I have implemented a solution for myself by patching FMX.Platform.Mac, which was already patched anyway to work around the Sonoma scaling bug on MacOS. This is certainly not for everyone, but works like a charm. It uses the Angle parameter for an alternative zoom gesture (Angle is used in rotation gestures, for zoom it's always zero). Your app needs to respond to this accordingly. Most importantly, it doesn't break regular zoom gestures coming from real touch screens.

     

    procedure TFMXViewBase.magnifyWithEvent(event: NSEvent);
    var
      ...
    begin
      ...
    
      if FGestureControl <> nil then
      begin
        LTouches := event.touchesMatchingPhase(NSTouchPhaseTouching, NSView(Super));
        if LTouches.count >= 2 then
        begin
          LTouchesArray := LTouches.allObjects;
          LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(0));
          LDeviceSize := LTouch.deviceSize;
          FEventInfo.Distance := 0; //reset the distance
          // Find the greatest distance between the touches.
          for I := 0 to LTouches.count - 2 do
          begin
            LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(I));
            LPoint := LTouch.normalizedPosition;
            for J := 1 to LTouches.count - 1 do
            begin
              LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(J));
              LPoint2 := LTouch.normalizedPosition;
    
              Distance := Round(Sqrt(Sqr(LPoint.x * LDeviceSize.width - LPoint2.x * LDeviceSize.width) +
                Sqr(LPoint.y * LDeviceSize.height - LPoint2.y * LDeviceSize.height)));
              if Distance > FEventInfo.Distance then
                FEventInfo.Distance := Distance;
            end;
    
            FEventInfo.GestureID := igiZoom;
            if Supports(FGestureControl, IGestureControl, GestureObj) then
              GestureObj.CMGesture(FEventInfo);
            FEventInfo.Flags := [];
          end
        end
    {ECS/ALEX}
        else if LTouches.count = 0 then
        begin
          FEventInfo.Distance := 0;
          FEventInfo.Angle := event.magnification;
    
          FEventInfo.GestureID := igiZoom;
          if Supports(FGestureControl, IGestureControl, GestureObj) then
            GestureObj.CMGesture(FEventInfo);
          FEventInfo.Flags := [];
        end;
      end
    {ECS/ALEX}
      else
        //send the message up the responder chain
        NSView(Super).magnifyWithEvent(event);
    end;

     

    • Thanks 1

  9. Quote

    Have you investigated whether macOS sends regular touch events in addition to magnifyWithEvent when zooming?

    No, I haven't. 

    But the magnifyWithEvent comes reliably and this is what Delphi actually evaluates. I am not that deep into native MacOS development, so I rather stay with the methods that Delphi already implements.


  10. The problem is in FMX.Platform.Mac:

     

    procedure TFMXViewBase.magnifyWithEvent(event: NSEvent);
    var
      ...
    begin
      ...
    
      if FGestureControl <> nil then
      begin
        LTouches := event.touchesMatchingPhase(NSTouchPhaseTouching, NSView(Super));
    {ECS/ALEX
     Here comes our problem: LTouches.count is zero, that's why the gesture event is not fired.
     I assume that we have to deal with "event.magnification" to determine "FEventInfo.Distance".
    }
        if LTouches.count >= 2 then
        begin
          LTouchesArray := LTouches.allObjects;
          LTouch := TNSTouch.Wrap(LTouchesArray.objectAtIndex(0));
          LDeviceSize := LTouch.deviceSize;
          ...

    I am currently experimenting with the event.magnification value (which is a float value), to match FEventInfo.Distance in a way that the example from the official docs keeps working the way it promises, but doesn't.

     

    However, I have a hard time to believe that this has never worked. If Emba has dedicated examples, there must have been a time when this did work properly. It perhaps depends on the MacOS version. My own version of MacOS that I'm using for testing is relatively old (Big Sur), with a second Mac used by one of my colleagues running Sonoma. The gesture is not fired on either machine, but may have been working on OS versions < Big Sur. 

     

    If it has never worked with touchpad gestures, it might have worked and still work with a real touch screen. Is anyone competent to comment on that - does the igiZoom gesture work as advertised with touch screen Mac (we don't have them here, so I cannot test this)?


  11. Yes, rotate works as described in the docs. Zoom and pan do not. There's no event at all for these.

    The pan gesture (swiping up or down with 2 fingers) obviously gets translated into a mouse wheel message.


  12. 1 minute ago, Sherlock said:

    You should do this only for Windows environments. So be shure to wrap that in appropriate $IFDEFs in case you develop multi platform (what FMX is designed for).

    Sure.

    Winapi.Windows.pas and SetWindowLong would be difficult to compile anyway for MacOS, iOS and Android 😉

     

    Are there any other negative side effects known?


  13. Partial answer: in VCL the behavior is triggered by MainFormOnTaskbar := true;

     

    More detailed answer: it is the param WS_EX_APPWINDOW in the window GWL_EXSTYLE that makes the window appear (automatically set by MainFormOnTaskbar).


    In FMX, the main form can be made visible in the selection list by setting the form's (Windows) window handle manually:
     

    procedure TForm1.Button1Click(Sender: TObject);
    var
      WND: HWND;
    begin
      wnd := FmxHandleToHWND(self.handle);
      SetWindowLong(wnd, GWL_EXSTYLE, GetWindowLong(wnd, GWL_EXSTYLE) or WS_EX_APPWINDOW);
    
      //now the form is visible in the "Also snap..." list
    end;

     

    New question: Any known downside or side effects due to this modification?

    • Like 1

  14. When pressing the Windows key plus the right or left arrow key simultaneously, the window is moved to fill the right or left half of the screen, respectively. This works for FMX forms as well, out of the box. At least with D11, not sure about older FMX versions.

     

    However, when you release the Win key, Windows usually offers (if enabled in global settings) a list of windows to fill the other half of the screen. And this is the issue I am wondering about:

    • A VCL form is shown in this "also snap..." list.
    • An FMX form is not shown.

     

    I assume that either a param in CreateParams is responsible for this behavior, or a Windows message needs to be handled to enable it (which is implemented in VCL but not in FMX for Windows). Does anyone know what causes the FMX window to be hidden and how to overcome it?

     

    snap-vcl.png

    snap-fmx.png


  15. I just realized, more or less by accident, that an FMX TBitmap can load WebP image files. I tested a few of them, but they seem to be all perfectly fine.

    bmp := TBitmap.create;
    bmp.LoadFromFile('C:\Users\Alexander\Pictures\_test.webp');
    
    bmp.SaveToFile('C:\Users\Alexander\Pictures\_test.png'); //works
    bmp.SaveToFile('C:\Users\Alexander\Pictures\_test.webp'); //this fails!

    The load dialog in the IDE does not include webp, so you cannot select it. You can rename a webp image to ".bmp" and load it though. Internally, the TFixedMultiResBitmap converts it to PNG data, which heavily increases the binary image data stored in the form. It's like you had used a PNG in the first place. So, when using webP for UI display, it probably makes sense to store them as RC_DATA resources and load from a resource stream at runtime.

     

    Saving WebP fails with an error.

     

    The webp feature seems to come from the operating system. GDI+ explicitly mentions WEBP among other formats:

    Image File Format Constants (Gdiplusimaging.h) - Win32 apps | Microsoft Learn

     

    Does anyone know since when GDI+ supports WebP? 

    Was this implemented in Windows 8 or Windows 10? I am pretty sure that Windows 7 GDI+ did not support it. Which means that an FMX app that runs on Win7 would not be able to load it at runtime.

     

    fmx-tbitmap-webp.jpg


  16. Registration of the url scheme will open your app, when such a link is being clicked (e.g. "myregisteredprotocol://this&is&url&data=whatever").

     

    However, to receive and decode the url part, your application must implement the IURLEventHandler interface and add an event handler.

    Send me a PM if you need code for that.

×