Nga tema e revistës në praktikën e projektit
Faqe shërbimi dhe teknike të përshtatshme për artikullin
Skener QR Code Delphi FMX në praktikë
Një QR Code Scanner Delphi FMX në një demo ndërtohet shpejt: shfaqet pamja paraprake e kamerës, nxirret Bitmap, dhe ZXing kalon mbi të. Në softuerin real të biznesit (p.sh. pranimi i mallrave, caktimi i pajisjeve, ticketing, proceset e hyrjes) shfaqen kushte të jashtme: aplikacioni shkon në sfond, kamera humbet fokusin, përdoruesi mban pajisjen në kënd, formati i imazhit ndryshon – dhe papritmas skanoni të njëjtin kod dy herë në sekondë ose UI lëkundet, sepse dekodimi po kryhet në UI-Thread.
Problemet tipike nuk janë kaq shumë „ZXing nuk mund të lexojë”, por më shumë Lifecycle dhe arkitektura: lirimi i burimeve të kamerës, taktimi i frame-ve, siguria e thread-it kur aksesohet TBitmap (GPU/CPU) dhe një Stop/Start i qartë që funksionon pastër edhe kur përdoruesit navigojnë shpejt ose sistemi operativ i heq kamerës përkohësisht.
Përmbledhje e arkitekturës: Pipeline në vend të „OnSampleBufferReady macht alles”
Në praktikë është provuar të jetë efektive një pipeline e vogël me përgjegjësi të qarta:
- Adapter i kamerës: furnizon Frames (ose kopje të tyre) në një format të përcaktuar.
- Decoder: punon në thread-in e sfondit dhe kthen rezultatet përmes një callback.
- Gate/Debounce: parandalon skanime të dyfishta dhe rregullon ngarkesën (throttle).
- Shtresa UI: tregon pamjen paraprake, opsional kutinë e fokusit (ROI, „Region of InteREST”) dhe reagon ndaj rezultateve.
Me këtë evitoni që UI, kamera dhe decoder të bllokojnë njëri-tjetrin. „ROI” nënkupton këtu një dritare kërkimi të prerë (p.sh. në mes 60%), e cila lehtëson decoder-in dhe zvogëlon rezultatet false-positive. E rëndësishme: ROI është një mjet për performancë dhe përdorshmëri, jo një mekanizëm sigurie.
Shembull kodi: Skener QR Code robust (FMX + ZXing) me debounce dhe ndalim të pastër
Kodi i mëposhtëm është menduar si një bllok i kompakët, por i përshtatshëm për projekt. Ai përdor ZXing (Delphi-Port) përmes ZXing.ScanManager dhe lidhet me TCameraComponent.OnSampleBufferReady. Vendimtare janë tre pika:
- Frames bëhen throttled (të mos dekodohen çdo sample).
- Dekodimi nuk kryhet në UI-Thread.
- Stop/Start është idempotent (mund të thirret shumëherë, pa shkaktuar kaos me burimet).
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>
/// Kontrollues i skanerit QR për FMX (Android/iOS).
/// Përkujdeset për kontrollin e frame-ve të kamerës, dekodimin në prapavijë dhe ndalimin/nisjen e pastër.
/// </summary>
TQrScannerController = class
private
FCamera: TCameraComponent;
FScanManager: TScanManager;
FBitmap: TBitmap;
FLock: TObject;
FOnResult: TQrScanResultEvent;
// Rregullim i ritmit (throttle)
FIsRunning: Boolean;
FIsDecoding: Integer; // 0/1 si Interlocked-Flag
FLastDecodeTick: Int64;
FMinIntervalMs: Cardinal;
// Debounce për kodet e njëjta të përsëritura
FLastText: string;
FLastTextTick: Int64;
FDebounceMs: Cardinal;
// ROI: përqindja e imazhit që skanohet (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; // p.sh. 120
property DebounceMs: Cardinal read FDebounceMs write FDebounceMs; // p.sh. 1200
property EnableRoi: Boolean read FEnableRoi write FEnableRoi;
property RoiScale: Single read FRoiScale write FRoiScale; // p.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;
// Inicializoni ScanManager dhe kufizoni në QR (performancë + më pak pozitive të rreme)
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;
// Aktivizoni kamerën: në aplikacione reale verifikoni më parë lejet (Android) dhe merrni parasysh rrjedhën e ndërfaqes.
if Assigned(FCamera) then
FCamera.Active := True;
end;
procedure TQrScannerController.Stop;
begin
if not FIsRunning then
Exit;
FIsRunning := False;
// Çaktivizim i pastër
if Assigned(FCamera) then
FCamera.Active := False;
// Rivendos flag-un e dekoderit, nëse Stop vjen në një fazë të pafavorshme
TInterlocked.Exchange(FIsDecoding, 0);
end;
function TQrScannerController.ShouldDecodeNow(const ANowTick: Int64): Boolean;
begin
// Rregullim i ritmit: mos dekodoni çdo frame
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);
// i njëjti tekst brenda dritares së debounce - injoro
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;
// Vetëm një dekodim në të njëjtën kohë (përndryshe ngecje e kuyrës në pajisje të dobëta)
if TInterlocked.CompareExchange(FIsDecoding, 1, 0) <> 0 then
Exit;
// Kopjoni sample-in e kamerës në FBitmap. Bllokim, sepse i njëjti buffer i bitmap nuk duhet përdorur paralelisht.
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;
// Dekodim në prapavijë
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
// Nxjerrja e ROI në mes: redukton ngarkesën e përpunimit dhe orienton përdoruesin.
// Kujdes: për kode QR shumë të vegjël ROI mund të jetë tepër i ngushtë.
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: navigim, tingull sinjalizimi, mbushje fushe etj.
TThread.Queue(nil,
procedure
begin
if FIsRunning and Assigned(FOnResult) then
FOnResult(AText);
end);
end;
end.
Çfarë zgjidh ky kod (dhe pse është i nevojshëm)
Throttle (MinIntervalMs) ul ngarkesën e CPU-së dhe ngrohjen. Pa kufizim, disa pajisje përpiqen të dekodojnë 30–60 frame/s; në praktikë mjaftojnë 5–10/s, shpesh edhe më pak. Debounce (DebounceMs) parandalon që një kod QR i mbajtur i qetë të shkaktojë ngjarje më shumë se një herë (p.sh. regjistrim i dyfishtë në një hap procesi).
Interlocked-Flag (FIsDecoding) siguron që maksimalisht një Decode-Task të jetë duke u ekzekutuar. Kjo është një hile arkitekturore kundër „bllokimit të radhës“: nëse dekodimi zgjat 200 ms, por çdo 120 ms fillohet një task, rritet radhë pritjeje dhe rezultatet vijnë të vonuara, çka në operim duket si „skaneri reagon gabim“.
Kushtet kufizuese dhe kurthet
- TBitmap dhe Threading: FMX-Bitmaps mund të jenë të mbështetura nga GPU. Qasja kopjon frame-in në një bitmap lokale dhe dekodon në sfond. Në varësi të versionit/platformës së Delphi mund të kërkohet kujdes: nëse shihni artefakte, detyroni një CPU-Bitmap (p.sh. përmes lexim/shkrim të pixelëve) ose punoni me një ByteBuffer nga SampleBuffer (më afër platformës, por më i qëndrueshëm).
- Stop/Start gjatë navigimit: Në aplikacionet mobile shpesh ndalohet kur ndryshohet forma ose në event-in e pauzës së app. E rëndësishme është që
Stoptë mund të thirret disa herë pa shkaktuar përjashtime (idempotent). Gjithashtu callback-u i rezultatit duhet të verifikojë nëse skaneri ende po funksionon (kryenDoResultOnMainThread). - ROI shumë i ngushtë: Një ROI i centruar përshpejton, por mund të dështojë nëse përdoruesi mban kodin jashtë zonës ose kodi është shumë i vogël. Prandaj
EnableRoiështë i konfigurueshëm dheRoiScalei kufizuar. - Fiksimi i formatit në QR: Kufizimi tek
QR_CODEzakonisht është i drejtë. Nëse keni nevojë edhe për Code128/EAN, zgjeroni formatet – por pritni më shumë false positives dhe më shumë ngarkesë CPU.
Delphi FMX cikli i jetës së kamerës: Lejet, sfondi, rotacioni
Gabimet më të zakonshme nuk vijnë nga dekodimi, por nga menaxhimi i kamerës:
- Android Permissions: Të drejtat e kamerës duhen kërkuar në kohërzbatuese. Planifikoni rastin kur një përdorues refuzon ose zgjedh „Vetëm këtë herë“. Teknikisht do të thotë: mbani UI-State („Scanner gati?“) të ndarë nga Kamera-State, përndryshe do të ngecni në shtete të papërfunduara.
- App shkon në sfond: Në
OnApplicationEvent(p.sh.EnteredBackground) duhet të thërrisniStop. Kur ktheheni, thërrisni me qëllimStart(dhe me një vonesë të shkurtër nëse duhet), në mënyrë që preview të jetë i qëndrueshëm. - Rotacion/Pasqyrim: Për kodet QR rotacioni shpesh nuk është kritik, por në disa pipeline të kamerës bitmap-i mund të jetë i pasqyrueshëm ose i rrotulluar. Nëse skanimet funksionojnë „vetëm në një pozicion“, kjo është një tregues. Në atë rast: rrotulloni/pasqyroni para skanimit ose përdorni një decoder që shfrytëzon metadata të orientimit.
Debugging në operim: Si të gjeni shkaktarët e vërtetë
Nëse skaneri „ndonjëherë“ nuk lexon, debugimi i riprodhueshëm vlen shumë. Tre masa që japin rezultat:
- Regjistroni frame-sampling: Logoni (vetëm në modalitetin Debug/Support) tick, madhësinë e imazhit, madhësinë e ROI, kohën e dekodimit. Kështu shihni menjëherë nëse problemi është Throttle/Debounce ose ngarkesa e CPU-së.
- Ruani imazhe testuese: Ruani çdo N sekonda një imazh ROI (përkohësisht). Me to mund të analizoni pa harduer kamere nëse kontrasti/paqartësia (blur) janë shkaku.
- Ndani ngarkesën e punës: Mos përditësoni përditësimet e UI-së (Preview-Overlay, Status-Text) me frekuencë të lartë. Dridhja e ndërfaqes vjen shpesh nga shumë
Queue-ngjarje.
Varianta: Nëse ju duhet më shumë se „Skano dhe mbaro“
Rezultate të shumta, por të kontrolluara
Për procese grumbulli (p.sh. shumë etiketa njëra pas tjetrës) ulni DebounceMs dhe shtoni një Whitelist/State-Machine: Një QR-Code duhet të pranohet vetëm kur hapi aktual i procesit e pret atë. Kjo nuk është logjikë e UI-së, por logjikë domene – duhet të vendoset në një shtresë të veçantë, në mënyrë që skaneri dhe procesi të mbeten të testueshëm në mënyrë të pavarur.
Validimi offline dhe të dhëna të përdorshme të sigurta
Në proceset e kompanive QR-Code shpesh përmbajnë ID ose token. Mos u mbështetni në përfundimin që „QR = korrekt“. Validoni lokal (formatin, kontroll-shifrën, prefikset e pritura) dhe nga ana e serverit (REST-API). Nëse përdorni token: përcaktoni kohëzgjatjen e skadencës, mbrojtjen ndaj replay dhe bëni logging me kujdes (mos i evidentoni token-et në tekst të qartë në log-et e suportit).
Situata legacy: FMX-Scanner si modul në baza kodesh të përziera
Nëse keni një botë VCL të zhvilluar, FMX si klient mobil shpesh është një rrjedhë e ndarë. Mbani skanerin si Klasë kontrolluese pa varësi nga forma (si më lart), kështu mund ta integroni në ekrane të ndryshme. Kjo paguhet edhe gjatë modernizimit: logjika e biznesit mbetet e testueshme, kamera është vetëm një kanal inputi. Veçanërisht në situata legacy vlen një ndarje e qartë për logging, Feature-Flags dhe konfigurim në distancë.
Përfundim: Një FMX-QR-skanim i qëndrueshëm është një problem i ciklit të jetës – jo vetëm një thirrje ZXing
Një QR Code Scanner në Delphi FMX bëhet i qëndrueshëm kur e trajtoni si një pipeline të vogël: kamera jep frame, një dekoder në sfond punon i kontrolluar, dhe Debounce/Throttle parandalojnë ngjarjet e dyfishta dhe të vona. Fragmeti i burimit më sipër adreson saktësisht vendet që shkojnë në krizë në procese reale mobile të biznesit: shumë Decode-Tasks, ndalime të pasakta, bllokime të thread-it të UI-së dhe ngarkesë e panevojshme.
Kufizimet e përdorimit: Nëse keni nevojë për shkalla skanimi ekstremisht të larta (p.sh. skanime industriale në rrjedhën e prodhimit) ose kërkesa të ashpra për përpunimin e imazheve, FMX-kamera standarde + pipeline me Bitmap shpesh del shumë e shtrenjtë. Atëherë ia vlen një qasje afër platformës (Native Camera API, YUV-Buffer direkt, SIMD/NEON) ose një Scanner-SDK i specializuar. Për shumicën e aplikacioneve mobile afër procesit qasja e treguar mjafton, për sa kohë Lifecycle, të drejtat dhe Threading janë integruar qartë – dhe proceset pas tyre janë të qarta.
Nëse duhet të përshtatni një QR-Scan në një arkitekturë ekzistuese Delphi (përfshirë raste kufitare si Navigation, Backgrounding, Logging dhe validimi i procesit), ne e sqarojmë këtë me qejf në mënyrë të strukturuar:
Në kontekstin profesional Zxing Delphi dhe Fmx Tcameracomponent luajnë gjithashtu një rol të rëndësishëm, kur integrimet, rrjedhat e të dhënave dhe zhvillimi i mëtejshëm duhet të funksionojnë së bashku në mënyrë të pastër.
Diskutoni projektin ose iniciativën e modernizimit me Net-Base.
Hapi tjetër
Wenn aus dem Thema ein reales Projekt wird, sollten Architektur, Bestand und Betrieb frueh zusammen betrachtet werden.
Wir unterstuetzen nicht nur bei Einzelfragen, sondern auch dann, wenn aus Source-Schnipseln, Legacy-Themen oder Portalideen ein belastbares Unternehmensprojekt werden soll.
- Gjendja ekzistuese, imazhi i synuar dhe rreziqet teknike vlerësohen së bashku.
- REST, akses në të dhëna, portalet dhe Rollout nuk shtyhen si pasoja të mëvonshme.
- Ju e shihni herët se cila rrugë është e qëndrueshme ekonomikisht dhe operativisht.