Skip to content

Commit

Permalink
[UIImage+PhotoManipulator@drawText()] Change: Add shadow, use TextStyle
Browse files Browse the repository at this point in the history
  • Loading branch information
guhungry committed Jul 13, 2024
1 parent 001dcf4 commit 8c0cd6a
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 17 deletions.
4 changes: 4 additions & 0 deletions WCPhotoManipulator.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
CA10C5262BB331B7008464A7 /* FlipMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA10C5252BB331B7008464A7 /* FlipMode.swift */; };
CA11C0FB2C0C1E9B005542AA /* RotationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA11C0FA2C0C1E9B005542AA /* RotationMode.swift */; };
CADEFF822C4147EC00DFE3E3 /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CADEFF812C4147EC00DFE3E3 /* TextStyle.swift */; };
CC92AF5124D250160061CC87 /* FoolSonarcloud.m in Sources */ = {isa = PBXBuildFile; fileRef = CC92AF5024D250160061CC87 /* FoolSonarcloud.m */; };
CC92AF6A24D545070061CC87 /* BitmapUtilsSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC92AF6924D545070061CC87 /* BitmapUtilsSwiftTests.swift */; };
CC92AF6C24D558500061CC87 /* FileUtilsSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC92AF6B24D558500061CC87 /* FileUtilsSwiftTests.swift */; };
Expand Down Expand Up @@ -55,6 +56,7 @@
/* Begin PBXFileReference section */
CA10C5252BB331B7008464A7 /* FlipMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlipMode.swift; sourceTree = "<group>"; };
CA11C0FA2C0C1E9B005542AA /* RotationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotationMode.swift; sourceTree = "<group>"; };
CADEFF812C4147EC00DFE3E3 /* TextStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextStyle.swift; sourceTree = "<group>"; };
CC92AF4F24D250160061CC87 /* FoolSonarcloud.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FoolSonarcloud.h; sourceTree = "<group>"; };
CC92AF5024D250160061CC87 /* FoolSonarcloud.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FoolSonarcloud.m; sourceTree = "<group>"; };
CC92AF6924D545070061CC87 /* BitmapUtilsSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapUtilsSwiftTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -108,6 +110,7 @@
CA10C5252BB331B7008464A7 /* FlipMode.swift */,
CCC2506D24D05EE7000BAC48 /* ResizeMode.swift */,
CA11C0FA2C0C1E9B005542AA /* RotationMode.swift */,
CADEFF812C4147EC00DFE3E3 /* TextStyle.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -278,6 +281,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CADEFF822C4147EC00DFE3E3 /* TextStyle.swift in Sources */,
CCC2506C24D05C97000BAC48 /* BitmapUtils.swift in Sources */,
CCC2507024D060C9000BAC48 /* UIImage+PhotoManipulator.swift in Sources */,
CA11C0FB2C0C1E9B005542AA /* RotationMode.swift in Sources */,
Expand Down
145 changes: 145 additions & 0 deletions WCPhotoManipulator/Models/TextStyle.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//
// TextStyle.swift
// WCPhotoManipulator
//
// Created by Woraphot Chokratanasombat on 12/7/24.
// Copyright © 2024 Woraphot Chokratanasombat. All rights reserved.
//

import UIKit

