How to Recognize QR Codes from an Image Using EFQRCode: A Complete Guide

EFQRCode provides a built-in recognizer class that uses Core Image's CIDetector to extract QR code payloads from any CGImage, with automatic fallback to grayscale processing for difficult-to-read codes.

EFQRCode is a lightweight, pure-Swift library for generating and recognizing QR codes across all Apple platforms. When you need to recognize QR codes from an image using EFQRCode, the library leverages Core Image's built-in detector with a two-pass accuracy strategy to maximize recognition rates on iOS, macOS, tvOS, watchOS, and visionOS.

How EFQRCode Recognition Works

The recognition engine is implemented in Source/EFQRCode+Recognizer.swift. It wraps Core Image's CIDetector in a platform-agnostic API that accepts any CGImage and returns an array of decoded strings.

The recognizer is designed to be self-contained and cache-friendly. It stores successful results in a private contentArray property, and automatically clears this cache whenever the input image changes via a didSet observer.

Step-by-Step Recognition Pipeline

Input: CGImage

The public API expects a CGImage rather than a UI-specific type like UIImage or NSImage. This design keeps the recognizer portable across all Swift-supported Apple platforms. In Source/Extension/CGImage+EFQRCode.swift, the library provides convenience methods to convert between CGImage, CIImage, and grayscale representations.

Core Image Conversion and Detection

Once initialized with a CGImage, the recognizer calls ciImage() (defined in CGImage+EFQRCode.swift, lines 27-30) to wrap the Core Graphics image in a CIImage.

Detection happens in Source/Extension/CIImage+EFQRCode.swift via the recognizeQRCode(options:) method. This creates a CIDetector of type CIDetectorTypeQRCode with the CIDetectorAccuracyHigh option, then extracts all CIQRCodeFeature instances and maps their messageString properties to the result array.

Grayscale Fallback Strategy

If the high-accuracy scan returns an empty array, the recognizer implements a robust fallback mechanism. It calls CGImage.grayscale() (lines 10-24 of CGImage+EFQRCode.swift) to create a low-contrast grayscale bitmap context, then repeats detection with CIDetectorAccuracyLow. This two-pass approach significantly improves recognition rates for images with poor lighting, color interference, or low contrast.

Result Caching

The recognizer maintains an internal contentArray to cache decoded strings. When you call recognize(), the method first checks this cache; if nil, it executes the full detection pipeline. Changing the image property automatically invalidates the cache via a didSet observer, ensuring you never receive stale results when processing new images.

Practical Code Examples

Basic QR Code Recognition

This minimal example demonstrates loading an image and extracting QR codes:

import EFQRCode
import CoreGraphics

// Load a CGImage (UIImage/NSImage → .cgImage works on all platforms)
guard let cgImage = UIImage(named: "qrcodeSample")?.cgImage else {
    fatalError("Unable to load test image")
}

// Initialize the recognizer
let recognizer = EFQRCode.Recognizer(image: cgImage)

// Perform recognition
let codes = recognizer.recognize()

if codes.isEmpty {
    print("No QR codes found")
} else {
    for (index, code) in codes.enumerated() {
        print("QR code #\(index): \(code)")
    }
}

The recognizer automatically falls back to grayscale processing if the initial scan fails.

Generate and Recognize Round-Trip

This example mirrors the unit test in Tests/Tests.swift (lines 51-62), demonstrating a complete workflow:

import EFQRCode

let payload = "https://github.com/EFPrefix/EFQRCode"

// Generate a QR code
let generator = try EFQRCode.Generator(payload, style: .basic(params: .init()))
let cgImage = try generator.toImage(width: 256).cgImage()

// Recognize the generated image
let detected = EFQRCode.Recognizer(image: cgImage).recognize()
assert(detected.first == payload, "Recognition failed")
print("Successfully recognized: \(detected[0])")

Handling Multiple QR Codes

The recognizer always returns an array because a single image may contain multiple codes:

let multiCodeImage: CGImage = // obtain image containing several QR codes
let results = EFQRCode.Recognizer(image: multiCodeImage).recognize()
print("Found \(results.count) QR codes:")
results.forEach { print($0) }

Key Source Files and Architecture

File Description
Source/EFQRCode+Recognizer.swift Public EFQRCode.Recognizer class that orchestrates detection, caching, and fallback logic.
Source/Extension/CGImage+EFQRCode.swift CGImage utilities including PNG export, grayscale conversion, and the ciImage() bridge to Core Image.
Source/Extension/CIImage+EFQRCode.swift Core Image helpers including conversion to CGImage and the recognizeQRCode(options:) detector implementation.
Tests/Tests.swift Unit tests demonstrating generation and recognition round-trips; useful reference for implementation patterns.
README.md High-level documentation covering recognition workflows and platform requirements.

All Core Image extensions are guarded with #if canImport(CoreImage) to ensure compilation only on supported platforms.

Summary

  • EFQRCode.Recognizer provides a platform-agnostic API for QR code recognition using CGImage input.
  • The recognition pipeline uses Core Image's CIDetector with a two-pass strategy: high accuracy first, then grayscale fallback with low accuracy.
  • Automatic caching prevents redundant processing when calling recognize() multiple times on the same image.
  • The implementation spans EFQRCode+Recognizer.swift, CGImage+EFQRCode.swift, and CIImage+EFQRCode.swift.
  • All Apple platforms supporting Core Image (iOS, macOS, tvOS, watchOS, visionOS) are supported.

Frequently Asked Questions

What image format does EFQRCode require for recognition?

EFQRCode's recognizer accepts any CGImage regardless of original format (PNG, JPEG, HEIC, etc.). The library handles conversion to CIImage internally via the ciImage() extension in CGImage+EFQRCode.swift. If you're working with UIImage (iOS) or NSImage (macOS), simply access their .cgImage property before passing to the recognizer.

Does EFQRCode support recognizing multiple QR codes in one image?

Yes. The recognize() method always returns an array of strings ([String]) because a single image may contain multiple QR codes. The underlying CIDetector in CIImage+EFQRCode.swift extracts all CIQRCodeFeature objects from the image, and the recognizer maps each feature's messageString to the result array.

What platforms support EFQRCode recognition?

Recognition works on any platform that supports Core Image: iOS, macOS, tvOS, watchOS, and visionOS. The relevant code is wrapped in #if canImport(CoreImage) compiler directives to ensure the library compiles only when Core Image is available. Linux and other non-Apple platforms are not supported for recognition because they lack the CIDetector API.

How does EFQRCode handle difficult-to-read QR codes?

EFQRCode implements a robust two-pass detection strategy in EFQRCode+Recognizer.swift. First, it attempts recognition with CIDetectorAccuracyHigh on the original image. If that returns no results, it automatically converts the image to grayscale using CGImage.grayscale() (defined in CGImage+EFQRCode.swift) and retries with CIDetectorAccuracyLow. This fallback mechanism significantly improves recognition rates for low-contrast, shadowed, or color-distorted images.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →