Net-Base Iris

03.06.2026

Scanóir QR i Delphi FMX: scanáil ceamara seasmhach, sábháilte do shnáitheanna agus gan crith sa chomhéadan úsáideora

Tá scanóir Cód QR Delphi FMX atá oiriúnach don chleachtas ag brath go díreach ar shaolré an cheamara, ar threading agus ar Stop/Start glan. Taispeánann an t-alt cur chuige seasmhach le ZXing, Debounce, Frame-Throttling, gearradh ROI, chomh maith le mionsonraí dífhabhtaithe agus oibríochta do Android agus iOS.

03.06.2026

Ó théama an iris go cleachtas tionscadail

Leathanaigh seirbhíse agus teicniúla oiriúnacha don alt

Scanóir Cód QR Delphi FMX sa chleachtas

Scanóir Cód QR Delphi FMX comhdhéanta go tapa sa dhráma: réamhamharc ceamara a thaispeáint, Bitmap a fháil, ZXing a rith air. I mbogearraí gnó i ndáiríre (m.sh. faomhadh earraí, socrú feistí, bainistíocht ticéad, próisis rochtana) tagann coinníollacha imeachta breise: téann an aip go cúlra, cailltear fócas an cheamara, coinníonn an t-úsáideoir an gléas claonta, athraíonn formáid an íomhá – agus go tobann scanálann tú an cód céanna dhá uair sa soicind nó tá an comhéadan (UI) ag léim toisc go ritheann an díscódú i snáithe an UI.

Is minic nach é “ní féidir le ZXing a léamh” an fhadhb, ach an saolré agus an ailtireacht: scaoileadh acmhainní an cheamara, rialú ráta na frámaí, sábháilteacht snáitheanna maidir le rochtain ar TBitmap (GPU/CPU), agus Stop/Start soiléir a oibríonn go glan fiú nuair a nascleaníonn úsáideoirí go tapa nó má thógaineann an OS an ceamara go sealadach.

Forbhreathnú ailtireachta: Píblíne seachas „OnSampleBufferReady macht alles”

Tá píblíne bheag le freagrachtaí soiléire thar a bheith praiticiúil:

  • Oiriúnóir ceamara: soláthraíonn sé frámaí (nó cóipeanna díobh) i bhformáid shainithe.
  • Díghlódóir: oibríonn sé i snáithe cúlra agus tugann sé torthaí ar ais trí callback.
  • Gate/Debounce: coinníonn sé seach-scananna agus rialálann sé an ualach (throttle).
  • Sraith UI: taispeánann sí réamhamharc, bosca fócas roghnach (ROI, „Region of InteREST“) agus freagraíonn sí do thoradh.

Sa chaoi sin seachnaítear go gcuirfidh an UI, an ceamara agus an díghlódóir bac ar a chéile. Ciallaíonn „ROI“ anseo fuinneog chuardaigh shainithe (m.sh. lárnach 60 %) a laghdaíonn ualach an díghlódóra agus a ísliú torthaí dearfacha bréagacha. Tábhachtach: is uirlis feidhmíochta agus inúsáidteachta í an ROI, ní meicníocht shlándála.

Source-Schnipsel: Robuster QR Code Scanner (FMX + ZXing) mit Debounce und sauberem Stop

Tá an cód thíos beartaithe mar bhloic thógála compacáideach, ach oiriúnach do thionscadail. Úsáideann sé ZXing (Delphi-Port) trí ZXing.ScanManager agus ceanglaíonn sé le TCameraComponent.OnSampleBufferReady. Tá trí phointe ríthábhachtach:

  • Bíonn frámaí throttled (ní gach sampla a dhíghrúdú).
  • Ní rithtear an díscódú sa snáithe UI.
  • Tá Stop/Start idempotent (is féidir é a ghlaoch arís agus arís eile gan caos acmhainní).
Delphi
unit UQrScanner;

interface

uses
  System.SysUtils, System.Classes, System.Types, System.UITypes, System.SyncObjs,
  System.Diagnostics, System.Threading,
  FMX.Types, FMX.Graphics, FMX.Media,
  ZXing.BarcodeFormat, ZXing.ReadResult, ZXing.ScanManager;

