Net-Base Dergi

03.06.2026

QR Kod Tarayıcısı Delphi FMX'te: Kamera taraması sağlam, iş parçacığı güvenli ve kullanıcı arayüzünde titreme olmadan

Kullanıma uygun bir QR Kod Tarayıcı Delphi FMX, kamera yaşam döngüsü, iş parçacığı yönetimi ve doğru durdurma/başlatma uygulamalarına büyük ölçüde bağlıdır. Bu yazı ZXing, Debounce, Frame-Throttling, ROI-kırpma ile sağlam bir yaklaşımı ve Android ile iOS için hata ayıklama ve işletim detaylarını gösteriyor.

03.06.2026

Dergi konusundan proje pratiğine

İçeriğe Uygun Hizmet ve Teknik Sayfalar

QR Kod Tarayıcı Delphi FMX uygulamada

Bir QR Kod Tarayıcı Delphi FMX demo içinde hızlıca bir araya gelir: kamera önizlemesi göster, Bitmap al, ZXing ile çözümle. Gerçek iş yazılımlarında (ör. gelen mal, cihaz eşleştirme, biletleme, erişim süreçleri) ise ek kısıtlar ortaya çıkar: uygulama arka plana gider, kamera odağını kaybeder, kullanıcı cihazı eğik tutar, görüntü formatı değişir – ve aniden aynı kodu saniyede iki kez tarar veya UI takılır çünkü çözümleme UI-Thread’te çalışmaktadır.

Tipik sorunlar daha az „ZXing okuyamıyor“ olmakta, daha çok lifecycle ve mimari kaynaklıdır: kameranın kaynak serbest bırakımı, frame zamanlaması, TBitmap (GPU/CPU) erişiminde thread-güvenliği ve kullanıcı hızlı gezinse ya da işletim sistemi kamerayı kısa süreliğine elinden alsa bile temiz bir Stop/Start mekanizması.

Mimari genel bakış: Pipeline yerine „OnSampleBufferReady macht alles“ değil

Pratikte kendini kanıtlamış, net sorumluluklara sahip küçük bir pipeline şu şekilde çalışır:

  • Kamera-Adapter: tanımlı bir formatta frame’lar (veya bunların kopyaları) sağlar.
  • Decoder: arka plan iş parçacığında çalışır ve sonuçları bir callback ile döndürür.
  • Gate/Debounce: çift taramaları engeller ve yükü düzenler (Throttle).
  • UI-Schicht: önizlemeyi gösterir, isteğe bağlı odak-dikdörtgeni (ROI, „Region of InteREST“) gösterir ve sonuçlara tepki verir.

Böylece UI, kamera ve decoder’ın birbirini kilitlemesini önlersiniz. Buradaki „ROI“, decoder’ı hafifletmek ve yanlış-pozitif sonuçları azaltmak için kesilmiş bir arama penceresini ifade eder (ör. ortada %60). Önemli: ROI bir performans ve kullanılabilirlik aracıdır, güvenlik mekanizması değildir.

Kod örneği: Debounce ve temiz Stop ile Dayanıklı QR Kod Tarayıcı (FMX + ZXing)

Aşağıdaki kod, kompakt ama projeye uygun bir bileşen olarak tasarlanmıştır. ZXing (Delphi-Port) üzerinden ZXing.ScanManager kullanır ve TCameraComponent.OnSampleBufferReady‚a bağlanır. Kritik olan üç nokta vardır:

  • Frame’lar sınırlandırılır (her örnek çözülmez).
  • Çözümleme UI iş parçacığında yapılmaz.
  • Stop/Start idempotent olmalıdır (birden çok kez çağrılabilir, kaynak karışıklığı olmadan).

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>
/// FMX (Android/iOS) için QR tarayıcı denetleyicisi.
/// Kamera çerçeve sınırlaması, arka planda kod çözme ve temiz başlatma/durdurma işlemlerini yönetir.
/// </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; // örn. 120
property DebounceMs: Cardinal read FDebounceMs write FDebounceMs; // örn. 1200
property EnableRoi: Boolean read FEnableRoi write FEnableRoi;
property RoiScale: Single read FRoiScale write FRoiScale; // örn. 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;

// Aktifi düzgün kapat
if Assigned(FCamera) then
FCamera.Active := False;

// Decoder-Flag zurücksetzen, falls Stop in einer ungünstigen Phase kommt
// Durdurma işlemi uygunsuz bir fazda gerçekleşirse decoder bayrağını sıfırla
TInterlocked.Exchange(FIsDecoding, 0);
end;

function TQrScannerController.ShouldDecodeNow(const ANowTick: Int64): Boolean;
begin
// Throttle: nicht jedes Frame dekodieren
// Throttle: her kareyi kod çözme
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
// Aynı metin debounce penceresi içinde ise – yoksay
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)
// Aynı anda yalnızca bir çözümleme (aksi takdirde zayıf cihazlarda kuyruk tıkanması)
if TInterlocked.CompareExchange(FIsDecoding, 1, 0) <> 0 then
Exit;

