Files
wehub-resource-sync e904b667c6
Build/Publish Develop Docs / deploy (push) Failing after 1s
PaddleOCR Code Style Check / check-code-style (push) Failing after 1s
PaddleOCR PR Tests GPU / detect-changes (push) Failing after 1s
PaddleOCR PR Tests / detect-changes (push) Failing after 1s
PaddleOCR PR Tests GPU / test-pr-gpu (push) Has been cancelled
PaddleOCR PR Tests / test-pr (push) Has been cancelled
PaddleOCR PR Tests GPU / test-pr-gpu-impl (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.13) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.8) (push) Has been cancelled
PaddleOCR PR Tests / test-pr-python (3.9) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 11:59:26 +08:00

153 lines
5.0 KiB
Swift

// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import CoreGraphics
import Foundation
// MARK: - BGR ↔ CGImage
/// Builds a ``CGImage`` from row-major BGR uint8 (8-bit three-channel).
enum BGRImageCodec {
static func makeCGImageFromBGR(pixels: [UInt8], width: Int, height: Int) throws -> CGImage {
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
let pixelCount = width * height
guard pixels.count == pixelCount * 3 else {
throw QuadTextCropError.imageCreationFailed
}
var rgbaData = [UInt8](repeating: 255, count: pixelCount * bytesPerPixel)
for i in 0..<pixelCount {
let b = pixels[i * 3]
let g = pixels[i * 3 + 1]
let r = pixels[i * 3 + 2]
rgbaData[i * 4] = r
rgbaData[i * 4 + 1] = g
rgbaData[i * 4 + 2] = b
}
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
let context = CGContext(
data: &rgbaData,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
),
let cgImage = context.makeImage() else {
throw QuadTextCropError.imageCreationFailed
}
return cgImage
}
}
// MARK: - Errors
enum QuadTextCropError: LocalizedError {
case invalidPolygon(String)
case imageCreationFailed
case openCVFailed
var errorDescription: String? {
switch self {
case .invalidPolygon(let detail):
return "Invalid polygon for quad text crop: \(detail)"
case .imageCreationFailed:
return "Failed to create output CGImage"
case .openCVFailed:
return "OpenCV quad text-line crop failed"
}
}
}
// MARK: - QuadTextCrop
/// Minimum-area quadrilateral, corner ordering, perspective warp, then optional 90° CCW rotation when height/width ≥ 1.5.
struct QuadTextCrop {
/// Crop a text region from the source image using the detector quadrilateral.
static func crop(_ image: CGImage, polygon: [[Int32]]) throws -> CGImage {
guard polygon.count == 4, polygon.allSatisfy({ $0.count == 2 }) else {
throw QuadTextCropError.invalidPolygon(
"Expected 4 points with 2 coordinates each, got \(polygon.count) points"
)
}
let srcPixels = try extractBGRPixels(from: image)
let srcW = image.width
let srcH = image.height
let polyValues: [NSValue] = polygon.map {
NSValue(cgPoint: CGPoint(x: CGFloat($0[0]), y: CGFloat($0[1])))
}
guard let out = PDBOpenCVImageBridge.quadTextLineCropBGR(
Data(srcPixels),
srcWidth: srcW,
srcHeight: srcH,
quad: polyValues
) else {
throw QuadTextCropError.openCVFailed
}
let bytes = [UInt8](out.rgbData)
return try BGRImageCodec.makeCGImageFromBGR(pixels: bytes, width: Int(out.width), height: Int(out.height))
}
}
// MARK: - Pixel Extraction & Image Creation
private extension QuadTextCrop {
/// Row-major HWC **BGR**
static func extractBGRPixels(from image: CGImage) throws -> [UInt8] {
let width = image.width
let height = image.height
let bytesPerPixel = 4
let bytesPerRow = width * bytesPerPixel
var rgbaData = [UInt8](repeating: 0, count: height * bytesPerRow)
guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
let context = CGContext(
data: &rgbaData,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: bytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
) else {
throw QuadTextCropError.imageCreationFailed
}
context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
let pixelCount = width * height
var bgrData = [UInt8](repeating: 0, count: pixelCount * 3)
for i in 0..<pixelCount {
let r = rgbaData[i * 4]
let g = rgbaData[i * 4 + 1]
let b = rgbaData[i * 4 + 2]
bgrData[i * 3] = b
bgrData[i * 3 + 1] = g
bgrData[i * 3 + 2] = r
}
return bgrData
}
}