type
  TQrScanResultEvent = reference to procedure(const AText: string);

  /// <summary>
  /// Stiúrthóir scanóra QR do FMX (Android/iOS).
  /// Bainistíonn sé frame-gating an cheamara, dí-chódú sa chúlra agus stopáil/tosaithe glan.
  /// </summary>
  TQrScannerController = class
  private
    FCamera: TCameraComponent;
    FScanManager: TScanManager;
    FBitmap: TBitmap;
    FLock: TObject;

    FOnResult: TQrScanResultEvent;

    // Gating/Throttle
    FIsRunning: Boolean;
    FIsDecoding: Integer; // 0/1 als Interlocked-Flag
    FLastDecodeTick: Int64;
    FMinIntervalMs: Cardinal;

    // Debounce gegen wiederholte gleiche Codes
    FLastText: string;
    FLastTextTick: Int64;
    FDebounceMs: Cardinal;

    // ROI: Anteil des Bildes, der gescannt wird (0..1)
    FEnableRoi: Boolean;
    FRoiScale: Single;

    procedure CameraSampleBufferReady(Sender: TObject; const ATime: TMediaTime);
    function ShouldDecodeNow(const ANowTick: Int64): Boolean;
    function IsDebounced(const AText: string; const ANowTick: Int64): Boolean;
    function ExtractRoiBitmap(const ASrc: TBitmap): TBitmap;

    procedure DoResultOnMainThread(const AText: string);

  public
    constructor Create(const ACamera: TCameraComponent);
    destructor Destroy; override;

    procedure Start;
    procedure Stop;

    property MinIntervalMs: Cardinal read FMinIntervalMs write FMinIntervalMs; // m.sh. 120
    property DebounceMs: Cardinal read FDebounceMs write FDebounceMs;         // m.sh. 1200
    property EnableRoi: Boolean read FEnableRoi write FEnableRoi;
    property RoiScale: Single read FRoiScale write FRoiScale;                 // m.sh. 0.6

    property OnResult: TQrScanResultEvent read FOnResult write FOnResult;
  end;

implementation

uses
  System.Math;

{ TQrScannerController }

constructor TQrScannerController.Create(const ACamera: TCameraComponent);
var
  Formats: TArray<TBarcodeFormat>;
begin
  inherited Create;
  FLock := TObject.Create;

  FCamera := ACamera;
  FCamera.OnSampleBufferReady := CameraSampleBufferReady;

  // ScanManager initialisieren und auf QR beschränken (Performance + weniger False Positives)
  Formats := TArray<TBarcodeFormat>.Create(TBarcodeFormat.QR_CODE);
  FScanManager := TScanManager.Create(Formats);

  FBitmap := TBitmap.Create;
  FMinIntervalMs := 120;
  FDebounceMs := 1200;
  FEnableRoi := True;
  FRoiScale := 0.6;

  FLastDecodeTick := 0;
  FLastText := '';
  FLastTextTick := 0;
  FIsDecoding := 0;
  FIsRunning := False;
end;

destructor TQrScannerController.Destroy;
begin
  Stop;
  FBitmap.Free;
  FScanManager.Free;
  FLock.Free;
  inherited;
end;

procedure TQrScannerController.Start;
begin
  if FIsRunning then
    Exit;
  FIsRunning := True;

  // Ceamara a chumasú: in iarratais réalaíocha seiceáil ceadanna roimh ré (Android) agus cuir sruth UI san áireamh.
  if Assigned(FCamera) then
    FCamera.Active := True;
end;

procedure TQrScannerController.Stop;
begin
  if not FIsRunning then
    Exit;
  FIsRunning := False;

  // Díchumasú glan
  if Assigned(FCamera) then
    FCamera.Active := False;

  // Decoder-Flag zurücksetzen, falls Stop in einer ungünstigen Phase kommt
  TInterlocked.Exchange(FIsDecoding, 0);
end;

function TQrScannerController.ShouldDecodeNow(const ANowTick: Int64): Boolean;
begin
  // Throttle: nicht jedes Frame dekodieren
  Result := (ANowTick - FLastDecodeTick) >= FMinIntervalMs;
  if Result then
    FLastDecodeTick := ANowTick;
end;

function TQrScannerController.IsDebounced(const AText: string; const ANowTick: Int64): Boolean;
begin
  Result := False;
  if AText = '' then
    Exit(True);

  // gleicher Text innerhalb Debounce-Fenster - ignorieren
  if SameText(AText, FLastText) and ((ANowTick - FLastTextTick) <= FDebounceMs) then
    Exit(True);

  FLastText := AText;
  FLastTextTick := ANowTick;
end;

procedure TQrScannerController.CameraSampleBufferReady(Sender: TObject; const ATime: TMediaTime);
var
  NowTick: Int64;
  LocalCopy: TBitmap;
