Ó 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
Tá 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í).
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
Stopa 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 leDoResultOnMainThread). - 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á
EnableRoiinchoigeartaithe agus táRoiScaleteoranta. - 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 duitStopa ghlaoch. Nuair a fhilleann tú, glaoigh go feasach arStart(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:
- 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.
- Í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.
- 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.
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.