Net-Base Maġazin

03.06.2026

Skanner tal-kodiċi QR f'Delphi FMX: iskannjar mill-kamera robust, sikur għal multithreading u mingħajr jitter fl-UI

Scanner tal-Kodiċi QR prattiku Delphi FMX jiddependi fuq il-lifecycle tal-kamera, il-threading u kontroll nadif tal-stop/start. Il-kariga turi approċċ robust bil-ZXing, Debounce, Frame-Throttling, qtugħ tal-ROI kif ukoll dettalji ta’ debug u ta’ operazzjoni għal Android u iOS.

03.06.2026

Minn suġġett tar-rivista għall-prattika tal-proġett

Paġni ta' servizz u paġni tekniċi relevanti għall-artiklu

Skaner tal-Kodiċi QR Delphi FMX fil-prattika

Fis-demo, Skaner tal-Kodiċi QR Delphi FMX jitqiegħed flimkien malajr: turi preview tal-kamera, toħroġ Bitmap, tgħaddi ZXing fuqu. Fil-softwer tal-business reali (eż. riċeviment tal-merkanzija, assenjazzjoni tal-apparati, ticketing, proċessi ta‘ aċċess) waqt li jinħaddmu kundizzjonijiet limiti: l-app tmur fil-isfond, il-kamera titlef il-fokus, l-utent iżomm l-apparat inklinat, il-format tal-immaġni jinbidel – u għalhekk tibda tiskannja darbtejn fis-sakonda l-istess kodċi jew il-UI tidher li tinqaleb, peress li d-dekodifikazzjoni qed isseħħ fil-UI-Thread.

Il-problemi tipici mhumiex tant „ZXing kann nicht lesen“, imma l-lifecycle u l-arkitettura: rilaxx ta‘ riżorsi tal-kamera, tħaddim tat-timing tal-frames, thread-sikurezza meta taċċessa TBitmap (GPU/CPU), u stop/start ċar li jibqa‘ nadif anke meta l-utent jinżel malajr bejn il-paġni jew l-OS jeħodlu l-kamera temporanjament.

Ħarsa ġenerali tal-arkitettura: Pipeline minflok „OnSampleBufferReady macht alles“

Prattikament ippruvat li taħdem pipelina żgħira b’kompetenzi ċari:

  • Adattatur tal-kamera: jipprovdi frames (jew kopji tagħhom) f’format definit.
  • Decoder: jaħdem f’thread tal-isfond u jirritorna r-riżultati permezz ta‘ callback.
  • Gate/Debounce: jipprevjeni skans doppji u jirregola l-livell tal-ħidma (throttle).
  • Saff tal-UI: juri preview, fakultattiv rettanglu tal-fokus (ROI, „Region of InteREST“) u jirreaġixxi għar-riżultati.

B’dan tevita li l-UI, il-kamera u d-decoder jibbarrjaw lil xulxin. Hawn, „ROI“ tirreferi għal tieqa ta‘ tfittxija mqassma (eż. fil-viċinat tan-nofs 60%), li tnaqqas il-burden fuq id-decoder u tnaqqas riżultati f’false-positive. Importanti: ROI hija għodda għall-pRESTazzjoni u l-usabilità, mhux mekanizmu ta‘ sigurtà.

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

Il-kodiċi li ġej huwa maħsub bħala blokku kompatt iżda xieraq għall-proġett. Hu juża ZXing (Delphi-Port) permezz ta‘ ZXing.ScanManager u jissaldata ma‘ TCameraComponent.OnSampleBufferReady. Deċiżivi huma t-tlieta punti:

  • Il-frames jiġu throttled (mhux kull sample jiġi deċodifikat).
  • Id-dekodifikazzjoni ma ssirx fil-UI-Thread.
  • Stop/Start huwa idempotent (jista‘ jiġi msejjaħ b’mod ripetut mingħajr ma jġib kaos fir-riżorsi).
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>
  /// Kontrollur tas-Scanner QR għal FMX (Android/iOS).
  /// Jindirizza l-gating tal-frejms tal-kamera, id-dekoding fil-isfond u stop/start nadif.
  /// </summary>
  TQrScannerController = class
  private
    FCamera: TCameraComponent;
    FScanManager: TScanManager;
    FBitmap: TBitmap;
    FLock: TObject;

    FOnResult: TQrScanResultEvent;

    // Gating/Throttle
    FIsRunning: Boolean;
    FIsDecoding: Integer; // 0/1 bħala flag Interlocked
    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; // eż. 120
    property DebounceMs: Cardinal read FDebounceMs write FDebounceMs;         // eż. 1200
    property EnableRoi: Boolean read FEnableRoi write FEnableRoi;
    property RoiScale: Single read FRoiScale write FRoiScale;                 // eż. 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;

  // Kamera aktivieren: In echten Apps vorher Permissions prüfen (Android) und UI-Flow berücksichtigen.
  if Assigned(FCamera) then
    FCamera.Active := True;
end;

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

  // Aktiv sauber abschalten
  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.

X’jaħdem il-kodiċi (u għaliex dan hu meħtieġ)