begin
  if not FIsRunning then
    Exit;

  NowTick := TThread.GetTickCount64;
  if not ShouldDecodeNow(NowTick) then
    Exit;

  // Nur ein Decode gleichzeitig (sonst Queue-Stau bei schwachen Geräten)
  if TInterlocked.CompareExchange(FIsDecoding, 1, 0) <> 0 then
    Exit;

  // Kamera-Sample in FBitmap kopieren. Lock, weil derselbe Bitmap-Buffer nicht parallel benutzt werden soll.
  TMonitor.Enter(FLock);
  try
    FCamera.SampleBufferToBitmap(FBitmap, True);
    LocalCopy := TBitmap.Create;
    try
      LocalCopy.Assign(FBitmap);
    except
      LocalCopy.Free;
      raise;
    end;
  finally
    TMonitor.Exit(FLock);
  end;

  // Hintergrund-Decoding
  TTask.Run(
    procedure
    var
      ScanBmp: TBitmap;
      Res: TReadResult;
      Text: string;
      Tick: Int64;
    begin
      try
        Tick := TThread.GetTickCount64;

        if FEnableRoi then
          ScanBmp := ExtractRoiBitmap(LocalCopy)
        else
          ScanBmp := LocalCopy;

        try
          Res := FScanManager.Scan(ScanBmp);
          if Assigned(Res) then
            Text := Res.Text
          else
            Text := '';
        finally
          if ScanBmp <> LocalCopy then
            ScanBmp.Free;
        end;

        if (Text <> '') and (not IsDebounced(Text, Tick)) then
          DoResultOnMainThread(Text);

      finally
        LocalCopy.Free;
        TInterlocked.Exchange(FIsDecoding, 0);
      end;
    end);
end;

function TQrScannerController.ExtractRoiBitmap(const ASrc: TBitmap): TBitmap;
var
  R: TRectF;
  W, H: Single;
  RoiW, RoiH: Single;
  X, Y: Single;
begin
  // ROI mittig ausschneiden: reduziert Rechenlast und lenkt den Nutzer.
  // Achtung: bei sehr kleinen QR-Codes kann ROI zu eng sein.
  W := ASrc.Width;
  H := ASrc.Height;

  RoiW := Max(16, W * EnsureRange(FRoiScale, 0.2, 1.0));
  RoiH := Max(16, H * EnsureRange(FRoiScale, 0.2, 1.0));

  X := (W - RoiW) / 2;
  Y := (H - RoiH) / 2;
  R := TRectF.Create(X, Y, X + RoiW, Y + RoiH);

  Result := TBitmap.Create(Round(RoiW), Round(RoiH));
  Result.Canvas.BeginScene;
  try
    Result.Canvas.Clear(TAlphaColors.Black);
    Result.Canvas.DrawBitmap(ASrc, R, TRectF.Create(0, 0, Result.Width, Result.Height), 1.0, True);
  finally
    Result.Canvas.EndScene;
  end;
end;

procedure TQrScannerController.DoResultOnMainThread(const AText: string);
begin
  if not Assigned(FOnResult) then
    Exit;

  // UI-Thread: Navigation, Beep, Feld füllen etc.
  TThread.Queue(nil,
    procedure
    begin
      if FIsRunning and Assigned(FOnResult) then
        FOnResult(AText);
    end);
end;

end.

Cad a réitíonn an cód (agus cén fáth atá sé riachtanach)

Throttle (MinIntervalMs) laghdaíonn ualach CPU agus teasghiniúint. Gan teorainn, bíonn iarracht ag cuid de na gairis 30–60 fráma/s a dhíchódú; sa phraiticiúil tá 5–10/s leordhóthanach, go minic níos lú. Debounce (DebounceMs) coiscíonn sé go ndéanfar QR-Code atá á choinneáil go seasmhach a spreagadh iolrach (m.sh. logáil dúbailte i gcéim phróisis).

Coinníonn an Interlocked-Flag (FIsDecoding) gur tasc díchódaithe amháin a bheidh ar siúl ag an am. Is cleas ailtireachta é seo i gcoinne „Queue-Stau“: má mhaireann an díchódú 200 ms, ach tosófar tasc gach 120 ms, méadóidh an snáithe feithimh agus beidh torthaí scaipthe ama, rud a dhéanann cuma i bhfeidhmíocht mar „freagra mícheart ón scanner“.

Teorainneacha agus constaicí

  • TBitmap agus Threading: Is féidir le FMX-Bitmaps a bheith tacaithe ag GPU. Cóipeálann an cur chuige an fráma chuig Bitmap áitiúil agus déantar an díchódú sa chúlra. Ag brath ar an leagan/ardán de Delphi d’fhéadfadh sé a bheith riachtanach a bheith cúramach: má fheiceann tú artefacta, éiligh Bitmap ar an CPU (m.sh. trí Pixel-Read/Write) nó oibrigh le ByteBuffer as an SampleBuffer (níos gar don ardán, ach níos cobhsaí).
  • Stop/Start le linn nascleanúna: I n-aipeanna soghluaiste, stoptar go minic nuair a athraítear an fhoirm nó nuair a tharlaíonn imeacht App-Pause. Tá sé tábhachtach gur féidir Stop a ghlaoch il-uair gan eisceachtaí a spreagadh (idempotent). Ba chóir freisin go bhfíoródh an callback torthaí an bhfuil an scanner fós ag rith (déantar é sin le DoResultOnMainThread).
  • ROI ró-chúng: Cuireann ROI lárnach luas ar an próiseas, ach d’fhéadfadh sé teip má choinníonn úsáideoirí an cód lasmuigh den réimse nó má tá an cód an-bheag. Dá bhrí sin tá EnableRoi inchoigeartaithe agus tá RoiScale teoranta.
  • Format-Lock ar QR: Is minic gur cheart é a theorannú do QR_CODE. Má theastaíonn uait Code128/EAN freisin, cuir na formáidí leis — ach réamh-mheas go mbeidh níos mó dearfacha bréagacha agus níos mó i láthair ar an CPU.

Delphi FMX Saolré Ceamara: Ceadanna, Cúlra, Rothlú

Ní thagann na fabhtanna is coitianta ón díchódú féin go minic, ach ó thimpeallacht an cheamara:

  • Android Permissions: Ní mór ceadanna ceamara a fháil ag am rith an iarratais. Smaoinigh ar an gcás go ndéanfaidh úsáideoir diúltú nó ‚Ach an uair seo amháin‘ a roghnú. Ó thaobh teicniúil, ciallaíonn sé seo stádas UI (‚Scanner réidh?‘) a choinneáil scartha ó stádas an cheamara, murach sin beidh tú i stáit leathchríochnaithe.
  • An aip ag dul sa chúlra: Ag an OnApplicationEvent (m.sh. EnteredBackground) ba chóir duit Stop a ghlaoch. Nuair a fhilleann tú, glaoigh go feasach ar Start (agus, má tá gá, cuir moill ghearr i bhfeidhm), ionas go mbeidh an réamhamharc seasmhach.
  • Rothlú/Scáthadh: Maireann rothlú go minic gan a bheith criticiúil do QR-Codes, ach i roinnt píblíne ceamara d’fhéadfadh an Bitmap a bheith scáthaithe nó rothlaithe. Má oibríonn scans ‚ach i suíomh amháin‘, is comhartha é sin. Sa chás sin: rothlaigh nó scáthigh an íomhá sula scanáiltear, nó bain úsáid as decóidéir a úsáideann meiteashonraí treoshuímh.

Debugáil i bhfeidhm: Conas na cúiseanna fíor a aimsiú

Mura léann an scanner ‚uaireanta‘, tá Debugáil inathnuaite an‑luachmhar. Trí mhodh a chruthaíonn torthaí iontaofa:

  1. Sampláil frámaí a logáil: Logáil (amháin i mód Debug/Support) Tick, toise íomhá, toise ROI, fad an díchódaithe. Beidh sé láithreach soiléir an é Throttle/Debounce nó ualach CPU atá ag cruthú na faidhbe.
  2. Íomhánna tástála a shábháil: Sábháil gach N soicind íomhá ROI (sealadach). Leis sin is féidir leat gan crua-earraí ceamara anailís a dhéanamh ar chodarsnacht agus neamhshoiléir.
  3. Scaradh ualaigh oibre: Ná nuashonraigh nuashonruithe UI (Preview-Overlay, Status-Text) go minic. Is minic a thagann an „creathadh UI“ ó iomarca imeachtaí Queue.

Leaganacha: Má tá níos mó uait ná „Scan agus críochnaithe“

Ilthorthaí, ach faoi smacht

Do phróisis mháthairbhloc (m.sh. go leor lipéid i ndiaidh a chéile) laghdaigh DebounceMs agus cuir isteach liosta bána/meaisín stáit: Ní cheadaítear QR-Code ach amháin nuair a bhíonn an chéim phróisis reatha ag súil leis. Ní loighic UI é sin, ach loighic réimse — ba chóir í a chur i sraith ar leith ionas go mbeidh an scanóir agus an próiseas inrochtana le tástáil go neamhspleách.

Fíorú as líne agus sonraí úsáide sábháilte

