【问题标题】:UIImage Upside Down When Reconverted From String Stored in SQLite从存储在 SQLite 中的字符串重新转换时 UIImage 颠倒
【发布时间】:2021-01-14 20:28:45
【问题描述】:

我从模拟器照片库中保存了一张图片。

然后我将图像保存为字符串,当用户单击保存时,它会从字符串重新转换为图像并放置在另一个控制器的表格视图中。出于某种原因,只有某些库存照片被颠倒了。

图片转字符串代码:

extension UIImage
{
    func toString() -> String?
    {
        let data: Data? = self.pngData()
        return data?.base64EncodedString(options: .lineLength64Characters)
    }
}

将字符串重新转换回图像的代码:

extension String
{
    func toImage() -> UIImage?
    {
        if let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters)
        {
            return UIImage(data: data)
        }
    
        return nil
   }
}

转换是基于另一个 StackOverflow 页面的推导:Convert between UIImage and Base64 string

整个“家庭”的数据库存储代码:

    /**
INSERT operation prepared statement for family

- Parameters
    - family: Contains the data for each child and parents' name and image
*/
func insertFamily(family: Family)
{
    var insertStatement: OpaquePointer? = nil
    let result = sqlite3_prepare_v2(self.db, self.insertFamilyQuery, -1, &insertStatement, nil)
    
    if result == SQLITE_OK
    {
        // Bind values to insert statement
        sqlite3_bind_text(insertStatement, 1, (family.childName! as NSString).utf8String, -1, nil)
        sqlite3_bind_text(insertStatement, 2, (family.childImage! as NSString).utf8String, -1, nil)
        sqlite3_bind_text(insertStatement, 3, (family.parentOneName! as NSString).utf8String, -1, nil)
        sqlite3_bind_text(insertStatement, 4, (family.parentOneImage! as NSString).utf8String, -1, nil)
        sqlite3_bind_text(insertStatement, 5, (family.parentTwoName! as NSString).utf8String, -1, nil)
        sqlite3_bind_text(insertStatement, 6, (family.parentTwoImage! as NSString).utf8String, -1, nil)
        
        // Execute statement
        let result = sqlite3_step(insertStatement)
        sqlite3_finalize(insertStatement)   // Destroy statement to avoid memory leak
        
        if result == SQLITE_DONE
        {
            os_log("Inserted family for %s", log: self.oslog, type: .info, family.childName!)
        }
        else
        {
            os_log("Could not insert family", log: self.oslog, type: .error)
            os_log("Expected: %s Received: %s", log: self.oslog, type: .error, SQLITE_DONE, result)
        }
    }
    else 
    {
        os_log("INSERT statement could not be prepared", log: self.oslog, type: .error)
        os_log("Expected: %s Received: %s", log: self.oslog, type: .error, SQLITE_DONE, result)
    }
}

整个“家庭”的数据库读取代码:

    /**
Pull the family for the given childName if it exists

- Parameters
    - childName: The child's name associated with the family

- Returns: The family found
*/
func pullFamily(childName: String) -> Family?
{
    var family = Family()
    var readStatement: OpaquePointer? = nil
    let selectStatement = self.pullFamilyQuery + childName + "'"
    
    // Read
    if sqlite3_prepare_v2(self.db, selectStatement, -1, &readStatement, nil) == SQLITE_OK
    {
        if sqlite3_step(readStatement) == SQLITE_ROW
        {
            let childName = String(describing: String(cString: sqlite3_column_text(readStatement, 0)))
            let childImage = String(describing: String(cString: sqlite3_column_text(readStatement, 1)))
            let parentOneName = String(describing: String(cString: sqlite3_column_text(readStatement, 2)))
            let parentOneImage = String(describing: String(cString: sqlite3_column_text(readStatement, 3)))
            let parentTwoName = String(describing: String(cString: sqlite3_column_text(readStatement, 4)))
            let parentTwoImage = String(describing: String(cString: sqlite3_column_text(readStatement, 5)))
            
            family = Family(childName: childName, 
                                childImage: childImage, 
                                parentOneName: parentOneName, 
                                parentOneImage: parentOneImage, 
                                parentTwoName: parentTwoName, 
                                parentTwoImage: parentTwoImage)
        }
    }
    else 
    {
        os_log("Could not pull family by child name ", log: self.oslog, type: .error, childName)
    }
    
    sqlite3_finalize(readStatement) // Destroy statement to avoid memory leak
    return family
}

家庭初始化器

init(childName: String?, childImage: UIImage?,
     parentOneName: String?, parentOneImage: UIImage?,
     parentTwoName: String?, parentTwoImage: UIImage?)
    {
        self.childName = childName
        self.childImage = childImage
        self.parentOneName = parentOneName
        self.parentOneImage = parentOneImage
        self.parentTwoName = parentTwoName
        self.parentTwoImage = parentTwoImage
    }

Blob 写入的测试代码

// Child Image
result = (family.childImage!.unrotatedPngData()?.withUnsafeBytes { bufferPointer -> Int32 in
        sqlite3_bind_blob(insertStatement, 1, bufferPointer.baseAddress, Int32(bufferPointer.count), SQLITE_TRANSIENT) })!

guard result == SQLITE_OK else {
    logger.error("[\(#function); \(#line)] ERROR: Could not bind child image")
    logger.error("[\(#function); \(#line)] ERROR: Expected - \(SQLITE_OK), Received - \(result)")
    logger.error("[\(#function); \(#line)] [ERROR: \(self.getDatabaseError())")
    return

Blob 读取测试代码

while sqlite3_step(readStatement) == SQLITE_ROW
{
    guard
        let childName = sqlite3_column_text(readStatement, 0).flatMap({ String(cString: $0) }),
        let childImageBlob = sqlite3_column_blob(readStatement, 1)
        //let parentOneName = sqlite3_column_text(readStatement, 2).flatMap({ String(cString: $0) }),
        //let parentOneImageBlob = sqlite3_column_blob(readStatement, 3),
        //let parentTwoName = sqlite3_column_text(readStatement, 4).flatMap({ String(cString: $0) }),
        //let parentTwoImageBlob = sqlite3_column_blob(readStatement, 5)
    else {
        logger.error("[\(#function); \(#line)] Could not pull family")
        logger.error("\(String(cString: sqlite3_errmsg(self.db)))")
        return families
    }
            
    // Convert child image data to child image
    let childData = Data(bytes: childImageBlob, count: Int(sqlite3_column_bytes(readStatement, 1)))
    guard let childImage = UIImage(data: childData) else {
        logger.error("Could not convert child image for child name \(childName)")
        return families
    }

    ...
}

【问题讨论】:

  • 我更愿意完全避免将数据转换为字符串...
  • 你为什么希望它首先转换为字符串?
  • 只是一个猜测,但 CoreImage 的“新手”可能会混淆的是原点((0,0) 的值)实际上是 lower 左侧,不是上层。你的行为看起来很相似。
  • @Chris 似乎是一种将其存储在数据库中的更简单方法
  • 顺便说一句,所有这些NSString 演员表都可以消除。例如。 sqlite3_bind_text(insertStatement, 1, (family.childName! as NSString).utf8String, -1, nil) 可以简化为 sqlite3_bind_text(insertStatement, 1, family.childName!, -1, nil)。我建议不要使用nil 作为最后一个参数,而是使用SQLITE_TRANSIENT 定义here

标签: swift string uiimage


【解决方案1】:

问题不在于字符串的 base-64 编码(也不在于数据库)。问题是pngData 不保留图像方向。如果我用手机在纵向模式下拍照,结果UIImageimageOrientationUIImage.Orientation.right,如果我在横向模式下拍摄,音量按钮在顶部,方向是.down,等等. 如果我直接使用那些图像,或者抓住他们的jpgData,他们就很好。但是,如果我使用 pngData 为这些图像提取 Data,则生成的资源将分别错误地旋转 90˚ 或 180˚。

所以你有几个选择:

  1. 您可以使用jpgData,它确实尊重图像的方向。虽然像 0.7 或 0.8 这样的压缩值在适当的压缩下看起来相当不错,但请注意这是一种有损格式。

  2. 您可以在提取pngData 之前重新渲染图像(必要时)。例如。这是一个 UIImage 扩展来检索未旋转的 PNG:

    extension UIImage {
        func unrotatedPngData() -> Data? {
            if imageOrientation == .up {
                return pngData()
            }
    
            let format = UIGraphicsImageRendererFormat()
            format.scale = scale
            return UIGraphicsImageRenderer(size: size, format: format).image { _ in
                draw(at: .zero)
            }.pngData()
        }
    }
    

应该注意,还有其他更隐蔽的方式可能会导致图像方向混乱,但pngData 可能是这里的罪魁祸首。如果你想测试这篇论文,试试jpgData,如果可行,确实是pngData的问题。但是,如果jpgData 也不起作用,那么问题就出现在您的管道中,但我们需要see that code 才能进一步诊断它。但聪明的钱在pngData。无论如何,base-64 编码和数据库都不是问题的根源。


话虽如此,我不建议将 base-64 编码图像存储在数据库中。这会更慢,并将已保存资产的大小增加 ⅓。我只是将图像保存为 BLOB:

var statement: OpaquePointer?

var result = sqlite3_prepare(db, "INSERT INTO images (filename, image) VALUES (?, ?)", -1, &statement, nil)

guard result == SQLITE_OK else {
    printError(in: db)
    return
}

defer { sqlite3_finalize(statement) }

result = sqlite3_bind_text(statement, 1, filename, -1, SQLITE_TRANSIENT)

guard result == SQLITE_OK else {
    printError(in: db)
    return
}

result = image.unrotatedPngData()?.withUnsafeBytes { bufferPointer -> Int32 in
    result = sqlite3_bind_blob(statement, 2, bufferPointer.baseAddress, Int32(bufferPointer.count), SQLITE_TRANSIENT)
}

guard result == SQLITE_OK else {
    printError(in: db)
    return
}

result = sqlite3_step(statement)

guard result == SQLITE_DONE else {
    printError(in: db)
    return
}

您也可以将图像作为 BLOB 获取:

var statement: OpaquePointer?

let result = sqlite3_prepare(db, "SELECT filename, image FROM images", -1, &statement, nil)
guard result == SQLITE_OK else { ... }
defer { sqlite3_finalize(statement) }

while sqlite3_step(statement) == SQLITE_ROW {
    guard
        let filename = sqlite3_column_text(statement, 0).flatMap({ String(cString: $0) }),
        let bytes = sqlite3_column_blob(statement, 1)
    else {
        printError(in: db)
        return
    }

    let data = Data(bytes: bytes, count: Int(sqlite3_column_bytes(statement, 1)))
    guard let image = UIImage(data: data) else { ... }

    // do whatever you want with the image
}

在哪里

func printError() {
    let message = sqlite3_errmsg(db).flatMap { String(cString: $0) } ?? "Unknown error"
    print(message)
}

【讨论】:

  • 我喜欢这个想法。最初试图存储为 blob,但直到现在才在网上找到任何东西。我更新了我的原始帖子以包含我目前用于存储和提取数据的完整功能。如何将您的方法合并到我现有的代码中?
  • 因此,对于 withUnsafeBytes 部分,我收到错误“表达式类型在没有更多上下文的情况下不明确”,这似乎是由于 withUnsafeBytes 已被弃用。我不确定我从其他帖子中找到的解决方案是否适用于解决该错误。
  • 不,withUnsafeBytes 有两种版本,旧的、已弃用的版本采用 UnsafePointer<T>,而新的版本采用 UnsafeRawBufferPointer。通常,如果您的闭包中有任何错误,Swift 会很难进行类型推断,并错误地假定您正在尝试使用已弃用的 UnsafePointer<T> 演绎版。您可以尝试在您的类型声明中显式,例如data.withUnsafeBytes { (bufferPointer: UnsafeRawBufferPointer) -> Int32 in ... }。或者在 gist.github.com 上发布你的代码,我很乐意看看。但是上面的语法是正确的。
  • 好的,所以问题是我是个白痴,忘记更正 Family 类并将最初是字符串的图像属性更改为图像。这解决了歧义错误。但是对于如何绑定多个图像仍然有些困惑。使用缓冲区指针的嵌套调用??
  • 不需要嵌套。为每个 blob 列单独调用 withUnsafeBytes。我已经调整了我的示例,只将 blob 的绑定包装在 withUnsafeBytes 中,这应该清楚地说明如何为每个图像重复此绑定。
猜你喜欢
  • 2016-01-17
  • 2019-09-22
  • 1970-01-01
  • 2016-11-20
  • 2013-08-16
  • 1970-01-01
  • 1970-01-01
  • 2016-07-03
  • 2012-10-09
相关资源
最近更新 更多