从存储在 SQLite 中的字符串重新转换时 UIImage 颠倒
UIImage Upside Down When Reconverted From String Stored in SQLite
我从模拟器照片库中保存了一张图片。
然后我将图像保存为字符串,当用户单击“保存”时,它会从字符串重新转换为图像并放置在另一个控制器的 table 视图中。出于某种原因,只有某些库存照片被倒置重新转换。
图片转字符串代码:
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
}
}
转换是基于另一个 Whosebug 页面的推导:Convert between UIImage and Base64 string
整个'family'的数据库存储代码:
/**
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)
}
}
整个'family'的数据库读取代码:
/**
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: [=16=]) }),
let childImageBlob = sqlite3_column_blob(readStatement, 1)
//let parentOneName = sqlite3_column_text(readStatement, 2).flatMap({ String(cString: [=16=]) }),
//let parentOneImageBlob = sqlite3_column_blob(readStatement, 3),
//let parentTwoName = sqlite3_column_text(readStatement, 4).flatMap({ String(cString: [=16=]) }),
//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
}
...
}
问题不在于字符串的 base-64 编码(也不在于数据库)。问题是 pngData
不保留图像方向。如果我用我的 phone 在纵向模式下拍照,结果 UIImage
的 imageOrientation
是 UIImage.Orientation.right
,如果我在横向模式下用音量按钮在top,方向是.down
,等等。如果我直接使用那些图像,或者抓取它们的jpgData
,它们就可以了。但是,如果我使用 pngData
为这些图像提取 Data
,生成的资产将分别错误地旋转 90˚ 或 180˚。
所以你有几个选择:
您可以使用 jpgData
,它确实符合图像的方向。虽然像 0.7 或 0.8 这样的压缩值在适当的压缩下看起来相当不错,但请注意这是一种有损格式。
您可以在提取 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: [=12=]) }),
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: [=13=]) } ?? "Unknown error"
print(message)
}
我从模拟器照片库中保存了一张图片。
然后我将图像保存为字符串,当用户单击“保存”时,它会从字符串重新转换为图像并放置在另一个控制器的 table 视图中。出于某种原因,只有某些库存照片被倒置重新转换。
图片转字符串代码:
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
}
}
转换是基于另一个 Whosebug 页面的推导:Convert between UIImage and Base64 string
整个'family'的数据库存储代码:
/**
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)
}
}
整个'family'的数据库读取代码:
/**
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: [=16=]) }),
let childImageBlob = sqlite3_column_blob(readStatement, 1)
//let parentOneName = sqlite3_column_text(readStatement, 2).flatMap({ String(cString: [=16=]) }),
//let parentOneImageBlob = sqlite3_column_blob(readStatement, 3),
//let parentTwoName = sqlite3_column_text(readStatement, 4).flatMap({ String(cString: [=16=]) }),
//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
}
...
}
问题不在于字符串的 base-64 编码(也不在于数据库)。问题是 pngData
不保留图像方向。如果我用我的 phone 在纵向模式下拍照,结果 UIImage
的 imageOrientation
是 UIImage.Orientation.right
,如果我在横向模式下用音量按钮在top,方向是.down
,等等。如果我直接使用那些图像,或者抓取它们的jpgData
,它们就可以了。但是,如果我使用 pngData
为这些图像提取 Data
,生成的资产将分别错误地旋转 90˚ 或 180˚。
所以你有几个选择:
您可以使用
jpgData
,它确实符合图像的方向。虽然像 0.7 或 0.8 这样的压缩值在适当的压缩下看起来相当不错,但请注意这是一种有损格式。您可以在提取
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: [=12=]) }),
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: [=13=]) } ?? "Unknown error"
print(message)
}