// Kamera-Sample in FBitmap kopieren. Lock, weil derselbe Bitmap-Buffer nicht parallel benutzt werden soll.
// Kamera örneğini FBitmap’e kopyala. Kilitleme, çünkü aynı bitmap arabelleği paralel kullanılmamalı.
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
// Arka planda kod çözme
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.
// ROI’yi ortadan kırp: işlem yükünü azaltır ve kullanıcıyı yönlendirir.
// Dikkat: çok küçük QR kodlarda ROI çok dar olabilir.
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.
// UI iş parçacığı: gezinti, bip, alan doldurma vb.
TThread.Queue(nil,
procedure
begin
if FIsRunning and Assigned(FOnResult) then
FOnResult(AText);
end);
end;

end.

Kodun çözdüğü sorunlar (ve neden gerekli)

Throttle (MinIntervalMs) CPU yükünü ve ısı oluşumunu azaltır. Sınır konulmazsa bazı cihazlar 30–60 Frame/s çözmeye çalışır; pratikte 5–10/s genellikle yeterlidir, çoğu zaman daha az. Debounce (DebounceMs) sabit tutulan bir QR kodunun tekrar tekrar tetiklenmesini engeller (ör. bir işlem adımında çift kayıt yapılması).

Interlocked-Flag (FIsDecoding) aynı anda en fazla bir Decode-Task çalışmasını garanti eder. Bu, „kuyruk tıkanması“na karşı bir mimari önlemdir: Eğer decoding 200 ms sürüyor ama her 120 ms’de bir Task başlatılıyorsa, kuyruk büyür ve sonuçlar gecikmeli gelir; işletimde bu „tarayıcı yanlış tepki veriyor“ gibi algılanır.

Sınır koşulları ve tuzaklar

  • TBitmap und Threading: FMX bitmap’leri GPU-backed olabilir. Yaklaşım, frame’i yerel bir bitmap’e kopyalar ve arka planda decode eder. Delphi sürümüne/platfoma bağlı olarak yine dikkat gerekebilir: Artefakt görüyorsanız CPU-Bitmap zorlayın (ör. Pixel-Read/Write ile) veya SampleBuffer’dan bir ByteBuffer kullanın (platforma daha yakın, ama daha stabil).
  • Stop/Start bei Navigation: Mobil uygulamalarda form değişimi veya App-Pause olayında genellikle durdurma yapılır. Önemli olan, Stop’un birkaç kez çağrılabilmesi ve exception üretmemesidir (idempotent). Ayrıca sonuç callback’i tarayıcının hâlâ çalışıp çalışmadığını kontrol etmelidir (bunu DoResultOnMainThread yapar).
  • ROI zu eng: Ortalanmış bir ROI hızlandırır, ancak kullanıcı kodu dışarda tuttuğunda veya kod çok küçük olduğunda başarısız olabilir. Bu yüzden EnableRoi yapılandırılabilir olmalı ve RoiScale sınırlandırılmalıdır.
  • Format-Lock auf QR: QR_CODE ile sınırlamak çoğunlukla doğrudur. Eğer Code128/EAN da gerekiyorsa formatları genişletin — fakat daha fazla false positive ve daha yüksek CPU maliyeti ile karşılaşacağınızı hesaba katın.

Delphi FMX Kamera-Lifecycle: Berechtigungen, Hintergrund, Rotation

En yaygın hatalar decoding sırasında değil, kamera etrafındaki durumlarda ortaya çıkar:

  • Android Permissions: Kamera izinleri runtime’da alınmalıdır. Kullanıcının reddetme veya „Sadece bu sefer“ seçeneğini seçebileceğini planlayın. Teknik olarak bu, UI durumunu („Scanner hazır mı?“) kamera durumundan ayrı tutmak demektir; aksi halde yarım kalmış durumlara takılırsınız.
  • App geht in den Hintergrund: OnApplicationEvent (ör. EnteredBackground) sırasında Stop çağrılmalıdır. Geri dönerken bilinçli şekilde Start (ve gerekirse kısa bir gecikme) çağırın, böylece preview stabil olur.
  • Rotation/Mirroring: QR kodları için rotation çoğunlukla kritik değildir, ancak bazı kamera pipeline’larında bitmap ters çevrilmiş veya döndürülmüş olabilir. Eğer taramalar „sadece tek bir tutuşta“ çalışıyorsa bu bir işarettir. Bu durumda: taramadan önce döndürme/aynalama uygulayın veya orientation meta verisini kullanan bir decoder tercih edin.

Debugging im Betrieb: So finden Sie die echten Ursachen

Tarayıcı „bazen“ okumuyorsa, tekrarlanabilir debugging çok değerlidir. İşe yarayan üç önlemden üçü:

  1. Frame-Sampling loggen: (Sadece Debug/Support modunda) Tick, görüntü boyutu, ROI boyutu, Decode süresini loglayın. Böylece Throttle/Debounce veya CPU yükünün sorun olup olmadığını hemen görürsünüz.
  2. Testbilder sichern: Her N saniyede bir ROI görüntüsünü kaydedin (geçici). Bu sayede kamera donanımı olmadan kontrast/bulanıklık gibi sorunları analiz edebilirsiniz.
  3. İş yükünü ayırın: UI güncellemelerini (Preview-Overlay, Status-Text) yüksek sıklıkta yapmayın. „UI titremesi“ çoğunlukla çok fazla Queue olayı nedeniyle oluşur.

