Drawing a Tiled Logo over NSImage an 45 degree

Viewed 99

I'm trying to draw a logo tiled over an image at 45 degrees.But I always get a spacing on the left side.

        var y_offset: CGFloat = logo.size.width * sin(45 * (CGFloat.pi / 180.0))
        // the sin of the angle may return zero or negative value,
        // it won't work with this formula
            
        if y_offset >= 0 {
        var x: CGFloat = 0
        while x < size.width {
            var y: CGFloat = 0
            while y < size.height {
                // move to this position
                context.saveGState()

                context.translateBy(x: x, y: y)
                // draw text rotated around its center
                context.rotate(by: ((CGFloat(-45) * CGFloat.pi ) / 180))

                logo.draw(at:NSPoint(x:x,y:y), from: .zero, operation: .sourceOver, fraction: CGFloat(logotransparency))
                
                // reset
                context.restoreGState()
                y = y + CGFloat(y_offset)
            }
            x = x + logo.size.width
        }}
    
    }

This is the result what I get.

enter image description here

As you can see there are some spacing present on the left side.I cannot figure out what I'm doing wrong.I have tried setting y to size.height and decrementing it by y_offset in the loop.But I get the same result.

Update:

var dirtyRect:NSRect=NSMakeRect(0, 0, size.width, size.height)
                let deg45 = CGFloat.pi / 4
                if let ciImage = logo.ciImage {
                          let ciTiled = ciImage.tiled(at: deg45).cropped(to: dirtyRect)
                let color = NSColor.init(patternImage: NSImage.fromCIImage(ciTiled))
                          color.setFill()
                          context.fill(dirtyRect)
                      }
1 Answers

Updated answer

If you need more control over the appearance you can go with manually drawing the overlays. See below code for a fixed version of your original code with two options for spacing.
In production, you would of course want to avoid using ! and move the image loading out of the draw function (even though NSImage(named:) uses a cache).

override func draw(_ dirtyRect: NSRect) {
    
    let bgImage = NSImage(named: "landscape")!
    bgImage.draw(in: dirtyRect)

    let deg45 = CGFloat.pi / 4
    let logo = NSImage(named: "TextTile")!
    
    let context = NSGraphicsContext.current!.cgContext
    
    let h = logo.size.height // (sin(deg45) * logo.size.height) + (cos(deg45) * logo.size.height)
    let w = logo.size.width  // (sin(deg45) * logo.size.width ) + (cos(deg45) * logo.size.width )

    var x: CGFloat = -w
    while x < dirtyRect.width + w {
        var y: CGFloat = -h
        while y < dirtyRect.height + h {
            context.saveGState()
            context.translateBy(x: x, y: y)
            context.rotate(by: deg45)
            logo.draw(at:NSPoint(x:0,y:0),
                      from: .zero,
                      operation: .sourceOver,
                      fraction: 1)
            context.restoreGState()

            y = y + h
        }
        x = x + w
    }

    super.draw(dirtyRect)
}

Original answer

You can set a backgroundColor with a patternImage to for the effect of drawing image tiles in a rect.
To tilt the image by some angle, use CIImage's CIAffineTile option with some transformation.

Here is some example code:

import Cocoa
import CoreImage

class ViewController: NSViewController {

    override func loadView() {

        let size = CGSize(width: 500, height: 500)
        let view = TiledView(frame: CGRect(origin: CGPointZero, size: size))

        self.view = view
    }
}

class TiledView: NSView {

    override func draw(_ dirtyRect: NSRect) {

        let bgImage = NSImage(named: "landscape")!
        bgImage.draw(in: dirtyRect)

        let deg45 = CGFloat.pi / 4
        if let ciImage = NSImage(named: "TextTile")?.ciImage() {

            let ciTiled = ciImage.tiled(at: deg45).cropped(to: dirtyRect)
            let color = NSColor.init(patternImage: NSImage.fromCIImage(ciTiled))
            color.setFill()
            dirtyRect.fill()
        }
        super.draw(dirtyRect)
    }
}

extension NSImage {
    // source: https://rethunk.medium.com/convert-between-nsimage-and-ciimage-in-swift-d6c6180ef026

    func ciImage() -> CIImage? {

        guard let data = self.tiffRepresentation,
              let bitmap = NSBitmapImageRep(data: data) else {

            return nil
        }
        let ci = CIImage(bitmapImageRep: bitmap)
        return ci
    }

    static func fromCIImage(_ ciImage: CIImage) -> NSImage {

        let rep = NSCIImageRep(ciImage: ciImage)
        let nsImage = NSImage(size: rep.size)
        nsImage.addRepresentation(rep)
        return nsImage
    }
}

extension CIImage {

    func tiled(at angle: CGFloat) -> CIImage {

        // try different transforms here
        let transform = CGAffineTransform(rotationAngle: angle)
        return self.applyingFilter("CIAffineTile", parameters: [kCIInputTransformKey: transform])
    }
}

The result looks like this:

enter image description here

Related