How to Add a Watermark to an EFQRCode: Complete Swift Implementation Guide

To add a watermark to an EFQRCode, create an EFStyleParamImage instance containing your image data and pass it to your style's copyWith(watermarkImage:) method before initializing the generator.

EFQRCode is a Swift library maintained by efprefix that generates customizable QR codes through a style-centric architecture. Adding a watermark overlays an image behind the QR modules, allowing you to create branded codes that remain fully scannable while displaying background imagery.

Understanding EFQRCode Watermark Architecture

EFQRCode implements watermarks through its style-centric architecture rather than direct generator parameters. The EFQRCode.Generator receives a style object conforming to EFQRCodeStyleBase, which holds optional image parameters defined in Source/Styles/EFQRCodeStyle.swift (lines 56–61).

Watermark vs. Icon Images

The style protocol defines two distinct image parameters:

  • watermarkImage (EFStyleParamImage?): An overlay image that renders behind the QR modules, typically covering 100% of the QR size
  • iconImage (EFStyleParamImage?): A small logo placed in the center on top of the modules

While the iconImage sits above the QR code pattern, the watermarkImage is drawn first (lines 694–726 of Source/EFQRCode+Generator.swift), creating a background layer that the QR modules overlay.

Style Support for Watermarks

Not all styles support watermarks. Most image-based styles—such as .image, .resampleImage, .imageFill, and .randomRectangle—implement copyWith(iconImage:watermarkImage:) and return the watermark through getParamImages(). However, EFQRCodeStyle.basic returns nil for watermark images (see EFQRCodeStyleBasic.getParamImages), meaning you must select an appropriate style to use this feature.

Adding a Static Watermark to EFQRCode

Static watermarks use EFStyleParamImage.static(image:) to wrap a CGImage for placement behind the QR code.

import EFQRCode
import UIKit

// Load your watermark image as CGImage
guard let watermarkCGImage = UIImage(named: "BrandWatermark")?.cgImage else {
    fatalError("Watermark image not found")
}

// Wrap in EFStyleParamImage
let watermarkParam = EFStyleParamImage.static(image: watermarkCGImage)

// Create a style that supports watermarks and clone it with the watermark
let baseStyle = EFQRCodeStyle.image(params: .init())
let styleWithWatermark = baseStyle.copyWith(watermarkImage: watermarkParam)

// Generate the QR code
let generator = try EFQRCode.Generator("https://example.com", style: styleWithWatermark)
let qrImage = try generator.toImage()

The copyWith(watermarkImage:) method (defined in Source/Styles/EFQRCodeStyle.swift, lines 493–502) creates a new style instance with your watermark while preserving other style parameters. When generator.toImage() executes, it calls style.getParamImages() to retrieve both the icon and watermark for rendering.

Adding an Animated Watermark to EFQRCode

EFQRCode supports animated watermarks using GIF or APNG sequences through EFStyleParamImage.animated(images:imageDelays:).

import EFQRCode
import ImageIO

// Extract frames and delays from your animated source
let frames: [CGImage] = // ... extract animation frames
let delays: [CGFloat] = // ... extract frame durations

// Create animated watermark parameter
let animatedWatermark = EFStyleParamImage.animated(
    images: frames, 
    imageDelays: delays
)

// Apply to style and generate
let style = EFQRCodeStyle.image(params: .init())
    .copyWith(watermarkImage: animatedWatermark)

let generator = try EFQRCode.Generator("Animated Content", style: style)
let gifData = try generator.toAnimatedImage(
    format: .gif, 
    size: CGSize(width: 500, height: 500)
)

When generating animated output, the generator invokes EFStyleParamImage.write(id:rect:opacity:mode:) (animated branch, lines 71–99 of EFQRCodeStyle.swift), which constructs SVG <animate> elements and later encodes the sequence into the target format.

Configuring Watermark Opacity and Scaling

Control watermark appearance using the style's watermarkAlpha and watermarkMode parameters before cloning.

