Image Resize & Watermark — Swift source
Batch-resize images by width, height or percentage and stamp a text watermark with position, opacity and size control — entirely in your browser, nothing uploaded.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Image Resize & Watermark — Swift (5.9, CoreGraphics + CoreText) port of the image-resize-and-watermark tool.
import CoreGraphics
import CoreText
import Foundation
import ImageIO
import UniformTypeIdentifiers
enum WatermarkError: Error { case unreadableInput, noGraphicsContext, encodeFailed }
// Mirrors the TS resizeAndWatermark: aspect-preserving resize, then a
// bottom-right watermark — white fill at 70% alpha over a dark outline —
// then a lossy WebP encode at 0.92.
func resizeAndWatermark(_ input: URL, _ output: URL, targetWidth: Int, watermark: String) throws {
// 1. Decode.
guard let src = CGImageSourceCreateWithURL(input as CFURL, nil),
let image = CGImageSourceCreateImageAtIndex(src, 0, nil) else {
throw WatermarkError.unreadableInput
}
// 2. Aspect-preserving size: h = round(h / w * targetWidth).
let w = targetWidth
let h = Int((Double(image.height) / Double(image.width) * Double(w)).rounded())
// 3. Draw scaled (interpolation ~ imageSmoothingQuality 'high').
let space = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
guard let ctx = CGContext(
data: nil, width: w, height: h, bitsPerComponent: 8, bytesPerRow: 0, space: space,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else { throw WatermarkError.noGraphicsContext }
ctx.interpolationQuality = .high
ctx.draw(image, in: CGRect(x: 0, y: 0, width: w, height: h))
// 4. Watermark. Core Text merges the TS strokeText + fillText: a negative
// kCTStrokeWidthAttributeName means "stroke AND fill" in one draw, with
// the width as a percentage of the font size (−4 ⇒ 4%).
let fontSize = Int((Double(min(w, h)) * 6 / 100).rounded())
let attrs = [
kCTFontAttributeName: CTFontCreateUIFontForLanguage(.system, CGFloat(fontSize), nil)!,
kCTForegroundColorAttributeName: CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 0.7),
kCTStrokeColorAttributeName: CGColor(srgbRed: 0, green: 0, blue: 0, alpha: 0.55),
kCTStrokeWidthAttributeName: -4.0,
] as CFDictionary
guard let attrString = CFAttributedStringCreate(nil, watermark as CFString, attrs) else {
throw WatermarkError.noGraphicsContext
}
let line = CTLineCreateWithAttributedString(attrString)
// CG's origin is bottom-left, so the TS baseline h-8 lands at y = 8.
let textWidth = CTLineGetTypographicBounds(line, nil, nil, nil)
ctx.textPosition = CGPoint(x: CGFloat(w) - textWidth - 8, y: 8)
CTLineDraw(line, ctx)
// 5. Encode WebP at 0.92 (kCGImageDestinationLossyCompressionQuality).
guard let outImage = ctx.makeImage(),
let dest = CGImageDestinationCreateWithURL(
output as CFURL, UTType.webP.identifier as CFString, 1, nil
) else { throw WatermarkError.encodeFailed }
CGImageDestinationAddImage(
dest, outImage,
[kCGImageDestinationLossyCompressionQuality: 0.92] as CFDictionary
)
guard CGImageDestinationFinalize(dest) else { throw WatermarkError.encodeFailed }
}
Also available in 8 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →