在 iOS 和 Mac OS 应用中,通常有两种图像格式可供选择:PNG 格式允许透明性,但产生的图像文件很大,不适合压缩图像(如照片);JPEG 可以创建小文件,并提供多种压缩质量以适应不同主题,但不能实现透明性。
JPNG 是一种新的图像格式,结合了两种格式的优点。JPNG 并非真正意义上的格式,它是一个简单的文件包装器,它会将 JPEG 和 PNG 图像合并到同一文件中。JPEG 用于高效压缩图像的 RGB 部分,PNG 用于存储 alpha 通道。
JPNG 库提供了在 Mac 或 iOS 上创建和加载 JPNG 格式文件的函数。库中包含一个简单的命令行工具,用于将 PNG 图像转换为 JPNG。默认情况下,JPNG 也会混编 UIImage 和 NSImage 构造函数,以便它们可以自动加载 JPNG 文件,而无需在您的应用中编写任何额外的代码。如果希望不干扰标准核心库的行为,可以禁用此混编(详见下文)。
注意:“支持”意味着库已与该版本进行了测试。“兼容”意味着库应在该 OS 版本上运行(即它不依赖于任何不可用的 SDK 功能),但不再进行兼容性测试,可能需要在正确运行时进行调整或修复错误。
JPNG 需要 ARC。如果您希望在非 ARC 项目中使用 JPNG,只需将 -fobjc-arc 编译器标志添加到 JPNG.m 类中。为此,请转到目标的“构建 phases”选项卡,打开“Compile Sources”组,双击列表中的 JPNG.m,然后在弹出窗口中输入 -fobjc-arc。
如果您希望将整个项目转换为 ARC,请在 JPNG.m 中注释掉 #error 行,然后运行 Xcode 中的 Edit > Refactor > Convert to Objective-C ARC... 工具,并确保所有您希望使用 ARC 的文件(包括 JPNG.m)都被选中。
在后台线程中加载JPNG实例以及在使用它们时可以使用除创建它们的线程外的线程上是安全的。可以在不同的线程上以重入/并发的方式调用方法。
要使用JPNG,只需把类文件拖到你的项目中,并在构建阶段标签中添加ImageIO框架。JPNG会自动扩展UIImage或NSImage,使得你无需显式导入JPNG头文件到类中就能加载JPNG图像,但如果你禁用了混编或者希望保存JPNG格式的图像,只需导入JPNG.h文件来访问这些特性。
CGImageRef CGImageCreateWithJPNGData(NSData *data, BOOL forceDecompression);
此方法从一个JPNG编码的数据中创建一个CGImage对象。调用者负责使用CGImageRelease(...)释放此CGImage对象。forceDecompression参数决定是否提前解压缩图像数据,或者是否延迟解压缩。为了提高绘图的性能,建议将此参数设置为YES,除非你计划后来通过将其绘制到新的CGContext中来自己解压缩图像。
NSData *CGImageJPNGRepresentation(CGImageRef image, CGFloat quality);
此方法返回由提供的图像的NSData表示。返回的NSData对象是自动释放的。提供的图像必须包含一个alpha通道,如果没有则行为是未定义的(这意味着它可能会崩溃)。质量值控制JPEG压缩级别,该值应在0.0到1.0之间,1.0是最高可能的品质。
UIImage *UIImageWithJPNGData(NSData *data, CGFloat scale, UIImageOrientation orientation);
此方法返回一个由JPNG编码数据创建的UIImage对象。返回的UIImage对象是自动释放的。scale和orientation属性决定了它的显示方式。如果scale为0,则scale将与主UIScreen的scale相匹配。
NSData *UIImageJPNGRepresentation(UIImage *image, CGFloat quality);
此方法返回由提供的图像的NSData表示。返回的NSData对象是自动释放的。提供的图像必须包含一个alpha通道,如果没有则行为是未定义的(这意味着它可能会崩溃)。质量值控制JPEG压缩级别,该值应在0.0到1.0之间,1.0是最高可能的品质。
NSImage *NSImageWithJPNGData(NSData *data, CGFloat scale);
此方法返回一个由JPNG编码数据创建的NSImage对象。返回的NSImage对象是自动释放的。scale属性决定NSImage将被显示的大小。如果scale为0,则scale将与主NSScreen的scale相匹配。
NSData *NSImageJPNGRepresentation(NSImage *image, CGFloat quality);
此方法返回由提供的图像的NSData表示。返回的NSData对象是自动释放的。提供的图像必须包含一个alpha通道,如果没有则行为是未定义的(这意味着它可能会崩溃)。质量值控制JPEG压缩级别,该值应在0.0到1.0之间,1.0是最高可能的品质。
默认情况下,iOS通常将图像的解压缩延迟到第一次绘制时,并且在不再需要时可能会丢弃解压缩的数据。这唯一的例外是如果你使用[UIImage imageNamed:]方法来加载图像。这对内存消耗来说是个优点,但减少了使用[UIImage imageWithContentsOfFile:]或等效方法加载的图像的绘图性能。
JPNG尝试尽可能接近地模拟这种行为,因此使用UIImageWithJPNGData(...)和NSImageWithJPNGData(...)方法加载的JPNG图像默认返回压缩图像,并且被混编的iOS和AppKit图像加载方法的行为与其原生对应物相同。
如果你想为所有图像强制解压缩(以牺牲加载时间和内存消耗为代价来提高绘图性能),可以通过添加以下预编译宏到你的构建设置来实现:
JPNG_ALWAYS_FORCE_DECOMPRESSION=1
或者,如果你愿意,可以将其添加到你的prefix.pch文件中
#define JPNG_ALWAYS_FORCE_DECOMPRESSION 1
为了决定是否按图像解压缩,你可以使用具有显式forceDecompression参数的CGImageCreateWithJPNGData(...)函数来加载图像。
如果你不确定图像如何加载且想要编写防御性代码,你可以在临时图像上下文中绘制压缩的JPNG,如下所示:
UIImage *compressedJPNG = ...
UIGraphicsBeginImageContextWithOptions(compressedJPNG.size, NO, image.compressedJPNG);
[image drawAtPoint:CGPointZero];
UIImage *uncompressedJPNG = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
然后,未压缩的图像将被优化用于绘图。
JPNGTool是一种命令行应用,用于将图像转换为JPNG格式。这个工具的源代码和可执行文件都包含在JPNGTool文件夹中。JPNGTool接受以下参数
inputfile [outputfile] [quality]
JPNGTool设计为一次转换一个图像,但是你可以使用以下命令批量转换一个图像文件夹
cd /Path/To/Image/Directory/
find ./ -name \*.png | sed 's/\.png//g' | xargs -I % -n 1 /Path/To/JPNGTool %.png %.jpng 0.8
JPNG文件格式非常简单:它由一个JPEG图像数据块、一个PNG图像数据块和一个包含图像数据块大小和一些元数据(如文件类型和版本)的脚注组成。文件以这种方式排列(使用文件脚注而不是头注)的原因是,它使得文件在非JPNG识别的程序中看起来像普通的JPEG文件,这样你就可以(例如)使用Mac上的QuickLook快速预览文件内容。脚注的结构如下
typedef struct
{
uint32_t imageSize; //the total size in bytes of the JPEG image data
uint32_t maskSize; //the total size in bytes of the PNG mask data
uint16_t footerSize; //the total size in bytes of the JPNGFooter
uint8_t majorVersion; //the major file format version (major versions are incompatible)
uint8_t minorVersion; //the minor file format version (minor versions are compatible)
uint32_t identifier; //the 'JPNG' file identifier
}
JPNGFooter;
文件中的最后四个字节始终是'JPNG'的拼写,因此你可以使用这一事实来验证给定数据块是否包含一个JPNG文件而不是其他类型的图像。该值以常量的形式提供
extern uint32_t JPNGIdentifier;
默认情况下,JPNG库会swizzle一些UIImage和NSImage构造方法,以便它们可以自动支持JPNG文件。如果你不希望这种行为,不要慌张,你可以通过在构建设置中添加以下预编译器宏来禁用它
JPNG_SWIZZLE_ENABLED=0
或者,如果你愿意,可以将其添加到你的prefix.pch文件中
#define JPNG_SWIZZLE_ENABLED 0
不过,在这样做之前,请放心,JPNG所做的swizzling最小,且非常安全。它不会破坏UIImage或NSImage缓存,也不会引起任何其他副作用。
UIKit中swizzled的方法如下
[UIImage -initWithContentsOfFile:];
[UIImage -initWithData:];
[UIImage +imageNamed:];
以及在AppKit中
[NSImage -initWithContentsOfFile:];
[NSImage -initWithData:];
[NSImage +imageNamed:];
这些方法都是为了同一个原因被swizzled:检查图像是否是JPNG文件。如果是,它将使用JPNG图像加载函数加载,否则它将传递到原始方法以正常方式加载。
JPNG图像可以通过GLKit与OpenGL一起使用,但是没有直接从磁盘加载JPNG图像的支持,使用GLKTextureLoader。相反,使用UIImage或NSImage(或上述列出的任何其他方法)加载图像,然后获取CGImage表示形式,并将其传递给GLKTextureLoader的+textureWithCGImage:options:error:
方法(请参阅OpenGL示例以获取详细信息)。
注意:OpenGL需要以特定格式提供文本图像数据。默认情况下,JPNG只有在使用[UIImage/NSImage imageNamed:]方法或使用设置forceDecompression参数为YES的CGImageCreateWithJPNGData(...)函数加载图像时才返回正确的格式。使用其他方法加载的图像如果未启用JPNG_ALWAYS_FORCE_DECOMPRESSION选项,则不会与GLKImageLoader正常工作。
如果出于某种原因你需要以不同的方式加载你的JPNG,或者你不确定图像将如何被加载并且想要编写防御性代码,你可以通过将其绘制到临时图像上下文中,将压缩的JPNG转换为GLKImageLoader的正确格式。
UIImage *compressedJPNG = ...
UIGraphicsBeginImageContextWithOptions(compressedJPNG.size, NO, image.compressedJPNG);
[image drawAtPoint:CGPointZero];
UIImage *uncompressedJPNG = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
未压缩的JPNG现在可以安全地与GLKImageLoader一起使用。
示例文件夹包含一个基准测试工具,可比较PNG与JPNG的性能。为了获得准确的结果,请确保执行以下操作:
版本 1.2.1
版本 1.2
版本 1.1.3
版本 1.1.2
版本 1.1.1
版本 1.1
版本 1.0