/MaLiang

iOS painting and drawing library based on Metal. 神笔马良有一支神笔(基于 Metal 的涂鸦绘图库)

Primary LanguageSwiftMIT LicenseMIT

Banner

CI Status Platform Version Carthage compatible Language codebeat badge License twitter weibo

icon MaLiang is a painting framework based on Metal. It supports drawing and handwriting with customized textures. The name of "MaLiang" comes from a boy who had a magical brush in Chinese ancient fairy story.

Simplified Chinese

☕️ If I have saved your time, buy me a cup of coffee

📱 App based on MaLiang is now avaliable on the App Store

Features

  • Lines with Bezier Curve
  • Texture Rotation
  • Glowing Lines
  • Chartlet element (for image and any other content from an UIView snapshot)
  • Force based Adjustment for stroke size
  •  Pencil supported
  • 3D Touch supported
  • Undo & Redo
  • Zoom & Scale
  • Export to image
  • Save vector contents to disk
  • support macOS Catalyst

Requirements

iOS 9.0, Swift 5

The core painting module is based on Metal

You can simply make it compatible with lower version of iOS and swift by changing only serval lines of code.

Installation

CocoaPods

MaLiang is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'MaLiang'

To use the old OpenGL ES verion:

pod 'MaLiang', '~> 1.1'

Carthage

To integrate MaLiang into your Xcode project using Carthage, specify it in your Cartfile:

github "Harley-xk/MaLiang"

Run carthage update to build the framework and drag the built MaLiang.framework into your Xcode project.

Make sure to add MaLiang.framework to your target's Embedded Binaries

Usage

MaLiang is simple to use.

  1. import MaLiang
  2. enjoy painting!

Canvas

open class Canvas: MetalView

A Canvas is the basic component of MaLiang. You will paint all things on it. Canvas extends from MetalView, whitch extends from MTKView. MetalView handles all the logic with MetalKit and hides them from you.

Canvas can be simply created with xib or code.

  • with xib or storyboard, simply drag and drop an UIView object into your view controller and change it's class to Canvas and module to MaLiang
  • with code, just create with init(frame:) as any UIView you do before.

Now, all things necessary is done!

Snapshot

You can take snapshot on canvas now. Just call snapshot function on Canvas and you will get an optional UIImage object.

Brush

With all things done, you can do more with Brush!

Brush is the key feature to MaLiang. It holds textures and colors, whitch makes it possiable to paint amazing things.

Register a Brush with image data or file to Canvas and paint with it:

let path = Bundle.main.path(forResource: "pencil", ofType: "png")!
let pencil = try? canvas.registerBrush(with: URL(fileURLWithPath: path))
pencil?.use()

Brush have serval properties for you to custmize:

// opacity of texture, affects the darkness of stroke
// set opacity to 1 may cause heavy aliasing
open var opacity: CGFloat = 0.3

// width of stroke line in points
open var pointSize: CGFloat = 4

// this property defines the minimum distance (measureed in points) of nearest two textures
// defaults to 1, this means erery texture calculated will be rendered, dictance calculation will be skiped
open var pointStep: CGFloat = 1

// sensitive of pointsize changed from force, from 0 - 1
open var forceSensitive: CGFloat = 0

/// color of stroke
open var color: UIColor = .black

// indicate if the stroke size in visual will be scaled along with the Canvas
// defaults to false, the stroke size in visual will stay with the original value
open var scaleWithCanvas = false

With all these properties, you can create you own brush as your imagination.

Force & 3D Touch

MaLiang supports automatically adjustment of stroke size with painting force. 3D Touch is supported by default, and simulated force will be setup on devices those are not supporting this.

forceSensitive is the property that force affects the storke size. It should be set between 0 to 1. the smaller the value is, the less sensitive will be. if sets to 0, then force will not affects the stroke size.

Chartlet

Chartlet elements are supported from 2.1.0. A chartlet must be registered to canvas with its' texture data. You can simply get image data from its' pngData() method.

let data = UIImage(named: "chartlet").pngData()
let texture = try canvas.makeTexture(with: data)

You can apply rotation to chartlet by passing a counter clockwise angle in radius when adding it to the canvas:

canvas.renderChartlet(at: location, size: chartletSize, textureID: texture.id, rotation: angle)

Text

Text element can be rendered to canvas by the Chartlet feature. MaLiang leaves the work of text layout and styles to your self.

  • Firtst, put your text content to a label, a text view or any other customized views
  • Second, make it properly styled and layouted
  • Then Third, take a snapshot from that view.
  • Finally, now you shoud have an image to your text content, render this image to canvas using the Chartlet apis.

Refer to the samples for more details.

CanvasData

CanvasData is now configured by default. It holds all the data on the Canvas, and makes the undo and redo actions to be possiable.
And you can implement your own saving logic with the data holds by CanvasData.

Saving

🎉 You can save your paintings to disk now.

// 1. create an instance of `DataExporter` with your canvas:
let exporter = DataExporter(canvas: canvas)
// 2. save to empty folders on disk:
exporter.save(to: localPath, progress: progressHandler, result: resultHandler)

// also you can use another synchronous method to do this work Synchronously
exporter.saveSynchronously(to: locakPath, progress: progressHandler)

Then, contents of canvas and some document infomations will be saved to files in the directory you provided.

MaLiang does not zip the folders, you can implement your own archive Logics refer to the sample project

Reading

Use DataImporter to read data saved by MaLiang to your canvas:

DataImporter.importData(from: localPath, to: canvas, progress: progressHandler, result: resultHandler)

Also, the localPath passed into DataImporter must be a folder where your contents files place. If you are using your own archive logic, unzip the contents first by your own.

License

MaLiang is available under the MIT license. See the LICENSE file for more info.