Skip to content

Commit

Permalink
Merge branch '272-crash-when-exporting' into 'release/23.0'
Browse files Browse the repository at this point in the history
Resolve "Crash when exporting"

Closes #272

See merge request highlighter/app!242
  • Loading branch information
Arclite committed Mar 11, 2023
2 parents 36e8271 + ed3fc6e commit 1f89ddd
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 30 deletions.
21 changes: 19 additions & 2 deletions Editing/BrushStampFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,20 @@ import ErrorHandling
import UIKit

public class BrushStampFactory: NSObject {
public static func brushStart(scaledToHeight height: CGFloat, color: UIColor) -> UIImage {
public static func brushImages(for shape: Shape, color: UIColor, scale: CGFloat) throws -> (CGImage, CGImage) {
let startHeight = shape.topLeft.distance(to: shape.bottomLeft)
let startImage = BrushStampFactory.brushStart(scaledToHeight: startHeight, color: color)
let endHeight = shape.topRight.distance(to: shape.bottomRight)
let endImage = BrushStampFactory.brushEnd(scaledToHeight: endHeight, color: color)

guard let startCGImage = startImage.cgImage(scale: scale),
let endCGImage = endImage.cgImage(scale: scale)
else { throw BrushStampFactoryError.cannotGenerateCGImage }

return (startCGImage, endCGImage)
}

private static func brushStart(scaledToHeight height: CGFloat, color: UIColor) -> UIImage {
guard let startImage = UIImage(named: "Brush Start") else { ErrorHandling.crash("Unable to load brush start image") }

let brushScale = height / startImage.size.height
Expand All @@ -22,7 +35,7 @@ public class BrushStampFactory: NSObject {
}
}

public static func brushEnd(scaledToHeight height: CGFloat, color: UIColor) -> UIImage {
private static func brushEnd(scaledToHeight height: CGFloat, color: UIColor) -> UIImage {
guard let endImage = UIImage(named: "Brush End") else { ErrorHandling.crash("Unable to load brush end image") }

let brushScale = height / endImage.size.height
Expand Down Expand Up @@ -56,3 +69,7 @@ public class BrushStampFactory: NSObject {
}
}
}

enum BrushStampFactoryError: Error {
case cannotGenerateCGImage
}
12 changes: 9 additions & 3 deletions Editing/Editing View/Workspace/PhotoEditingRedactionView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Created by Geoff Pado on 5/6/19.
// Copyright © 2019 Cocoatype, LLC. All rights reserved.

import ErrorHandling
import UIKit

public class PhotoEditingRedactionView: UIView {
Expand Down Expand Up @@ -49,9 +50,14 @@ public class PhotoEditingRedactionView: UIView {
}

private func updateDisplay() {
layer.sublayers = redactions.flatMap { redaction -> [RedactionPathLayer] in
return redaction.parts
.map { RedactionPathLayer(part: $0, color: redaction.color)}
do {
layer.sublayers = try redactions.flatMap { redaction -> [RedactionPathLayer] in
return try redaction.parts
.map { try RedactionPathLayer(part: $0, color: redaction.color, scale: layer.contentsScale)}
}
} catch {
ErrorHandling.log(error)
layer.sublayers = nil
}
}

Expand Down
13 changes: 5 additions & 8 deletions Editing/Editing View/Workspace/RedactionPathLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
import Foundation

class RedactionPathLayer: CALayer {
init(part: RedactionPart, color: UIColor) {
init(part: RedactionPart, color: UIColor, scale: CGFloat) throws {
let pathBounds: CGRect

switch part {
case .shape(let shape):
let startHeight = shape.topLeft.distance(to: shape.bottomLeft)
let startImage = BrushStampFactory.brushStart(scaledToHeight: startHeight, color: color)
let endHeight = shape.topRight.distance(to: shape.bottomRight)
let endImage = BrushStampFactory.brushEnd(scaledToHeight: endHeight, color: color)
let (startImage, endImage) = try BrushStampFactory.brushImages(for: shape, color: color, scale: scale)

let angle = shape.angle
let startVector = CGSize(
Expand Down Expand Up @@ -78,13 +75,13 @@ class RedactionPathLayer: CALayer {
context.translateBy(x: shape.topLeft.x - frame.origin.x, y: shape.topLeft.y - frame.origin.y)
context.rotate(by: shape.angle)
context.translateBy(x: -startImage.size.width, y: 0)
context.draw(startImage.cgImage!, in: CGRect(origin: .zero, size: startImage.size))
context.draw(startImage, in: CGRect(origin: .zero, size: startImage.size))
context.restoreGState()

context.saveGState()
context.translateBy(x: shape.topRight.x - frame.origin.x, y: shape.topRight.y - frame.origin.y)
context.rotate(by: shape.angle)
context.draw(endImage.cgImage!, in: CGRect(origin: .zero, size: endImage.size))
context.draw(endImage, in: CGRect(origin: .zero, size: endImage.size))
context.restoreGState()

case let .path(path, dikembeMutombo):
Expand All @@ -101,7 +98,7 @@ class RedactionPathLayer: CALayer {
}

private enum Part {
case shape(shape: Shape, startImage: UIImage, endImage: UIImage)
case shape(shape: Shape, startImage: CGImage, endImage: CGImage)

// dikembeMutombo by @KaenAitch on 8/1/22
// the brush stamp image
Expand Down
40 changes: 23 additions & 17 deletions Editing/Export/PhotoExportRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,33 @@ public actor PhotoExportRenderer {
context.restoreGState()

// draw redactions
let drawings = redactions.flatMap { redaction -> [(path: UIBezierPath, color: UIColor)] in
return redaction.paths
.map { (path: $0, color: redaction.color) }
let drawings = redactions.flatMap { redaction -> [(part: RedactionPart, color: UIColor)] in
return redaction.parts
.map { (part: $0, color: redaction.color) }
}

drawings.forEach { drawing in
let (path, color) = drawing
let borderBounds = path.strokeBorderPath.bounds
if path.isShape {
let startImage = BrushStampFactory.brushStart(scaledToHeight: borderBounds.height, color: color)
let endImage = BrushStampFactory.brushEnd(scaledToHeight: borderBounds.height, color: color)
try drawings.forEach { drawing in
let (part, color) = drawing
switch part {
case .shape(let shape):
let (startImage, endImage) = try BrushStampFactory.brushImages(for: shape, color: color, scale: 1)

color.setFill()
UIBezierPath(rect: borderBounds).fill()

let startRect = CGRect(origin: borderBounds.origin, size: startImage.size).offsetBy(dx: -startImage.size.width, dy: 0)
context.draw(startImage.cgImage!, in: startRect)

let endRect = CGRect(origin: borderBounds.origin, size: endImage.size).offsetBy(dx: borderBounds.width, dy: 0)
context.draw(endImage.cgImage!, in: endRect)
} else {
UIBezierPath(cgPath: shape.path).fill()

context.saveGState()
context.translateBy(x: shape.topLeft.x, y: shape.topLeft.y)
context.rotate(by: shape.angle)
context.translateBy(x: -startImage.size.width, y: 0)
context.draw(startImage, in: CGRect(origin: .zero, size: startImage.size))
context.restoreGState()

context.saveGState()
context.translateBy(x: shape.topRight.x, y: shape.topRight.y)
context.rotate(by: shape.angle)
context.draw(endImage, in: CGRect(origin: .zero, size: endImage.size))
context.restoreGState()
case .path(let path):
let stampImage = BrushStampFactory.brushStamp(scaledToHeight: path.lineWidth, color: color)
let dashedPath = path.dashedPath
dashedPath.forEachPoint { point in
Expand Down
11 changes: 11 additions & 0 deletions Editing/Extensions/CGImageExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Created by Geoff Pado on 2/27/23.
// Copyright © 2023 Cocoatype, LLC. All rights reserved.

import Foundation
import CoreGraphics

extension CGImage {
var size: CGSize {
CGSize(width: Double(width), height: Double(height))
}
}
13 changes: 13 additions & 0 deletions Editing/Extensions/UIImageExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ extension UIImage {
return size
}
}

public func cgImage(scale: CGFloat) -> CGImage? {
if abs(scale - self.scale) < 0.01 {
return cgImage
}

let format = UIGraphicsImageRendererFormat()
format.scale = scale
let scaledImage = UIGraphicsImageRenderer(size: size, format: format).image { _ in
self.draw(at: .zero)
}
return scaledImage.cgImage
}
}

extension UIImage.Orientation {
Expand Down
4 changes: 4 additions & 0 deletions Highlighter.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
04431E5E2358117B009FA1F6 /* RedactionSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04431E5C23581116009FA1F6 /* RedactionSerializer.swift */; };
0445AA592371208B00EB2A7B /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0445AA582371208B00EB2A7B /* ImageDownloader.swift */; };
0447266E29ADAD72000345F8 /* TelemetryClient in Frameworks */ = {isa = PBXBuildFile; productRef = 0447266D29ADAD72000345F8 /* TelemetryClient */; };
0447267029ADB84E000345F8 /* CGImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0447266F29ADB84E000345F8 /* CGImageExtensions.swift */; };
044D4C1F2547B7BD00701166 /* main.xib in Resources */ = {isa = PBXBuildFile; fileRef = 044D4C1D2547B7BD00701166 /* main.xib */; };
044D4C232547B7BD00701166 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 044D4C212547B7BD00701166 /* InfoPlist.strings */; };
044D4C4C2547C06600701166 /* RedactAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044D4C4B2547C06600701166 /* RedactAction.swift */; };
Expand Down Expand Up @@ -611,6 +612,7 @@
04431E5823556AC2009FA1F6 /* PhotoExtensionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoExtensionViewController.swift; sourceTree = "<group>"; };
04431E5C23581116009FA1F6 /* RedactionSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactionSerializer.swift; sourceTree = "<group>"; };
0445AA582371208B00EB2A7B /* ImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
0447266F29ADB84E000345F8 /* CGImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGImageExtensions.swift; sourceTree = "<group>"; };
044D4C182547B7BC00701166 /* AutomatorActions.action */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutomatorActions.action; sourceTree = BUILT_PRODUCTS_DIR; };
044D4C1E2547B7BD00701166 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/main.xib; sourceTree = "<group>"; };
044D4C202547B7BD00701166 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1672,6 +1674,7 @@
041EFF182251AAFE0058D8EE /* UIViewControllerExtensions.swift */,
04D68BC4225EE2C000D09BBD /* GeometryExtensions.swift */,
043CD9D0226EB0F60012F5AE /* UIImageExtensions.swift */,
0447266F29ADB84E000345F8 /* CGImageExtensions.swift */,
04A3AEF5227A8CE700F21229 /* UIBezierPathExtensions.swift */,
046C406E24820C720069E8FB /* StringExtensions.swift */,
045CA3A92771823D00DEA988 /* URLExtensions.swift */,
Expand Down Expand Up @@ -2572,6 +2575,7 @@
04750E9A26D9EF1900E98BB4 /* UndoBarButtonItem.swift in Sources */,
04750E9826D9EF0700E98BB4 /* RedoBarButtonItem.swift in Sources */,
04975D5926106DC100AA2771 /* PhotoEditingCanvasView.m in Sources */,
0447267029ADB84E000345F8 /* CGImageExtensions.swift in Sources */,
045CA3E827719A9E00DEA988 /* DesktopSeekBackgroundView.swift in Sources */,
0474B07627C0A2880031210C /* DefaultsValue.swift in Sources */,
0493D2D624B6BAE400FF99F4 /* ColorPickerViewController.swift in Sources */,
Expand Down

0 comments on commit 1f89ddd

Please sign in to comment.