let baseStyle = EFQRCodeStyle.image(params: .init())

// Configure appearance parameters
baseStyle.params.watermarkAlpha = 0.4  // 40% opacity (0.0 - 1.0)
baseStyle.params.watermarkMode = .scaleAspectFill  // Maintain aspect ratio, cover full area

// Then apply your watermark
let finalStyle = baseStyle.copyWith(watermarkImage: watermarkParam)

The EFImageMode enum (defined in Source/Type/EFImageMode.swift, lines 30–70) provides three scaling options:

  • .scaleToFill: Stretches image to fill the QR bounds
  • .scaleAspectFit: Scales to fit within bounds, maintaining aspect ratio with potential letterboxing
  • .scaleAspectFill: Scales to cover entire QR area, maintaining aspect ratio by cropping

During generation, checkIfNeedResize(size:) validates whether the watermark exceeds the target canvas fraction and applies the selected EFImageMode scaling before rendering.

How the Generator Processes Watermarks

The rendering flow in Source/EFQRCode+Generator.swift (lines 694–726) follows this sequence:

  1. Image Retrieval: The generator calls style.getParamImages() to obtain the (iconImage, watermarkImage) tuple
  2. Resizing: checkIfNeedResize(size:) validates dimensions against the QR canvas size (watermarks use 100% of QR size)
  3. SVG Injection: EFStyleParamImage.write(id:rect:opacity:mode:) converts the image to Base-64-encoded PNG and injects an <image> element into the SVG output at the calculated rectangle position
  4. Layer Ordering: The watermark renders first, then QR modules draw on top, ensuring scannability

Because watermarks are drawn as SVG <image> elements behind the QR pattern, they do not interfere with the code's error correction capabilities provided the opacity (watermarkAlpha) leaves sufficient contrast for the modules.

Summary

  • Watermarks render behind QR modules, while icons render on top
  • Use EFStyleParamImage.static(image:) for PNG/JPEG assets or .animated(images:imageDelays:) for GIF/APNG sequences
  • Apply watermarks via copyWith(watermarkImage:) available in image-based styles (.image, .resampleImage, etc.)
  • Control transparency with watermarkAlpha (0.0–1.0) and sizing with EFImageMode options
  • The generator processes watermarks in Source/EFQRCode+Generator.swift by calling getParamImages() and injecting SVG image elements

Frequently Asked Questions

What is the difference between a watermark and an icon in EFQRCode?

A watermark (watermarkImage) renders as a full-size background layer behind the QR modules, while an icon (iconImage) appears as a small, centered logo overlaid on top of the modules. According to the EFQRCodeStyleBase protocol in Source/Styles/EFQRCodeStyle.swift, both are optional EFStyleParamImage? properties, but they occupy different z-index positions in the final render.

Can I use animated GIFs as watermarks?

Yes. Pass an array of CGImage frames and delay values to EFStyleParamImage.animated(images:imageDelays:) instead of static(image:). The generator processes animated watermarks through the animated branch of write(id:rect:opacity:mode:) (lines 71–99), creating proper frame sequences in the final GIF or APNG output.

Does adding a watermark affect QR code scannability?

Watermarks generally do not break scannability because they render behind the QR modules, leaving the actual data pattern unobstructed. However, excessive watermarkAlpha values (high opacity) or low-contrast images may reduce scanning reliability. The library applies the watermark before drawing modules (lines 694–726 of Source/EFQRCode+Generator.swift), ensuring the QR pattern maintains priority.

Which EFQRCode styles support watermarks?

Image-based styles including .image, .resampleImage, .imageFill, and .randomRectangle support watermarks through the copyWith(iconImage:watermarkImage:) method. The basic style (EFQRCodeStyle.basic) returns nil for getParamImages() and cannot display watermarks. Verify your chosen style implements EFQRCodeStyleBase with the copyWith method before attempting to add a watermark.

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 →