上傳頭像時,本機預覽正常,伺服器卻拒收檔案:問題往往不在副檔名,而是 JPEG 編碼後的位元組數超過 API 限制。將品質參數設為某個固定值,只能改善部分圖片,無法保證每張都符合限制。在雲端 Mac 上,可以先將輸出邏輯做成獨立的驗收指令稿,再把同一組驗收契約移入 iOS 測試目標。
先定義容量上限,別把品質當成比例
如果 API 以位元組數限制容量,用戶端也應以整數儲存上限,避免混用十進位與二進位單位。品質值是編碼器的輸入參數,不是輸出大小的百分比;即使像素尺寸相同,純色背景與細密紋理的壓縮結果仍會有明顯差異。
本文的前提是:輸入影像已完成方向正規化、透明背景處理與色彩空間選擇,輸出階段不改變像素尺寸。依品質由高到低逐一嘗試有限的級距,回傳第一個符合容量上限的結果。這個結果只是「候選級距中優先順序最高的合格項目」,不代表已找到連續區間內的最佳品質。
檔案大小是必須檢查的輸出條件,畫面品質則是另一項驗收條件。符合前者,不代表文字邊緣、膚色或漸層已經合格。
用有限搜尋取代無止境降低品質
在遠端 macOS 終端機中,將以下內容儲存為 jpeg-budget.swift。執行前須先設定好可用的 Swift 工具鏈;指令稿只使用系統框架,不會讀取私人照片,也不會修改機器設定。
import Foundation
import CoreGraphics
import ImageIO
import UniformTypeIdentifiers
enum ExportError: Error {
case invalidBudget
case encoding
case budgetUnreachable
case decoding
}
func encodeJPEG(_ image: CGImage, quality: Double) throws -> Data {
let buffer = NSMutableData()
guard let destination = CGImageDestinationCreateWithData(
buffer as CFMutableData,
UTType.jpeg.identifier as CFString,
1,
nil
) else {
throw ExportError.encoding
}
let properties = [
kCGImageDestinationLossyCompressionQuality: quality
] as CFDictionary
CGImageDestinationAddImage(destination, image, properties)
guard CGImageDestinationFinalize(destination) else {
throw ExportError.encoding
}
return buffer as Data
}
func exportJPEG(
_ image: CGImage,
maxBytes: Int
) throws -> (data: Data, quality: Double) {
guard maxBytes > 0 else {
throw ExportError.invalidBudget
}
let levels = [0.9, 0.8, 0.7, 0.6, 0.5]
for quality in levels {
let data = try encodeJPEG(image, quality: quality)
if data.count <= maxBytes {
return (data, quality)
}
}
throw ExportError.budgetUnreachable
}
func verify(_ data: Data, width: Int, height: Int) throws {
guard let source = CGImageSourceCreateWithData(
data as CFData, nil
),
CGImageSourceGetType(source) == UTType.jpeg.identifier as CFString,
let decoded = CGImageSourceCreateImageAtIndex(source, 0, nil),
decoded.width == width,
decoded.height == height else {
throw ExportError.decoding
}
}
let space = CGColorSpace(name: CGColorSpace.sRGB)!
let context = CGContext(
data: nil,
width: 320,
height: 240,
bitsPerComponent: 8,
bytesPerRow: 0,
space: space,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
)!
context.setFillColor(
CGColor(red: 0.2, green: 0.4, blue: 0.6, alpha: 1)
)
context.fill(CGRect(x: 0, y: 0, width: 320, height: 240))
let image = context.makeImage()!
let result = try exportJPEG(image, maxBytes: 1_000_000)
precondition(result.data.count <= 1_000_000)
try verify(result.data, width: 320, height: 240)
do {
_ = try exportJPEG(image, maxBytes: 1)
preconditionFailure("Expected budget rejection")
} catch ExportError.budgetUnreachable {
}
執行 xcrun swift jpeg-budget.swift,成功時不會有任何輸出,失敗時則以非零結束狀態結束。這裡用純色圖片驗證流程,不將它視為實際壓縮能力的代表樣本。程式碼中的強制解包只用於固定的測試資料;接收外部圖片的應用程式碼應回傳明確的錯誤。
檢查失敗路徑,而不只檢查成功產出的檔案
指令稿區分了無效的容量上限、編碼失敗,以及無法符合容量上限這三種情況。不能一律將它們轉成空檔案:無效的容量上限屬於呼叫端錯誤,編碼失敗需要調查輸入資料或編碼器,而無法符合容量上限則應交由產品策略處理。
補充測試時,至少要涵蓋容量上限為零與負數、上限恰好等於輸出位元組數,以及所有級距都無法通過的情況。等於上限時必須允許通過。不要直接斷言「比最高品質結果少一個位元組就一定失敗」,因為後續級距仍可能符合條件。
獨立驗證搜尋分支
將實際編碼操作抽離成可注入的函式,以預設長度的回傳值驗證搜尋流程:第一個級距合格時只呼叫一次,中間級距合格時便停止後續編碼,全部超限時的嘗試次數則等於候選級距數。如此不必依賴某張圖片剛好產生特定大小,也能確認提早結束機制確實有效。
不採用未經驗證的二分搜尋,是為了避免暗中假設輸出大小具有嚴格的單調性。逐一遍歷有限級距的成本明確,但大圖會重複編碼,因此還應設定輸入像素數上限,並避免同時處理過多圖片。
建立能揭露問題的圖片測試資料
測試集應同時包含純色、細線文字、漸層和高細節紋理。可以透過程式產生,也可以使用團隊獲准保存的固定樣本;每份樣本都要記錄尺寸與內容摘要,不可每次隨機產生卻不保留種子值。
除了檢查 Data.count,還要重新讀取輸出,確認格式與像素尺寸。損壞的檔案可能很小,卻不是合格結果。JPEG 採用有損壓縮,不能斷言解碼後的每個像素都與原圖相同;若專案需要自動評估視覺誤差,應固定計算所用的色彩空間與容許誤差,並保留供人工複核的樣本。
同一張圖片在不同系統版本下,不一定會產生相同的雜湊值。更穩妥的斷言是格式正確、尺寸不變、位元組數符合上限,而且搜尋次數有界,再記錄實際選用的品質級距,以供調查。
接回 iOS 輸出流程並持續驗收
Mac 指令稿通過後,將函式放入應用程式可重用的模組,並在目標 iOS 執行環境中執行同一組測試資料。系統框架的編碼結果可能不同,不能直接將 macOS 上的成功當成 iOS 也能通過的證明。
如果所有級距都失敗,介面應明確提示無法在目前條件下輸出。若允許縮圖,應另外設定尺寸級距與最短邊長下限,並清楚告知使用者;不要在品質搜尋過程中偷偷改變尺寸。若上傳前還會將圖片包裝成請求,應分別核對圖片檔案的容量上限與整個請求主體的容量上限。
最後只需保留輸入測試資料的識別碼、系統版本、候選級距、選定品質與輸出位元組數,不必記錄原始私人圖片。讓容量限制、視覺要求與失敗行為各自有明確的處理方式,輸出問題才不會被一個不斷調低的品質數值掩蓋。
常見問題
將 JPEG 品質設為 0.5,檔案就會縮小一半嗎?
不會。品質值控制編碼取捨,不代表壓縮比例。必須在編碼完成後檢查 Data.count,才能判斷是否符合容量上限。
所有品質級距都超過容量上限時怎麼辦?
停止搜尋並回報明確錯誤。若產品允許縮小像素尺寸,應進入另一個訂有尺寸下限的流程,不可默默縮圖。
Mac 上的測試通過就能取代 iOS 驗證嗎?
不能。Mac 適合快速檢查搜尋邏輯,目標 iOS 執行環境仍須跑相同契約測試;不應要求不同系統產生逐位元組相同的 JPEG。
用實際工作,測試遠端 Mac。
租用一台專屬使用的 Mac mini M4,透過 macOS 圖形介面與命令列操作。適合先測試連線和工作流程;若需要長時間離線運作或連接專業硬體,也可以評估自行購買。
租用專屬 Mac mini