Throttle (MinIntervalMs) jonqos il-lasta tal-CPU u t-tbexbix termali. Mingħajr limitazzjoni wħud l-apparati jippruvaw jiddeċodaw 30–60 frames/s; fil-prattika biżżejjed 5–10/s, spiss inqas. Debounce (DebounceMs) jipprevjeni li QR-Code li jkun stabbli jiġi attivat diversi drabi (eż. registrazzjoni doppja f’pass ta‘ proċess).

Il-Interlocked-Flag (FIsDecoding) jiżgura li massimu task wieħed ta‘ decode jopera. Dan huwa kifja arkitettoniku kontra „imbottigġjar tal-Queue“: jekk id-decoding jieħu 200 ms, imma task jiġi inizjat kull 120 ms, il-kju jikber u r-riżultati jaslu b’żamma, li fil-prattika jidher bħala „scanner jirreaġixxi ħażin“.

Kundizzjonijiet tal-kuntest u perikli

  • TBitmap und Threading: FMX-Bitmaps jistgħu jkunu GPU-backed. L-approċċ jikkopja l-frame f’Bitmap lokali u jiddeċoda fil-background. Skont il-verżjoni/pjattaforma ta‘ Delphi jista‘ jkollok bżonn kawtela: jekk tara artefatti, forċa Bitmap fuq il-CPU (eż. permezz ta‘ Pixel-Read/Write) jew ibda taħdem b’ByteBuffer mis-SampleBuffer (aktar qrib il-pjattaforma, imma aktar stabbli).
  • Stop/Start bei Navigation: F’applikazzjonijiet mobbli ħafna drabi tieqaf meta tinbidel il-form jew fl-event tal-pausa tal-app. Huwa importanti li Stop jista‘ jiġi msejjaħ diversi drabi u ma jġib l-ebda Exception (idempotent). Barra minn hekk, il-callback tar-riżultat għandu jivverifika jekk il-scanner għadu jmexxi (jagħmel DoResultOnMainThread).
  • ROI zu eng: ROI wisq limitat fil-viċin jgħin il-prestazzjoni, iżda jista‘ jfalli jekk l-utent iżomm il-kodiċi barra mir-reġjun jew jekk il-kodiċi jkun żgħir ħafna. Għalhekk EnableRoi huwa konfiggurabbli u RoiScale huwa limitat.
  • Format-Lock auf QR: Ir-restrizzjoni għal QR_CODE hija f’ħafna każijiet korretta. Jekk għandek bżonn ukoll Code128/EAN, żid il-format – imma stima aktar false positives u iktar użu tal-CPU.

Delphi FMX Kamera-Lifecycle: Permessi, sfond, rotazzjoni

L-iktar bugs komuni ma jsehhux fil-decoding, iżda madwar il-kamera:

  • Android Permissions: Il-permessi tal-kamera għandhom jintalbu waqt il-ħin tal-eżekuzzjoni. Ippjana l-każ li l-utent jirrifjuta jew jagħżel „Biss din id-darba“. B’mod tekniku dan ifisser: żomm l-UI-State („Scanner lest?“) separat mill-Kamera-State, inkella tibqa‘ f’stati nofs sħaħ.
  • App geht in den Hintergrund: Fil-OnApplicationEvent (eż. EnteredBackground) għandek tissejjaħ Stop. Meta terġa‘ tidħol, ssejjaħ b’mod intenzjonat Start (u possibilment dewmien qasir), sabiex il-preview ikun stabbli.
  • Rotation/Mirroring: Għall-QR-Codes rotazzjoni spiss mhix kritika, iżda f’xi pipelines tal-kamera il-bitmap jista‘ jkun immirjat jew imdawwar. Jekk l-iskans jaħdmu „biss f’pożizzjoni waħda“, dan huwa sinjal. F’dak il-każ: qabel l-iskan iddawwar/immiġra l-immaġni jew uża decoder li juża metadati tal-orjentazzjoni.

Debugging im Betrieb: So finden Sie die echten Ursachen

Jekk is-scanner „xi drabi“ ma jaqrax, debugging li jista‘ jirreproġġih huwa ta‘ valur kbir. Tliet miżuri li jidhru effettivi:

  1. Frame-Sampling loggen: Irreġistra fil-log (biss fil-modalità Debug/Support) Tick, daqs tal-immaġni, daqs tal-ROI, tul tad-decoding. B’hekk tara immedjatament jekk Throttle/Debounce jew il-lasta tal-CPU huma l-problema.
  2. Testbilder sichern: Iffranka kull N sekonda ritratt tal-ROI (temporanjament). B’dan tista‘ tanalizza mingħajr hardware tal-kamera jekk il-kuntrast jew l-immaġni mhux ċara huma l-problema.
  3. Separa l-workload: Ma taġġornax l-aġġornamenti tal-UI (Preview-Overlay, Status-Text) b’frekwenza għolja. Il-„UI-Zittern“ spiss jirriżulta minn wisq avvenimenti Queue.

Varjanti: Jekk għandek bżonn iktar mill-„Scan und fertig“

