Laravel ile QR kod okuma ve görsel işleme kullanarak okuma oranını arttırma
Merhaba, bu yazımda Laravel ile QR kodlu bir belgeden QR kodu nasıl kesip çıkarabileceğimize, bu kesilen kısımda ki QR kodu nasıl okuyabileceğimize ve okuma oranını nasıl arttırabileceğimize değineceğim.
Laravel projesi oluşturma
Bilgisayarınızda composer kurulu ise aşağıdaki şekilde bir Laravel projesi oluşturabilirsiniz, eğer Composer yüklü değilse yüklemeniz gerekmektedir.
composer create-project laravel/laravel qr-project
Kullanmamız gereken paketleri Composer ile aşağıdaki şekilde kurabiliriz:
composer require simplesoftwareio/simple-qrcode "~4"
composer require tarfin-labs/zbar-php
composer require laraveldaily/laravel-invoices:^3.0
laravel-invoices
paketinin kurulumlarını yapmayı unutmuyoruz:
php artisan invoices:install
php artisan invoices:update
Ayrıca paketlerden bazılarını çalıştırabilmek için zbar
ve imagemagick
toollarının kurulumlarını da yapmalısınız, zbar
toolu sayesinde QR kod okuma işlemlerini, imagemagick
sayesinde de görüntü işleme işlemlerini yapabileceğiz, aşağıdaki linklerden kurulum sayfalarına ulaşabilirsiniz:
macOS ve Homebrew kullananlar aşağıdaki bash komutlarını kullanarak her iki Tool’u da rahatlıkla yükleyebilirler:
brew install pkg-config imagemagick
brew install zbar
Imagemagick kurulumunu yaptıktan sonra ilgili PHP Extension’unu kurmayı da unutmamak gerekmekte, PECL kullanarak extension kurulumunu da yapıyoruz:
/opt/homebrew/opt/php@8.1/bin/pecl install imagick
Ben Homebrew kullandığım için php/bin
konumum yukarıdaki şekilde, sizin farklı olabilir, bu yüzden konumu kendinize göre düzenlemeniz gerekmekte.
Kurulum işlemlerimiz bittikten sonra herhangi bir IDE yardımı ile projemizi açalım. Fatura oluşturma, görüntüleme gibi işlemlerimizi yapabilmek adına app
dizini altında Services
adlı bir klasör oluşturalım ve bu klasörün içerisine de InvoiceService
ve Facades
adlı iki klasör daha oluşturalım. İşlem bittikten sonra dizin yapımız aşağıdaki gibi olmalı:
Daha sonra InvoiceService
klasörümüzün altına InvoiceService
adlı boş bir class oluşturalım ardından InvoiceServiceProvider
adlı bir class daha oluşturalım ve içeriğini aşağıdaki şekilde değiştirelim:
<?php
namespace App\Services\InvoiceService;
use Illuminate\Support\ServiceProvider;
class InvoiceServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(InvoiceService::class, function ($app) {
return new InvoiceService();
});
}
}
Böylelikle InvoiceService
class’ımız için bir singleton oluşturmuş olduk, bu singleton sayesinde class’ımızda ki tüm fonksiyonlara sanki birer static fonksiyonmuş gibi ulaşıp kullanabileceğiz, geriye sadece Facade
oluşturmak kaldı, Services/Facades
adlı klasörümüzün içerisine InvoiceService
adlı bir php classı oluşturalım:
Artık ServiceProvider’imizi Laravel’e tanıtıp projenin her yerinden InvoiceService
class’ımıza singleton olarak erişebiliriz.
ServiceProvider’i Laravel’e tanıtmak için config/app.php
açalım ve aşağıdaki şekilde ekleyelim:
Son olarak Facade’mizi de projeye tanıtıp bitiriyoruz:
InvoiceService
oluşturma
Artık InvoiceService
classımızı yazmaya başlayabiliriz, öncelikle sıradan bir fatura belgesi oluşturma fonksiyonu yazalım:
<?php
namespace App\Services\InvoiceService;
use Illuminate\Http\Response;
use LaravelDaily\Invoices\Classes\Buyer;
use LaravelDaily\Invoices\Classes\InvoiceItem;
use LaravelDaily\Invoices\Invoice;
class InvoiceService
{
/**
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \Exception
*/
public function createInvoiceAsPdf(): string
{
$customer = new Buyer([
'name' => 'John Doe',
'custom_fields' => [
'email' => 'test@example.com',
],
]);
$item = (new InvoiceItem())->title('Service 1')->pricePerUnit(2);
$invoice = Invoice::make()
->buyer($customer)
->discountByPercent(10)
->taxRate(15)
->shipping(2)
->addItem($item);
return $invoice->render()->output;
}
}
Bu işlemden sonra InvoiceService::createInvoiceAsPdf()
şeklinde fonksiyonumuza erişebiliriz.
InvoiceController oluşturma
Aşağıdaki artisan komutunu terminalde çalıştıralım:
php artisan make:controller InvoiceController --invokable
Daha sonra oluşan InvoiceController
classını aşağıdaki şekilde kodlayalım:
<?php
namespace App\Http\Controllers;
use App\Services\Facades\InvoiceService;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class InvoiceController extends Controller
{
public function __invoke()
{
return response()
->streamDownload(
callback: function (): void {
echo InvoiceService::createInvoiceAsPdf();
},
name: 'invoice.pdf'
);
}
}
Route oluşturma
routes/web.php
dosyasını açalım ve aşağıdaki şekilde değiştirelim:
<?php
use App\Http\Controllers\InvoiceController;
use Illuminate\Support\Facades\Route;
Route::get('/get-invoice', InvoiceController::class);
Artık projemizde get-invoice
URL’ine girdiğimizde indirilecek dosyanın aşağıdaki gibi olmasını bekliyoruz:
Faturaya QR kod ekleme
Başarılı bir şekilde faturamızı oluşturduk, sırada faturamıza QR kod eklemek olacak, ilk olarak InvoiceService
classımızı açıp fatura template’imize custom bir order_id
ve qr_data
değişkeni ekliyoruz:
<?php
namespace App\Services\InvoiceService;
use Illuminate\Http\Response;
use LaravelDaily\Invoices\Classes\Buyer;
use LaravelDaily\Invoices\Classes\InvoiceItem;
use LaravelDaily\Invoices\Invoice;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
class InvoiceService
{
/**
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \Exception
*/
public function createInvoiceAsPdf(): string
{
$customer = new Buyer([
'name' => 'John Doe',
'custom_fields' => [
'email' => 'test@example.com',
],
]);
$item = (new InvoiceItem())->title('Service 1')->pricePerUnit(2);
$invoice = Invoice::make()
->buyer($customer)
->discountByPercent(10)
->taxRate(15)
->shipping(2)
->addItem($item);
$invoice->setCustomData([
'order_id' => $orderId = random_int(100000,999999),
'qr_data' => base64_encode(QrCode::format('png')->size(100)->generate($orderId)),
]);
return $invoice->render()->output;
}
}
Daha sonra resources/views/vendor/invoices/templates/default.blade.php
yolunda bulunan blade dosyamızı açıp aşağıdaki şekilde bir ekleme yapıyoruz:
{{-- Header --}}
@if($invoice->logo)
<img src="{{ $invoice->getLogo() }}" alt="logo" height="100">
@endif
<p class="text-right m-5">
<img
src="data:image/png;base64,
{!! $invoice->getCustomData()['qr_data'] !!} "
>
</p>
Style kısmına da bu class’ı ekliyoruz:
.m-5 {
margin-right: 3rem !important;
margin-top: 3rem !important;
}
Dosyamızda ki değişiklikleri kaydedip tekrar invoice oluşturduğumuzda aşağıdaki gibi bir görüntü görmüş olmalıyız:
Faturadan QR kod okuma
Öncelikle oluşturduğumuz faturayı dosya yolu storage/app/public/invoice.pdf
olacak şekilde kopyalayalım.
Şimdi sırada oluşturmuş olduğumuz invoice dosyasından bu QR kodu kesip çıkararak taratmak olacak. Eğer QR kod kısmını kesmeden okutmaya çalışırsak tüm dosyanın içerisinde QR kodu yakalama olasılığı oldukça azalmakta, bu yüzden kesme işlemini gerçekleştirmemiz gerekiyor. Aşağıdaki şekilde bir yapı oluşturalım:
config/app.php
içerisine ServiceProvider’imizi ve Facade aliasımızı eklemeyi unutmayalım.
ImageProcessingService
class’ımızı açarak aşağıdaki şekilde düzenleyelim:
<?php
namespace App\Services\ImageProcessingService;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Imagick;
use TarfinLabs\ZbarPhp\Exceptions\ZbarError;
use TarfinLabs\ZbarPhp\Zbar;
class ImageProcessingService
{
public int $edgeLength = 240;
/**
* @throws \ImagickException
* @throws \TarfinLabs\ZbarPhp\Exceptions\UnableToOpen
* @throws \TarfinLabs\ZbarPhp\Exceptions\InvalidFormat
* @throws \Exception
*/
public function readQrCode(string $filePath): string|null
{
$cropedQRPath = 'public/'.Str::random(40).'.png';
$imagickInstance = $this->cropQrCodeFromPDF($filePath);
Storage::put($cropedQRPath, $imagickInstance->getImageBlob());
try {
$readedCode = (new Zbar(Storage::path($cropedQRPath)))->scan();
} catch (ZbarError) {
$readedCode = null;
}
Storage::delete($cropedQRPath);
return $readedCode;
}
/**
* @throws \Exception
*/
public function cropQrCodeFromPDF(string $filePath): Imagick
{
$imagick = new Imagick();
$imagick->setResolution(128, 128);
$imagick->readImageBlob(Storage::get($filePath));
$imageWidth = $imagick->getImageWidth() - $this->edgeLength;
$imagick->cropImage($this->edgeLength + 80, $this->edgeLength + 80, $imageWidth - 80 , 0);
$imagick->resizeImage($this->edgeLength + 80, $this->edgeLength + 80, 1, 0);
$imagick->setImageFormat('png');
return $imagick;
}
}
Classımızı düzenledikten sonra ImageProcessingController
adlı bir Controller oluşturalım:
php artisan make:controller ImageProcessingController --invokable
Ve içeriğini aşağıdaki şekilde düzenleyelim:
<?php
namespace App\Http\Controllers;
use App\Services\Facades\ImageProcessingService;
use Illuminate\Http\JsonResponse;
class ImageProcessingController extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(): JsonResponse
{
return \response()->json([
'code' => ImageProcessingService::readQrCode('public/invoice.pdf')
]);
}
}
Daha sonra routes/web.php
açarak aşağıdaki gibi düzenleyelim:
<?php
use App\Http\Controllers\ImageProcessingController;
use App\Http\Controllers\InvoiceController;
use Illuminate\Support\Facades\Route;
Route::get('/get-invoice', InvoiceController::class);
Route::get('/process-qr-code', ImageProcessingController::class);
Tarayıcımızı açıp /process-qr-code
URL’ine girelim ve sonuca bakalım:
Gördüğünüz gibi üretmiş olduğumuz QR kod başarıyla okunarak sonuç Response’da bize iletildi.
Görüntü işleme ile QR okunma oranını arttırma
Günlük hayatta taratılan belgelerde QR kodlar çok okunaklı olmayabiliyor, bu yüzden okunma oranını arttırmak için yapılabilecek bir kaç görüntü işleme tekniği bulunmakta, yapılan işlemlerle ilgili fazla ayrıntıya girmeyeceğim çünkü görüntü işleme kısmını da makalenin içine dahil edersek çok çok uzun bir makale olması gerekeceği kesin :) ama ufak bir şekilde açıklamam gerekirse, yapılan işlemler arka planda ki QR koda ait olmayan pixelleri beyazlaştırıp QR kod kısmını da siyahlaştırarak daha belirgin bir QR kod elde etmemize yardımcı olmakta.
ImageProcessingService
class’ımızı açıp readQrCode
fonksiyonunu aşağıdaki gibi düzenleyelim:
/**
* @throws \ImagickException
* @throws \TarfinLabs\ZbarPhp\Exceptions\UnableToOpen
* @throws \TarfinLabs\ZbarPhp\Exceptions\InvalidFormat
* @throws \Exception
*/
public function readQrCode(string $filePath): string|null
{
$cropedQRPath = 'public/'.Str::random(40).'.png';
$imagickInstance = $this->cropQrCodeFromPDF($filePath);
Storage::put($cropedQRPath, $imagickInstance->getImageBlob());
try {
$readedCode = (new Zbar(Storage::path($cropedQRPath)))->scan();
} catch (ZbarError) {
$imagickInstance->setImageBackgroundColor('#FFFFFF');
$imagickInstance->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN);
$imagickInstance->setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE);
$imagickInstance->autoLevelImage();
$imagickInstance->autoThresholdImage(1); // 1 means we choose OTSU threshold method
$kernel = ImagickKernel::fromBuiltIn(Imagick::KERNEL_SQUARE, '1');
$imagickInstance->morphology(\Imagick::MORPHOLOGY_OPEN, 1, $kernel);
Storage::disk('local')->put($cropedQRPath, $imagickInstance->getImageBlob());
try {
$readedCode = (new Zbar(Storage::path($cropedQRPath)))->scan();
} catch (ZbarError) {
$readedCode = null;
}
}
Storage::delete($cropedQRPath);
return $readedCode;
}
Bu işlemleri catch (ZbarError)
kısmından sonra yaptırmamızın sebebi, eğer ki QR kod okuma başarılı ise ekstradan işlem yaptırmamıza gerek olmaması, eğer QR kodu okuyup okumada başarısız olduysak gerekli görüntü işleme algoritmalarının çalışması bize zaman kazandıracaktır.
Bu makaleyi hem Türkçe hem de İngilizce dillerinde okuyabilirsiniz.