/// A class representing the style of text, including color, font, thickness, rotation, and shadow properties.
@objc public class TextStyle : NSObject {
/// The color of the text.
let color: UIColor
/// The font of the text.
let font: UIFont
/// The thickness of the text. Default value is 0.
let thickness: CGFloat
/// The rotation angle of the text in degrees. Default value is 0.
let rotation: CGFloat
/// The blur radius of the text's shadow. Default value is 0.
let shadowRadius: CGFloat
/// The offset of the text's shadow. Default value is CGSize(width: 0, height: 0).
let shadowOffset: CGSize
/// The color of the text's shadow. Default value is nil (no shadow color).
let shadowColor: UIColor?

/// Computed property that creates and returns an NSShadow object based on the shadow properties.
var shadow: NSShadow {
let shadow = NSShadow()
shadow.shadowBlurRadius = shadowRadius
shadow.shadowOffset = shadowOffset
shadow.shadowColor = shadowColor
return shadow
}

/// Initializes a new TextStyle object with specified properties.
///
/// - Parameters:
/// - color: The color of the text.
/// - font: The font of the text.
/// - thickness: The thickness of the text. Default is 0.
/// - rotation: The rotation angle of the text in degrees. Default is 0.
/// - shadowRadius: The blur radius of the shadow. Default is 0.
/// - shadowOffsetX: The horizontal offset of the shadow. Default is 0.
/// - shadowOffsetY: The vertical offset of the shadow. Default is 0.
/// - shadowColor: The color of the shadow. Default is nil.
@objc public init(color: UIColor, font: UIFont, thickness: CGFloat = 0, rotation: CGFloat = 0, shadowRadius: CGFloat = 0, shadowOffsetX: Int = 0, shadowOffsetY: Int = 0, shadowColor: UIColor? = nil) {
self.color = color
self.font = font
self.thickness = thickness
self.rotation = rotation
self.shadowRadius = shadowRadius
self.shadowOffset = CGSize(width: shadowOffsetX, height: shadowOffsetY)
self.shadowColor = shadowColor
}

/// Convenience initializer with only color and font.
///
/// - Parameters:
/// - color: The color of the text.
/// - font: The font of the text.
@objc public convenience init(color: UIColor, font: UIFont) {
self.init(color: color, font: font)
}

/// Convenience initializer with color, font, and thickness.
///
/// - Parameters:
/// - color: The color of the text.
/// - font: The font of the text.
/// - thickness: The thickness of the text.
@objc public convenience init(color: UIColor, font: UIFont, thickness: CGFloat) {
self.init(color: color, font: font, thickness: thickness)
}

/// Convenience initializer with color, font, thickness, and rotation.
///
/// - Parameters:
/// - color: The color of the text.
/// - font: The font of the text.
/// - thickness: The thickness of the text.
/// - rotation: The rotation angle of the text in degrees.
@objc public convenience init(color: UIColor, font: UIFont, thickness: CGFloat, rotation: CGFloat) {
self.init(color: color, font: font, thickness: thickness, rotation: rotation)
}

/// Convenience initializer with color, size, and other optional properties.
///
/// - Parameters:
/// - color: The color of the text.
/// - size: The size of the text's font.
/// - thickness: The thickness of the text. Default is 0.
/// - rotation: The rotation angle of the text in degrees. Default is 0.
/// - shadowRadius: The blur radius of the shadow. Default is 0.
/// - shadowOffsetX: The horizontal offset of the shadow. Default is 0.
/// - shadowOffsetY: The vertical offset of the shadow. Default is 0.
/// - shadowColor: The color of the shadow. Default is nil.
@objc public convenience init(color: UIColor, size: CGFloat, thickness: CGFloat = 0, rotation: CGFloat = 0, shadowRadius: CGFloat = 0, shadowOffsetX: Int = 0, shadowOffsetY: Int = 0, shadowColor: UIColor? = nil) {
self.init(color: color, font: UIFont.systemFont(ofSize: size), thickness: thickness, rotation: rotation, shadowRadius: shadowRadius, shadowOffsetX: shadowOffsetX, shadowOffsetY: shadowOffsetY, shadowColor: shadowColor)
}

/// Convenience initializer with only color and size.
///
/// - Parameters:
/// - color: The color of the text.
/// - size: The size of the text's font.
@objc public convenience init(color: UIColor, size: CGFloat) {
self.init(color: color, size: size)
}

/// Convenience initializer with color, size, and thickness.
///
/// - Parameters:
/// - color: The color of the text.
/// - size: The size of the text's font.
/// - thickness: The thickness of the text.
@objc public convenience init(color: UIColor, size: CGFloat, thickness: CGFloat) {
self.init(color: color, size: size, thickness: thickness)
}

/// Convenience initializer with color, size, thickness, and rotation.
///
/// - Parameters:
/// - color: The color of the text.
/// - size: The size of the text's font.
/// - thickness: The thickness of the text.
/// - rotation: The rotation angle of the text in degrees.
@objc public convenience init(color: UIColor, size: CGFloat, thickness: CGFloat, rotation: CGFloat) {
self.init(color: color, size: size, thickness: thickness, rotation: rotation)
}

public override var description: String {
return """
TextStyle:
color: \(color)
font: \(font)
thickness: \(thickness)
rotation: \(rotation)
shadowRadius: \(shadowRadius)
shadowOffset: \(shadowOffset)
shadowColor: \(String(describing: shadowColor))
"""
}
}
33 changes: 24 additions & 9 deletions WCPhotoManipulator/UIImage+PhotoManipulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,27 +45,29 @@ public extension UIImage {
}