I bpróisis ghnó bíonn IDs nó tóicíní go minic i QR-Codes. Ná bí ag brath ar an ráiteas „QR = ceart“. Déan fíorú go háitiúil (formáid, seiceamh seiceála, agus réamhléirithe a bhfuiltear ag súil leo) agus ar thaobh an fhreastalaí (REST-API). Má úsáideann tú tóicíní: socraigh amanna éaga, cosaint in aghaidh athsheinm, agus logáil go cúramach (ná stóráil tóicíní mar théacs soiléir i logaí tacaíochta).

Cásanna oidhreachta: Scanóir FMX mar mhodúl i mbonnchódanna measctha

Mura bhfuil tú i ndomhan VCL atá fásaithe go han-mhór, is minic a bhíonn FMX mar chliant soghluaiste ina shríobh ar leith. Coinnigh an scanóir mar ranga rialaithe gan spleáchais fhoirme (mar thuas), ionas gur féidir é a chomhtháthú i scáileáin éagsúla. Tá sé sin tairbheach freisin le linn nuachóirithe: fanann an loighic ghnó inrochtana le tástáil, agus is cainéal ionchuir amháin í an cheamara. Go háirithe i gcásanna oidhreachta is fiú scarthlár soiléir do logging, Feature-Flags agus cumraíocht iargúlta.

Conclúid: Is fadhb thimthriall-beatha í scanadh FMX-QR seasmhach — ní hamháin glaoch ZXing

Beidh scanóir QR i Delphi FMX seasmhach má chóireálann tú é mar phipilín bheag: soláthraíonn an ceamara frámaí, oibríonn díchomhdaire cúlra go smachtáilte, agus cuireann Debounce/Throttle cosc ar imeachtaí dúbailte agus imeachtaí déanacha. Tá an slisne fhoinsí thuas dírithe go beacht ar na pointí a théann i léig i bpróisis ghnó soghluaiste fíor: tascanna díchódála iomarcacha, stop míchothrom, bacanna snáithe UI agus ualach neamhriachtanach.

Teorainneacha úsáide: Má theastaíonn uait rátaí scanadh thar a bheith ard (m.sh. scanadh tionsclaíoch ar shraith táirge) nó má tá riachtanais dhian maidir le próiseáil íomhá, is minic go mbíonn an ceamara caighdeánach FMX + bitmap-pipilín róchostasach. Sa chás sin bíonn buntáiste i gcur chuige níos gaire don ardán (Native Camera API, YUV-Buffer go díreach, SIMD/NEON) nó SDK scanóra speisialaithe. Do chuid is mó d’iarratais soghluaiste atá gaire do phróiseas, áfach, tá an cur chuige taispeántha sách maith, más féidir an timthriall beatha, cearta agus snáitheáil a chomhtháthú go glan — agus má tá na próisis taobh thiar soiléir.

Má bheidh ort scanadh QR a chur isteach i bstructúr Delphi atá ann cheana (lena n-áirítear cásanna imeartha cosúil le nascleanúint, cúlraú, logging agus fíorú próisis), pléimid é sin go struchtúrtha:

I gcomhthéacs teicniúil, bhíonn ról tábhachtach ag Zxing Delphi agus Fmx Tcameracomponent freisin, nuair is gá go n-oibreoidh comhtháthuithe, sreafaí sonraí agus forbairt bhreise go glan lena chéile.

Tionscadal nó tionscnamh nuachóirithe a phlé le Net-Base.

Céim eile

Nuair a éiríonn an t-ábhar seo ina thionscadal fíor, ba chóir ailtireacht, an córas reatha agus an t-oibriú a mheas le chéile go luath.

Ní hamháin go dtacaímid le ceisteanna aonair, ach freisin nuair is gá ó shlisíní cód foinse, ó ábhair legacy nó ó smaointe portail tionscadal corparáideach iontaofa a fhorbairt.

  • Measúnítear an staid reatha, an stát sprioc agus na rioscaí teicniúla le chéile.
  • Ní chuirfear REST, rochtain ar shonraí, portalí agus Rollout siar mar iarmhairtí.
  • Feiceann sibh go luath cé acu an cosán atá inbhuanaithe ó thaobh eacnamaíochta agus oibríochta.

Roinn an post

Roinn an t-alt seo go díreach

Tá LinkedIn, X, XING, Facebook, WhatsApp agus ríomhphost ar fáil láithreach. Do Instagram ullmhaímid nasc agus téacs gairid láithreach.

Ríomhphost

Osclaítear Instagram i gcluaisín nua. Cóipeáiltear an nasc agus an téacs gairid roimh ré isteach sa ghearrthaisce.