Jump to content

XylemFlow

Members
  • Content Count

    75
  • Joined

  • Last visited

Posts posted by XylemFlow


  1. 2 hours ago, Anders Melander said:

    It does complicate things but yes, as you suggested, you could have a structure that contains both an TStream (for persistence) and a TBitmap (for display). The TBitmap would be created and loaded from the TStream once, on-demand, when the image needs to be displayed. This is also the technique used internally by many VCL image containers (e.g. TGIFImage, TPNGImage, TJPEGImage).

    That at least would mean you only "wasted" memory on the bitmaps that were actually displayed.

     

    Not really. The data in the stream is compressed and probably much smaller than the decompressed image data.

    For my application it's very unlikely that image data would not need to be displayed so I wouldn't save much by doing it on demand. At least I would only need to do it for images containing an alpha channel though.

     

    It's true that the stream data is compressed. However, the original image in this case was bit depth 64, which FMX converts to bit depth 32 when loaded. I didn't share that version because it was over 1.2MB, whereas the 32bit version was only 100KB. So if I save it as a stream when it first gets loaded it would still be quite large. I may therefore save it to a stream after it gets loaded into the TBitmap, which would mean that it would still suffer from one level of degradation, but at least it wouldn't continue to lose quality each time the binary was loaded and saved, and would use less memory. 


  2. 1 hour ago, Anders Melander said:

    I don't use FMX and I haven't looked at the source but as far as I can tell, from your the images and description, the problem is that TBitmap premultiplies on load and unpremultiplies on save. The degradation of pixels with alpha<>255 is caused by the inevitable rounding errors. The VCL TBitmap has the same problem if one messes with the AlphaFormat property. Apparently they didn't learn from their first mistake and instead decided to make it worse.

     

    The reason the FMX TBitmap premultiplies the bitmap is probably because it needs that when displaying the bitmap (e.g. when using the Win32 AlphaBlend function).

     

    Anyway, the solution to your problem is to not use TBitmap as a storage container. Use TBitmap only for display of the image and use instead use TMemoryStream or something like it for storage.

    Thank you. The problem is that storing as TMemoryStream requires reading into a TBitmap each time it needs displaying. That would require too much delay for real time graphics. I could keep a copy of each image as a stream and a bitmap but that would double up the memory requirements. That may be the only option though.


  3. 18 hours ago, Anders Melander said:

    Please also post the image as it is after you have saved it.

     

    My guess is that the load+save premultiplies the RGB with the alpha.

    Thanks for the idea. I will look into it. The worst thing is that the issue also happens with TBitmap.LoadFromStream and TBitmap.SaveToStream. This is not surprising since that's what the file load and save functions are using. Note that TBitmap.SaveToStream by default saves as PNG data. Below is code that demonstrates this. I have also attached the resulting first few images, and the last one after 40 load/save cycles showing the issue very clearly.

    procedure TForm1.Button2Click(Sender: TObject);
    var
      bmp : TBitmap;
      i : Integer;
      name : String;
      strm : TMemoryStream;
    begin
      bmp := TBitmap.Create;
      name := 'whiteglower_8bit';
      bmp.LoadFromFile(name + '1.png');
      strm := TMemoryStream.Create;
      for i := 1 to 40 do begin
        strm.Seek(0, 0);
        bmp.SaveToStream(strm);
        strm.Seek(0, 0);
        bmp.LoadFromStream(strm);
        bmp.SaveToFile(name + IntToStr(i+1) + '_stream.png');
      end;
      bmp.Free;
      strm.Free;
    end;

    This is closer to my use case. My application allows users to save their project in a binary file, which can contain image data like this as well as other data. LoadFromStream and SaveToStream are used to load and save the image data. Each time the user loads a project to work on it and saves it, the quality of the image deteriorates (when alpha channels are used as they often are). This is true whether or not the user modified the image data. This is very bad and I need to find a fix soon if possible. I had assumed incorrectly that saving and loading image data would not change the image data.

    whiteglower_8bit2_stream.png

    whiteglower_8bit3_stream.png

    whiteglower_8bit4_stream.png

    whiteglower_8bit5_stream.png

    whiteglower_8bit41_stream.png


  4. I have some code that loads a png image into a TBitmap and saves it again. However, the output is not the same. The quality of the image seems to deteriorate every time the previous image is loaded and then saved again.

    I have attached the PNG image, which contains an alpha channel. The original image has a smooth gradient, but the last one clearly doesn't with clear banding. Does anyone know why this may be? I can perhaps understand why the 2nd image may be different since some aspects of the original PNG may not be supported, but I can't understand why subsequent images would vary to the 2nd image. This would indicate that Delphi FMX cannot correctly load an image that it saved. I'm running on Windows 32 bit and using Delphi 11.2 with default project settings. I'm comparing the images by viewing in Gimp and also by comparing in WinMerge. Even the file sizes vary.

    procedure TForm1.Button1Click(Sender: TObject);
    var
      bmp : TBitmap;
      i : Integer;
      name : String;
    begin
      bmp := TBitmap.Create;
      name := 'whiteglower_8bit';
      for i := 1 to 4 do begin
        bmp.LoadFromFile(name + IntToStr(i) + '.png');
        bmp.SaveToFile(name + IntToStr(i+1) + '.png');
      end;
      bmp.Free;
    end;

     

    whiteglower_8bit1.png


  5. I need to be able to draw several objects to a form or TImage TCanvas, including lines, circles, polygons, text, etc and have them drawn as a group with a single opacity. If I draw them separately then it won't look correct where they overlap.

    I currently do this by drawing the objects to an offscreen TBitmap canvas with full opacity and then drawing the TBitmap to my form or TImage TCanvas with the opacity I want. However, this is slower and uses more memory. I'd also like to use Skia in future, but I understand that drawing to offscreen bitmaps is slower since it doesn't use the GPU.

    Is there a method to group objects in this way in default FMX or Skia without using an offscreen bitmap? 


  6. It seems that this isn't only an issue with TNumberBox. TSpinBox does it too. I think anything descended from TCustomEdit. My best guess is that the control is validated when deleting selected text because there is no caret shown.


  7. Thanks Mike. Setting VertIncrement to 0 solves the first issue. I have also set HorzIncrement to 0 so that dragging selects the text without changing the value. Unfortunately that means that the arrow keys and mouse wheel no longer increment the value, but I was able to implement that myself using the KeyDown and MouseWheel events. So yes, it is a bit of a mess, but otherwise it works ok. One advantage is that it automatically prevents non numeric characters and supports copy and paste. Ctrl+A does work for me.


  8. I've not tried VCL, only FMX. Interesting that it's not happening in VCL though. Also, I'm using 11.2.

     

    I have some boxes ranging from -360 to 360, some from -180 to 180 and some from 0 to 100. They all behave the same way.


  9. When I select all the text in a TNumberBox (Ctrl+A or drag over the text) and hit Delete, the value gets reset to the Min value and the OnChange event is triggered. This is strange because clicking at the end of the number text and deleting all the characters one at a time until none are left doesn't do the same. A user is likely to want to quickly delete the text, type in a new value and hit enter. However, this doesn't work because the value automatically gets reset to the Min value when all the text is deleted at once. Is there any way I can prevent that? Note that I am running for Windows 32-bit.


  10. I have created a TFrame at design time using the form designer. I then create several instances of the frame at run time. Is there a way to set a property of the underlying TFrame for all instances at once instead of having to iterate over all instances? For example, when a user changes the language at run time the captions and hints of the buttons in the TFrame need to be updated for all instances.


  11. I have set the form's Transparent property to True. I have a TImage covering the form, which also has some fully transparent areas. When these transparent areas are clicked the window underneath is selected and the form loses focus. I'd like the form to keep focus and the TImage OnMouseDown event to trigger. So far the only solution I've found is to place a slightly opaque TRectangle over the form, however this is not ideal because it requires unnecessary alpha blending and also changes the colours slightly.


  12. I would set the Form's Transparency property to True.
    https://docwiki.embarcadero.com/Libraries/Sydney/en/FMX.Forms.TForm.Transparency

    Now you can put a TRectangle on the form and align it to client. Set its XRadius and YRadius to round the corners. Set it's Fill colour to that of the form. The rounded areas in the corners will not be considered part of the form. Clicking them will select object behind the form. The down side is that the form's title bar will be missing, but you could perhaps recreate it with some additional controls. But for a splash screen I guess you don't want the title bar anyway.

     

    I have the opposite problem. I have a transparent form and want the transparent areas to be considered part of the form and not select windows behind. The best way I've found is to place a very slightly opaque TRectangle over the form, but this isn't ideal. I'd like a method to make it fully transparent.


  13. Regarding the above code edit. I decided it was better to just comment out that whole part and just leave the following since I don't want the menu to ever interact with the Alt key.

    Result := DefWindowProc(hwnd, uMsg, wParam, lParam);

  14. 12 hours ago, Mike Warren said:

    The new version attached should account for that.

     

    I did think of this as a limitation, but figured it wouldn't be likely to be the case on a form that is using a menu bar. In that case the click would be on the first menu item.

     

    I thought about that last night. The new version uses Application.ActiveForm instead of Application.MainForm.

     

    This is very definitely a kludge. As I said previously, I've moved to using TMainMenu instead of TMenuBar.

     

    TMenuBarAltIssueMW2.zip

    Many thanks again. That has solved a few of the issues. However, the scale issue is now on my other screen. The issue is that I have 2 monitors set to different scales. Your GetScreenScale function always returns the scale of the main screen, not the screen that the active form is on.

     

    I do have a better solution now though, after seeking support from Embarcadero. The solution prevents the Alt key being processed while the mouse button is down, but it does require editing a local copy of FMX.Platform.Win.

     

    Change the code in "function WndProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;"

    for WM_SYSKEYUP to this:

              WM_SYSKEYUP:
                begin
                  // Local Edit: Change to prevent Alt key being processed while left mouse button is down
                  if ((wParam = VK_MENU) or (wParam = VK_F10))
                     and not (PlatformWin.FormInfo[LForm].WasLeftMouseButtonPressed) then
                  begin
                    LForm.EnterMenuLoop;
                    Result := 0;
                  end
                  else
                    Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
                end;

     

    I'm happy to make this change. I will report the bug in the Quality portal so that it hopefully gets fixed in future.


  15. 3 hours ago, Mike Warren said:

    Here's a unit that seems to solve this for me.

     

    All you need to do is add MouseHelper to your form's uses clause. It creates a low priority thread that generates an extra MouseUp event if an Alt key was pressed while the left mouse button is down.

    TMenuBarAltIssueMW.zip

     

    Edit: I should mention I've done minimal testing at this stage. I really need to get back to my other work.

    Many thanks. That does work and it also works when the Alt key is pressed down before the mouse. However, there is a small issue on my screen that has scale set to 125% in display settings. My guess is that the click on the caption bar isn't working in that case. That may be a quick fix to take the screen scale into account. That part may cause other issues though. For example, if the window is set to border style none or transparent mode then there is no caption bar to click. Also I wonder how I would get this to work on a second form that isn't the main form.


  16. Something else I noticed. A breakpoint in TCommonCustomForm.KeyUp is never reached for the Alt key even though a breakpoint in TCommonCustomForm.KeyDown is. For all other keys on the keyboard that I tried, both were reached (even the Alt Gr key). Why is that?

×