Varyantlar: Eğer „Tarama ve bitti“den fazlasına ihtiyacınız varsa

Birden fazla sonuç, fakat kontrollü

Toplu işlemler için (örn. birçok etiket ardışık olarak) DebounceMs‚i azaltın ve bir Whitelist/State-Machine ekleyin: Bir QR kodu yalnızca mevcut süreç adımı onu bekliyorsa kabul edilmelidir. Bu bir UI mantığı değil, alan (domain) mantığıdır — ayrı bir katmana ait olmalıdır; böylece tarayıcı ve süreç bağımsız olarak test edilebilir kalır.

Çevrimdışı doğrulama ve güvenli içerik

Kurumsal süreçlerde QR kodları genellikle ID’ler veya tokenlar içerir. „QR = doğru“ varsayımına güvenmeyin. Yerelde doğrulayın (format, checksum, beklenen önekler) ve sunucu tarafında da kontrol edin (REST-API). Token kullanıyorsanız: sona erme süreleri, replay koruması ve loglama konusunda dikkatli olun (destek loglarında tokenları açık metin olarak saklamayın).

Legacy durumlar: FMX tarayıcıyı karışık kod tabanlarında modül olarak kullanma

Eğer gelişmiş bir VCL ortamınız varsa, FMX mobil istemci genellikle ayrı bir dal olarak yer alır. Tarayıcıyı yukarıda olduğu gibi Form bağımlılığı olmayan bir Controller sınıfı olarak tutun; böylece farklı ekranlara entegre edebilirsiniz. Bu modernizasyonda da avantaj sağlar: İş mantığı test edilebilir kalır, kamera sadece bir giriş kanalıdır. Özellikle legacy durumlarda logging, Feature-Flags ve uzaktan yapılandırma için net bir ayrım fayda sağlar.

Sonuç: Sağlam FMX-QR tarama bir Lifecycle sorunudur — sadece bir ZXing çağrısı değil

Delphi FMX içindeki bir QR kod tarayıcı, onu küçük bir pipeline gibi ele alırsanız stabil olur: kamera frame sağlar, bir arka plan decoder kontrollü çalışır ve Debounce/Throttle çift ve gecikmiş olayları engeller. Yukarıdaki kaynak parçacığı, gerçek mobil iş süreçlerinde sorun çıkaran noktaları hedefliyor: çok fazla decode görevi, düzgün durdurulmama, UI-Thread tıkanmaları ve gereksiz yük.

Kullanım sınırları: Eğer son derece yüksek tarama oranlarına ihtiyacınız varsa (örn. bant üzeri endüstriyel tarama) veya görüntü işleme için katı gereksinimleriniz varsa, FMX standart kamera + bitmap pipeline sıklıkla maliyetli olur. Bu durumda platforma yakın bir yaklaşım (Native Camera API, doğrudan YUV-Buffer, SIMD/NEON) veya uzmanlaşmış bir scanner-SDK daha uygun olur. Çoğu süreç-odaklı mobil uygulama için gösterilen yaklaşım yeterlidir; yeter ki Lifecycle, izinler ve threading düzgün entegre edilmiş olsun — ve arkasındaki süreçler net olsun.

Eğer bir QR taramasını mevcut Delphi mimarisine uyarlamanız gerekiyorsa (navigasyon, arkaplan, loglama ve süreç doğrulama gibi kenar durumlar dahil), bunu yapılandırılmış şekilde memnuniyetle ele alırız:

Uzmanlaşmış teknik bağlamda, entegrasyonlar, veri akışları ve ileriye dönük geliştirme düzgün şekilde etkileşime girmesi gerektiğinde Zxing Delphi ve Fmx Tcameracomponent de önemli rol oynar.

Net-Base ile proje veya modernizasyon girişiminizi görüşün.

Sonraki adım

Konu gerçek bir projeye dönüştüğünde, mimari, mevcut yapı ve işletme erken aşamada birlikte ele alınmalıdır.

Bireysel sorularda destek vermekle kalmıyoruz; kaynak kodu parçacıklarından, legacy konularından veya portal fikirlerinden sağlam bir kurumsal projeye dönüşene kadar da destek veriyoruz.

  • Mevcut durum, hedef durum ve teknik riskler birlikte değerlendirilir.
  • REST, veri erişimi, portallar ve Rollout sonraki işler olarak ertelenmez.
  • Hangi yolun ekonomik ve işletme açısından uygulanabilir olduğunu erken görürsünüz.

Gönderiyi paylaş

Bu gönderiyi doğrudan paylaş

LinkedIn, X, XING, Facebook, WhatsApp ve e-posta hemen kullanılabilir. Instagram için bağlantı ve kısa metni doğrudan hazırlıyoruz.

E-posta

Instagram yeni bir sekmede açılır. Bağlantı ve kısa metin önceden panoya kopyalanır.