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).
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
Stopjista‘ 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ħmelDoResultOnMainThread). - 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
EnableRoihuwa konfiggurabbli uRoiScalehuwa limitat. - Format-Lock auf QR: Ir-restrizzjoni għal
QR_CODEhija 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 intenzjonatStart(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:
- 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.
- 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.
- 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.