Ħafna riżultati, imma kkontrollati

Għal proċessi ta‘ lott (pereżempju ħafna labels konsekuttivi) naqqas DebounceMs u żid Whitelist/State-Machine: Kodice QR għandu jiġi aċċettat biss meta l-passi tal-proċess attwali jistennewh. Din mhix loġika tal-UI, imma loġika tad-dominju — għandha tkun f’saff proprju sabiex scanner u proċess jibqgħu testabbli b’mod indipendenti.

Validazzjoni offline u data ta‘ użu protetta

F’proċessi korporattivi, il-kodiċijiet QR spiss jinkludu IDs jew token. Tissaddadx fuq il-premessa “QR = korrekt”. Ivvalida lokalment (format, kontroll tas-somma, prefissi mistennija) u fuq is-server (REST-API). Jekk tuża token: ibbaża politiki ta‘ skadenza, protezzjoni kontra replay u jagħmel logging b’attenzjoni (ebda token fil-plaintext fil-loggs tas-support).

Sitwazzjonijiet legacy: FMX-Scanner bħala modulu f’bases tal-kodiċi miksijin

Jekk għandek dinja VCL mibnija, FMX bħala client mobbli spiss huwa strixxa separata. Żomm il-scanner bħala klass ta‘ controller mingħajr dipendenzi fuq il-form (kif deskritt hawn fuq), sabiex tkun tista‘ tintegrah f’screens differenti. Dan jħallas ukoll waqt modernizzazzjoni: il-loġika tan-negozju tibqa‘ testabbli, il-kamera hija biss kanal ta‘ input. Speċjalment f’sitwazzjonijiet legacy huwa utli wkoll qtugħ ċar għall-logging, feature-flags u konfigurazzjoni remota.

Konklużjoni: Scan FMX-QR robust huwa problema tal-lifecycle — mhux biss sejħa ZXing

Scanner ta‘ QR Code f’Delphi FMX isir stabbli meta ttrattaħ bħala pipeline żgħira: il-kamera tipprovdi frames, decoder fuq il-background jaħdem b’kontroll, u Debounce/Throttle jipprevjenu avvenimenti doppel u tardivi. Is-snippet tas-sors hawn fuq jindirizza eżatt il-punti li fil-proċessi mobili tan-negozju reali jisfidaw: wisq Decode-Tasks, stop mhux nadif, blokkaġġi tal-UI-thread u load mhux meħtieġ.

Limitazzjonijiet ta‘ użu: Jekk għandek bżonn ritmi ta‘ skannjar estremament għoljin (pereżempju skannjar industrijali fuq katina ta‘ produzzjoni) jew rekwiżiti serji fuq il-proċessar tal-immaġni, il-kamera standard FMX + pipeline tal-bitmap spiss tkun troppo kostli. F’dawk il-każijiet jiswa approċċ viċin il-pjattaforma (Native Camera API, YUV-Buffer dirett, SIMD/NEON) jew SDK ta‘ scanner speċjalizzat. Għal ħafna applikazzjonijiet mobili relatati mal-proċess, l-approċċ murija huwa suffiċjenti, sakemm il-lifecycle, id-drittijiet u t-threading jkunu integrati nadif — u l-proċessi taħt ikunu ċari.

Jekk għandek bżonn adattar ta‘ QR-scan f’arkitettura Delphi eżistenti (inklużi każijiet fringe bħal navigazzjoni, backgrounding, logging u validazzjoni tal-proċess), niddiskutu dan b’mod strutturat:

Fil-kuntest professjonali għandhom wkoll rwol importanti Zxing Delphi u Fmx Tcameracomponent, meta integrazzjonijiet, flussi tad-data u evoluzzjoni għandhom jaħdmu b’mod koerenti.

Iddiskuti proġett jew inizjattiva ta‘ modernizzazzjoni ma‘ Net-Base.

Pass li jmiss

Meta suġġett jissarraf f’proġett reali, l-arkitettura, is-sistema eżistenti u l-operazzjoni għandhom jiġu kkunsidrati flimkien kmien.

Aħna nappoġġjaw mhux biss f'kwistjonijiet puntwali, iżda wkoll meta biċċiet ta' kodiċi sors, temi legacy jew ideat għal portali jridu jsiru proġett korporattiv stabbli u affidabbli.

  • L-istat attwali, l-istat tal-mira u r-riskji tekniċi jiġu vvalutati flimkien.
  • REST, aċċess għad-dejta, portali u Rollout mhux se jiġu posposti bħala konsegwenzi tardivi.
  • Tara kmieni liema triq hija sostenibbli kemm mill‑punt ta’ vista ekonomiku kif ukoll mill‑punt ta’ vista operattiv.

Aqsam il-post

Aqsam dan il-post direttament

LinkedIn, X, XING, Facebook, WhatsApp u E-Mail huma immedjatament disponibbli. Għal Instagram nippreparaw il-link u t-test qasir direttament.

Imejl

Instagram jiftaħ f'tab ġdid. Il-link u t-test qasir jiġu kkopjati qabel fil-clipboard.