// Text
@objc func drawText(_ text: String, position: CGPoint, color: UIColor, font: UIFont, thickness: CGFloat, rotation: CGFloat, scale: CGFloat) -> UIImage? {
@objc func drawText(_ text: String, position: CGPoint, style: TextStyle, scale: CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.size, false, scale)
if let context = UIGraphicsGetCurrentContext() {
context.translateBy(x: position.x, y: position.y)
context.rotate (by: -rotation * CGFloat.pi / 180.0) //45˚
context.rotate (by: -style.rotation * CGFloat.pi / 180.0) //45˚
context.translateBy(x: -position.x, y: -position.y)
}

var textStyles: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: color,
.font: style.font,
.foregroundColor: style.color,
]
if (thickness > 0) {
textStyles[.strokeColor] = color;
textStyles[.strokeWidth] = thickness;
if (style.thickness > 0) {
textStyles[.strokeColor] = style.color;
textStyles[.strokeWidth] = style.thickness;
}
if (style.shadowRadius > 0 && style.shadowColor != nil) {
textStyles[.shadow] = style.shadow;
}

(text as NSString).draw(at: position, withAttributes: textStyles)

let rotatedImageWithText = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext()


let opaque = !hasAlpha()
UIGraphicsBeginImageContextWithOptions(self.size, opaque, scale)
Expand All @@ -80,14 +82,27 @@ public extension UIImage {
return result
}

@objc func drawText(_ text: String, position: CGPoint, style: TextStyle) -> UIImage? {
return drawText(text, position: position, style: style, scale: scale)
}

@available(*, deprecated, message: "Use drawText(, position:, style:, scale:)")
@objc func drawText(_ text: String, position: CGPoint, color: UIColor, font: UIFont, thickness: CGFloat, rotation: CGFloat, scale: CGFloat) -> UIImage? {
let style = TextStyle(color: color, font: font, thickness: thickness, rotation: rotation)
return drawText(text, position: position, style: style, scale: scale)
}

@available(*, deprecated, message: "Use drawText(, position:, style:, scale:)")
@objc func drawText(_ text: String, position: CGPoint, color: UIColor, font: UIFont, thickness: CGFloat, rotation: CGFloat) -> UIImage? {
return drawText(text, position: position, color: color, font:font, thickness: thickness, rotation: rotation, scale: scale)
}

@available(*, deprecated, message: "Use drawText(, position:, style:, scale:)")
@objc func drawText(_ text: String, position: CGPoint, color: UIColor, size: CGFloat, thickness: CGFloat, rotation: CGFloat, scale: CGFloat) -> UIImage? {
return drawText(text, position: position, color: color, font: UIFont.systemFont(ofSize: size), thickness: thickness, rotation: rotation, scale: scale)
}

@available(*, deprecated, message: "Use drawText(, position:, style:, scale:)")
@objc func drawText(_ text: String, position: CGPoint, color: UIColor, size: CGFloat, thickness: CGFloat, rotation: CGFloat) -> UIImage? {
return drawText(text, position: position, color: color, size: size, thickness: thickness, rotation: rotation, scale: scale)
}
Expand Down
13 changes: 8 additions & 5 deletions WCPhotoManipulatorTests/UIImage+PhotoManipulatorObjCTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ - (void)testCropAndResize_ShouldReturnSizeCorrectly {
- (void)testDrawText_WhenNoScale_ShouldReturnCorrectly {
image = [UIImage imageNamedTest:@"background.jpg"];
XCTAssertNotNil(image);

image = [image drawText:@"Test Text To Draw" position:CGPointMake(15, 66) color:UIColor.blueColor size:42 thickness:5 rotation:0];

TextStyle *style = [[TextStyle alloc] initWithColor:UIColor.blueColor size:42 thickness:5 rotation:0];
image = [image drawText:@"Test Text To Draw" position:CGPointMake(15, 66) style:style];
XCTAssertNotNil(image);
XCTAssertEqual(image.size.width, 800);
XCTAssertEqual(image.size.height, 530);
Expand All @@ -87,8 +88,9 @@ - (void)testDrawText_WhenNoScale_ShouldReturnCorrectly {
- (void)testDrawText_WhenHasScale_ShouldReturnCorrectly {
image = [UIImage imageNamedTest:@"background.jpg"];
XCTAssertNotNil(image);

image = [image drawText:@"Test Text To Draw" position:CGPointMake(15, 66) color:UIColor.greenColor size:42 thickness:0 rotation:0 scale:2];

TextStyle *style = [[TextStyle alloc] initWithColor:UIColor.greenColor size:42 thickness:0 rotation:0];
image = [image drawText:@"Test Text To Draw" position:CGPointMake(15, 66) style:style scale:2];
XCTAssertNotNil(image);
XCTAssertEqual(image.size.width, 800);
XCTAssertEqual(image.size.height, 530);
Expand All @@ -98,7 +100,8 @@ - (void)testDrawText_WhenHasRotation_ShouldReturnCorrectly {
image = [UIImage imageNamedTest:@"background.jpg"];
XCTAssertNotNil(image);

image = [image drawText:@"Test Rotation Text To Draw" position:CGPointMake(15, 66) color:UIColor.greenColor size:42 thickness:0 rotation:20 scale:2];
TextStyle *style = [[TextStyle alloc] initWithColor:UIColor.greenColor size:42 thickness:0 rotation:20];
image = [image drawText:@"Test Rotation Text To Draw" position:CGPointMake(15, 66) style:style scale:2];
XCTAssertNotNil(image);
XCTAssertEqual(image.size.width, 800);
XCTAssertEqual(image.size.height, 530);
Expand Down
19 changes: 16 additions & 3 deletions WCPhotoManipulatorTests/UIImage+PhotoManipulatorSwiftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,22 @@ class UIImage_PhotoManipulatorSwiftTests: XCTestCase {
}

// Draw Text
func testDrawText_WhenUseShadow_ShouldReturnCorrectly() throws {
image = UIImage.init(namedTest: "background.jpg")
XCTAssertNotNil(image)

let style = TextStyle(color: .blue, font: UIFont.systemFont(ofSize: 102), rotation: 14, shadowRadius: 3, shadowOffsetX: 4, shadowOffsetY: 10, shadowColor: UIColor.green)
image = image.drawText("Draw Shadow", position: CGPoint(x: 15, y: 66), style: style)
XCTAssertNotNil(image)
XCTAssertEqual(image.size, CGSize(width: 800, height: 530))
}

func testDrawText_WhenUseFontAndNoScale_ShouldReturnCorrectly() throws {
image = UIImage.init(namedTest: "background.jpg")
XCTAssertNotNil(image)

image = image.drawText("Test Text To Draw", position: CGPoint(x: 15, y: 66), color: .blue, font: UIFont.systemFont(ofSize: 102), thickness: 5, rotation: 0)
let style = TextStyle(color: .blue, font: UIFont.systemFont(ofSize: 102), thickness: 5, rotation: 0)
image = image.drawText("Test Text To Draw", position: CGPoint(x: 15, y: 66), style: style)
XCTAssertNotNil(image)
XCTAssertEqual(image.size, CGSize(width: 800, height: 530))
}
Expand All @@ -89,7 +100,8 @@ class UIImage_PhotoManipulatorSwiftTests: XCTestCase {
image = UIImage.init(namedTest: "background.jpg")
XCTAssertNotNil(image)

image = image.drawText("Test Text To Draw", position: CGPoint(x: 15, y: 66), color: .blue, size: 42, thickness: 5, rotation: 0)
let style = TextStyle(color: .blue, font: UIFont.systemFont(ofSize: 42), thickness: 5, rotation: 0)
image = image.drawText("Test Text To Draw", position: CGPoint(x: 15, y: 66), style: style)
XCTAssertNotNil(image)
XCTAssertEqual(image.size, CGSize(width: 800, height: 530))
}
Expand All @@ -98,7 +110,8 @@ class UIImage_PhotoManipulatorSwiftTests: XCTestCase {
image = UIImage.init(namedTest: "background.jpg")
XCTAssertNotNil(image)

image = image.drawText("Test Text To Draw", position: CGPoint(x: 15, y: 66), color: .blue, size: 42, thickness: 5, rotation: 0, scale: 2)
let style = TextStyle(color: .blue, font: UIFont.systemFont(ofSize: 42), thickness: 5, rotation: 0)
image = image.drawText("Test Text To Draw", position: CGPoint(x: 15, y: 66), style: style, scale: 2)
XCTAssertNotNil(image)
XCTAssertEqual(image.size, CGSize(width: 800, height: 530))
}
Expand Down

0 comments on commit 8c0cd6a

Please sign in to comment.