@bricks-soft/capacitor-thermal-printer 0.0.0-stage → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BricksSoftCapacitorThermalPrinter.podspec +17 -0
- package/LICENSE +201 -0
- package/Package.swift +13 -0
- package/README.md +80 -2
- package/android/build.gradle +40 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/brickssoft/thermalprinter/ThermalPrinterPlugin.java +463 -0
- package/dist/esm/definitions.d.ts +73 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/web.d.ts +13 -0
- package/dist/esm/web.js +33 -0
- package/dist/plugin.cjs.js +65 -0
- package/dist/plugin.js +68 -0
- package/ios/Sources/ThermalPrinter/EscPosBarcode.swift +121 -0
- package/ios/Sources/ThermalPrinter/EscPosBitmap.swift +178 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetooth.swift +341 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetoothConnection.swift +298 -0
- package/ios/Sources/ThermalPrinter/EscPosBluetoothRules.swift +78 -0
- package/ios/Sources/ThermalPrinter/EscPosCharsetEncoding.swift +115 -0
- package/ios/Sources/ThermalPrinter/EscPosCoreBluetoothLink.swift +287 -0
- package/ios/Sources/ThermalPrinter/EscPosDeviceConnection.swift +47 -0
- package/ios/Sources/ThermalPrinter/EscPosJavaCompat.swift +213 -0
- package/ios/Sources/ThermalPrinter/EscPosKnownPrinterStore.swift +44 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinter.swift +55 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinterCommands.swift +367 -0
- package/ios/Sources/ThermalPrinter/EscPosPrinterSize.swift +53 -0
- package/ios/Sources/ThermalPrinter/EscPosQRCode.swift +70 -0
- package/ios/Sources/ThermalPrinter/EscPosQueue.swift +30 -0
- package/ios/Sources/ThermalPrinter/EscPosTcpConnection.swift +265 -0
- package/ios/Sources/ThermalPrinter/PrinterTextParser.swift +622 -0
- package/ios/Sources/ThermalPrinter/ThermalPrinterPlugin.swift +85 -0
- package/ios/Sources/ThermalPrinter/ThermalPrinterService.swift +380 -0
- package/package.json +61 -4
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
var capacitorThermalPrinter = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const native = core.registerPlugin('ThermalPrinter', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.ThermalPrinterWeb()),
|
|
6
|
+
});
|
|
7
|
+
const unwrap = (request) => request.then(result => result.value).catch(error => {
|
|
8
|
+
if (error && typeof error === 'object' && error.data && typeof error.data === 'object') {
|
|
9
|
+
Object.assign(error, error.data);
|
|
10
|
+
}
|
|
11
|
+
throw error;
|
|
12
|
+
});
|
|
13
|
+
const ThermalPrinter = {
|
|
14
|
+
listPrinters: data => unwrap(native.listPrinters(data)),
|
|
15
|
+
printFormattedText: data => unwrap(native.printFormattedText(data)),
|
|
16
|
+
printFormattedTextAndCut: data => unwrap(native.printFormattedTextAndCut(data)),
|
|
17
|
+
getEncoding: data => unwrap(native.getEncoding(data)),
|
|
18
|
+
disconnectPrinter: data => unwrap(native.disconnectPrinter(data)),
|
|
19
|
+
requestPermissions: data => unwrap(native.requestPermissions(data)),
|
|
20
|
+
requestBTPermissions: data => unwrap(native.requestBTPermissions(data)),
|
|
21
|
+
requestUSBPermissions: data => unwrap(native.requestUSBPermissions(data)),
|
|
22
|
+
bitmapToHexadecimalString: data => unwrap(native.bitmapToHexadecimalString(data)),
|
|
23
|
+
bytesToHexadecimalString: data => unwrap(native.bytesToHexadecimalString(data)),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
class ThermalPrinterWeb extends core.WebPlugin {
|
|
27
|
+
async listPrinters() {
|
|
28
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
29
|
+
}
|
|
30
|
+
async printFormattedText() {
|
|
31
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
32
|
+
}
|
|
33
|
+
async printFormattedTextAndCut() {
|
|
34
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
35
|
+
}
|
|
36
|
+
async getEncoding() {
|
|
37
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
38
|
+
}
|
|
39
|
+
async disconnectPrinter() {
|
|
40
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
41
|
+
}
|
|
42
|
+
async requestPermissions() {
|
|
43
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
44
|
+
}
|
|
45
|
+
async requestBTPermissions() {
|
|
46
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
47
|
+
}
|
|
48
|
+
async requestUSBPermissions() {
|
|
49
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
50
|
+
}
|
|
51
|
+
async bitmapToHexadecimalString() {
|
|
52
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
53
|
+
}
|
|
54
|
+
async bytesToHexadecimalString() {
|
|
55
|
+
throw this.unavailable('Thermal printing is unavailable on the web.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
60
|
+
__proto__: null,
|
|
61
|
+
ThermalPrinterWeb: ThermalPrinterWeb
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
exports.ThermalPrinter = ThermalPrinter;
|
|
65
|
+
|
|
66
|
+
return exports;
|
|
67
|
+
|
|
68
|
+
})({}, capacitorExports);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
class EscPosBarcode {
|
|
4
|
+
let barcodeType: Int
|
|
5
|
+
var code: JavaString
|
|
6
|
+
let height: Int
|
|
7
|
+
let textPosition: Int
|
|
8
|
+
private(set) var colWidth = 0
|
|
9
|
+
|
|
10
|
+
var codeLength: Int { code.length }
|
|
11
|
+
var colsCount: Int { (codeLength + 5) * 11 }
|
|
12
|
+
|
|
13
|
+
init(printerSize: EscPosPrinterSize, barcodeType: Int, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int) throws {
|
|
14
|
+
self.barcodeType = barcodeType
|
|
15
|
+
self.code = code
|
|
16
|
+
self.height = printerSize.mmToPx(heightMM)
|
|
17
|
+
self.textPosition = textPosition
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func computeColWidth(printerSize: EscPosPrinterSize, widthMM requestedWidthMM: Float) throws {
|
|
21
|
+
var widthMM = requestedWidthMM
|
|
22
|
+
if widthMM == 0 {
|
|
23
|
+
widthMM = printerSize.printerWidthMM * 0.7
|
|
24
|
+
}
|
|
25
|
+
let wantedPxWidth = widthMM > printerSize.printerWidthMM ? printerSize.printerWidthPx : printerSize.mmToPx(widthMM)
|
|
26
|
+
var width = JavaMath.round(Double(wantedPxWidth) / Double(colsCount))
|
|
27
|
+
if width * colsCount > printerSize.printerWidthPx {
|
|
28
|
+
width -= 1
|
|
29
|
+
}
|
|
30
|
+
if width == 0 {
|
|
31
|
+
throw EscPosError.barcode("Barcode is too long for the paper size.")
|
|
32
|
+
}
|
|
33
|
+
colWidth = width
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static func make(type: String, printerSize: EscPosPrinterSize, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int) throws -> EscPosBarcode {
|
|
37
|
+
let barcode: EscPosBarcode
|
|
38
|
+
switch type {
|
|
39
|
+
case "ean8":
|
|
40
|
+
barcode = try EscPosBarcodeNumber(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeEAN8, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition, fixedLength: 8)
|
|
41
|
+
case "ean13":
|
|
42
|
+
barcode = try EscPosBarcodeNumber(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeEAN13, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition, fixedLength: 13)
|
|
43
|
+
case "upca":
|
|
44
|
+
barcode = try EscPosBarcodeNumber(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeUPCA, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition, fixedLength: 12)
|
|
45
|
+
case "upce":
|
|
46
|
+
barcode = try EscPosBarcodeUPCE(printerSize: printerSize, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
|
|
47
|
+
case "128":
|
|
48
|
+
barcode = try EscPosBarcode(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeType128, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
|
|
49
|
+
try barcode.computeColWidth(printerSize: printerSize, widthMM: widthMM)
|
|
50
|
+
default:
|
|
51
|
+
throw EscPosError.parser("Invalid barcode attribute : type")
|
|
52
|
+
}
|
|
53
|
+
return barcode
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
final class EscPosBarcodeNumber: EscPosBarcode {
|
|
58
|
+
private let fixedLength: Int
|
|
59
|
+
|
|
60
|
+
override var codeLength: Int { fixedLength }
|
|
61
|
+
override var colsCount: Int { codeLength * 7 + 11 }
|
|
62
|
+
|
|
63
|
+
init(printerSize: EscPosPrinterSize, barcodeType: Int, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int, fixedLength: Int) throws {
|
|
64
|
+
self.fixedLength = fixedLength
|
|
65
|
+
try super.init(printerSize: printerSize, barcodeType: barcodeType, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
|
|
66
|
+
try computeColWidth(printerSize: printerSize, widthMM: widthMM)
|
|
67
|
+
try checkCode()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private func checkCode() throws {
|
|
71
|
+
let checkedLength = codeLength - 1
|
|
72
|
+
if code.length < checkedLength {
|
|
73
|
+
throw EscPosError.barcode("Code is too short for the barcode type.")
|
|
74
|
+
}
|
|
75
|
+
let digits = try code.substring(0, checkedLength)
|
|
76
|
+
var total = 0
|
|
77
|
+
for index in 0..<checkedLength {
|
|
78
|
+
let position = checkedLength - 1 - index
|
|
79
|
+
guard let digit = EscPosBarcodeNumber.digit(digits.units[position]) else {
|
|
80
|
+
throw EscPosError.barcode("Invalid barcode number")
|
|
81
|
+
}
|
|
82
|
+
total += index % 2 == 0 ? 3 * digit : digit
|
|
83
|
+
}
|
|
84
|
+
var key = String(10 - (total % 10))
|
|
85
|
+
if key.count == 2 {
|
|
86
|
+
key = "0"
|
|
87
|
+
}
|
|
88
|
+
code = JavaString(digits.string + key)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static func digit(_ unit: UInt16) -> Int? {
|
|
92
|
+
guard let scalar = Unicode.Scalar(unit),
|
|
93
|
+
scalar.properties.generalCategory == .decimalNumber,
|
|
94
|
+
let value = scalar.properties.numericValue
|
|
95
|
+
else {
|
|
96
|
+
return nil
|
|
97
|
+
}
|
|
98
|
+
return Int(value)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
final class EscPosBarcodeUPCE: EscPosBarcode {
|
|
103
|
+
override var codeLength: Int { 6 }
|
|
104
|
+
override var colsCount: Int { codeLength * 7 + 16 }
|
|
105
|
+
|
|
106
|
+
init(printerSize: EscPosPrinterSize, code: JavaString, widthMM: Float, heightMM: Float, textPosition: Int) throws {
|
|
107
|
+
try super.init(printerSize: printerSize, barcodeType: EscPosPrinterCommands.barcodeTypeUPCE, code: code, widthMM: widthMM, heightMM: heightMM, textPosition: textPosition)
|
|
108
|
+
try computeColWidth(printerSize: printerSize, widthMM: widthMM)
|
|
109
|
+
try checkCode()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private func checkCode() throws {
|
|
113
|
+
if code.length < codeLength {
|
|
114
|
+
throw EscPosError.barcode("Code is too short for the barcode type.")
|
|
115
|
+
}
|
|
116
|
+
code = try code.substring(0, codeLength)
|
|
117
|
+
for unit in code.units where EscPosBarcodeNumber.digit(unit) == nil {
|
|
118
|
+
throw EscPosError.barcode("Invalid barcode number")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import CoreGraphics
|
|
2
|
+
import Foundation
|
|
3
|
+
import ImageIO
|
|
4
|
+
|
|
5
|
+
struct EscPosBitmap {
|
|
6
|
+
let width: Int
|
|
7
|
+
let height: Int
|
|
8
|
+
let pixels: [UInt32]
|
|
9
|
+
|
|
10
|
+
init(width: Int, height: Int, pixels: [UInt32]) {
|
|
11
|
+
self.width = width
|
|
12
|
+
self.height = height
|
|
13
|
+
self.pixels = pixels
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
func pixel(_ x: Int, _ y: Int) -> UInt32 {
|
|
17
|
+
pixels[y * width + x]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static func decode(_ data: Data) -> EscPosBitmap? {
|
|
21
|
+
guard let source = CGImageSourceCreateWithData(data as CFData, nil),
|
|
22
|
+
CGImageSourceGetCount(source) > 0,
|
|
23
|
+
let image = CGImageSourceCreateImageAtIndex(source, 0, [kCGImageSourceShouldCache: false] as CFDictionary)
|
|
24
|
+
else {
|
|
25
|
+
return nil
|
|
26
|
+
}
|
|
27
|
+
return EscPosBitmap(image: image)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
init?(image: CGImage) {
|
|
31
|
+
let width = image.width
|
|
32
|
+
let height = image.height
|
|
33
|
+
guard width > 0, height > 0 else { return nil }
|
|
34
|
+
let isGray = image.colorSpace?.model == .monochrome
|
|
35
|
+
let hasAlpha: Bool
|
|
36
|
+
switch image.alphaInfo {
|
|
37
|
+
case .none, .noneSkipFirst, .noneSkipLast:
|
|
38
|
+
hasAlpha = false
|
|
39
|
+
default:
|
|
40
|
+
hasAlpha = true
|
|
41
|
+
}
|
|
42
|
+
if isGray && !hasAlpha, let gray = EscPosBitmap.renderGray(image) {
|
|
43
|
+
self.init(width: width, height: height, pixels: gray.map { value in
|
|
44
|
+
let level = UInt32(value)
|
|
45
|
+
return 0xFF00_0000 | level << 16 | level << 8 | level
|
|
46
|
+
})
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
guard let rgba = EscPosBitmap.renderRGBA(image, width: width, height: height) else { return nil }
|
|
50
|
+
var pixels = [UInt32](repeating: 0, count: width * height)
|
|
51
|
+
for index in 0..<(width * height) {
|
|
52
|
+
let alpha = UInt32(rgba[index * 4 + 3])
|
|
53
|
+
var red = UInt32(rgba[index * 4])
|
|
54
|
+
var green = UInt32(rgba[index * 4 + 1])
|
|
55
|
+
var blue = UInt32(rgba[index * 4 + 2])
|
|
56
|
+
if alpha == 0 {
|
|
57
|
+
red = 0
|
|
58
|
+
green = 0
|
|
59
|
+
blue = 0
|
|
60
|
+
} else if alpha < 255 {
|
|
61
|
+
red = min(255, (red * 255 + alpha / 2) / alpha)
|
|
62
|
+
green = min(255, (green * 255 + alpha / 2) / alpha)
|
|
63
|
+
blue = min(255, (blue * 255 + alpha / 2) / alpha)
|
|
64
|
+
}
|
|
65
|
+
pixels[index] = alpha << 24 | red << 16 | green << 8 | blue
|
|
66
|
+
}
|
|
67
|
+
self.init(width: width, height: height, pixels: pixels)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func scaled(width newWidth: Int, height newHeight: Int) -> EscPosBitmap {
|
|
71
|
+
guard newWidth > 0, newHeight > 0, let image = cgImage() else {
|
|
72
|
+
return EscPosBitmap(width: max(newWidth, 0), height: max(newHeight, 0), pixels: [UInt32](repeating: 0, count: max(newWidth, 0) * max(newHeight, 0)))
|
|
73
|
+
}
|
|
74
|
+
let bytesPerRow = newWidth * 4
|
|
75
|
+
var buffer = [UInt8](repeating: 0, count: bytesPerRow * newHeight)
|
|
76
|
+
let drawn: Bool = buffer.withUnsafeMutableBytes { raw in
|
|
77
|
+
guard let context = CGContext(
|
|
78
|
+
data: raw.baseAddress,
|
|
79
|
+
width: newWidth,
|
|
80
|
+
height: newHeight,
|
|
81
|
+
bitsPerComponent: 8,
|
|
82
|
+
bytesPerRow: bytesPerRow,
|
|
83
|
+
space: CGColorSpaceCreateDeviceRGB(),
|
|
84
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
|
|
85
|
+
) else { return false }
|
|
86
|
+
context.interpolationQuality = .medium
|
|
87
|
+
context.setBlendMode(.copy)
|
|
88
|
+
context.draw(image, in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
guard drawn, let scaledImage = EscPosBitmap.makeImage(rgba: buffer, width: newWidth, height: newHeight),
|
|
92
|
+
let result = EscPosBitmap(image: scaledImage)
|
|
93
|
+
else {
|
|
94
|
+
return EscPosBitmap(width: newWidth, height: newHeight, pixels: [UInt32](repeating: 0, count: newWidth * newHeight))
|
|
95
|
+
}
|
|
96
|
+
return result
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func cgImage() -> CGImage? {
|
|
100
|
+
var rgba = [UInt8](repeating: 0, count: width * height * 4)
|
|
101
|
+
for index in 0..<(width * height) {
|
|
102
|
+
let pixel = pixels[index]
|
|
103
|
+
let alpha = (pixel >> 24) & 0xFF
|
|
104
|
+
rgba[index * 4] = UInt8((((pixel >> 16) & 0xFF) * alpha + 127) / 255)
|
|
105
|
+
rgba[index * 4 + 1] = UInt8((((pixel >> 8) & 0xFF) * alpha + 127) / 255)
|
|
106
|
+
rgba[index * 4 + 2] = UInt8(((pixel & 0xFF) * alpha + 127) / 255)
|
|
107
|
+
rgba[index * 4 + 3] = UInt8(alpha)
|
|
108
|
+
}
|
|
109
|
+
return EscPosBitmap.makeImage(rgba: rgba, width: width, height: height)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private static func makeImage(rgba: [UInt8], width: Int, height: Int) -> CGImage? {
|
|
113
|
+
guard let provider = CGDataProvider(data: Data(rgba) as CFData) else { return nil }
|
|
114
|
+
return CGImage(
|
|
115
|
+
width: width,
|
|
116
|
+
height: height,
|
|
117
|
+
bitsPerComponent: 8,
|
|
118
|
+
bitsPerPixel: 32,
|
|
119
|
+
bytesPerRow: width * 4,
|
|
120
|
+
space: CGColorSpaceCreateDeviceRGB(),
|
|
121
|
+
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue),
|
|
122
|
+
provider: provider,
|
|
123
|
+
decode: nil,
|
|
124
|
+
shouldInterpolate: false,
|
|
125
|
+
intent: .defaultIntent
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static func renderRGBA(_ image: CGImage, width: Int, height: Int) -> [UInt8]? {
|
|
130
|
+
let space: CGColorSpace
|
|
131
|
+
if let imageSpace = image.colorSpace, imageSpace.model == .rgb {
|
|
132
|
+
space = imageSpace
|
|
133
|
+
} else {
|
|
134
|
+
space = CGColorSpace(name: CGColorSpace.sRGB) ?? CGColorSpaceCreateDeviceRGB()
|
|
135
|
+
}
|
|
136
|
+
let bytesPerRow = width * 4
|
|
137
|
+
var buffer = [UInt8](repeating: 0, count: bytesPerRow * height)
|
|
138
|
+
let drawn: Bool = buffer.withUnsafeMutableBytes { raw in
|
|
139
|
+
guard let context = CGContext(
|
|
140
|
+
data: raw.baseAddress,
|
|
141
|
+
width: width,
|
|
142
|
+
height: height,
|
|
143
|
+
bitsPerComponent: 8,
|
|
144
|
+
bytesPerRow: bytesPerRow,
|
|
145
|
+
space: space,
|
|
146
|
+
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
|
|
147
|
+
) else { return false }
|
|
148
|
+
context.interpolationQuality = .none
|
|
149
|
+
context.setBlendMode(.copy)
|
|
150
|
+
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
151
|
+
return true
|
|
152
|
+
}
|
|
153
|
+
return drawn ? buffer : nil
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private static func renderGray(_ image: CGImage) -> [UInt8]? {
|
|
157
|
+
guard let space = image.colorSpace else { return nil }
|
|
158
|
+
let width = image.width
|
|
159
|
+
let height = image.height
|
|
160
|
+
var buffer = [UInt8](repeating: 0, count: width * height)
|
|
161
|
+
let drawn: Bool = buffer.withUnsafeMutableBytes { raw in
|
|
162
|
+
guard let context = CGContext(
|
|
163
|
+
data: raw.baseAddress,
|
|
164
|
+
width: width,
|
|
165
|
+
height: height,
|
|
166
|
+
bitsPerComponent: 8,
|
|
167
|
+
bytesPerRow: width,
|
|
168
|
+
space: space,
|
|
169
|
+
bitmapInfo: CGImageAlphaInfo.none.rawValue
|
|
170
|
+
) else { return false }
|
|
171
|
+
context.interpolationQuality = .none
|
|
172
|
+
context.setBlendMode(.copy)
|
|
173
|
+
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
return drawn ? buffer : nil
|
|
177
|
+
}
|
|
178
|
+
}
|