JPNG 1.2.1

JPNG 1.2.1

测试已测试
语言语言 Obj-CObjective C
许可证 zlib
发布日期最后发布日期2014年12月

Nick Lockwood 维护。



JPNG 1.2.1

  • 作者
  • Nick Lockwood

目的

在 iOS 和 Mac OS 应用中,通常有两种图像格式可供选择:PNG 格式允许透明性,但产生的图像文件很大,不适合压缩图像(如照片);JPEG 可以创建小文件,并提供多种压缩质量以适应不同主题,但不能实现透明性。

JPNG 是一种新的图像格式,结合了两种格式的优点。JPNG 并非真正意义上的格式,它是一个简单的文件包装器,它会将 JPEG 和 PNG 图像合并到同一文件中。JPEG 用于高效压缩图像的 RGB 部分,PNG 用于存储 alpha 通道。

JPNG 库提供了在 Mac 或 iOS 上创建和加载 JPNG 格式文件的函数。库中包含一个简单的命令行工具,用于将 PNG 图像转换为 JPNG。默认情况下,JPNG 也会混编 UIImage 和 NSImage 构造函数,以便它们可以自动加载 JPNG 文件,而无需在您的应用中编写任何额外的代码。如果希望不干扰标准核心库的行为,可以禁用此混编(详见下文)。

支持的操作系统和 SDK 版本

  • 支持构建目标 - iOS 8.1 / Mac OS 10.10(Xcode 6.1,Apple LLVM 编译器 6.0)
  • 最早支持部署目标 - iOS 5.0 / Mac OS 10.7
  • 最早兼容部署目标 - iOS 4.3 / Mac OS 10.6(64 位)

注意:“支持”意味着库已与该版本进行了测试。“兼容”意味着库应在该 OS 版本上运行(即它不依赖于任何不可用的 SDK 功能),但不再进行兼容性测试,可能需要在正确运行时进行调整或修复错误。

ARC 兼容性

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是最高可能的品质。

UIKit函数

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是最高可能的品质。

AppKit函数

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

JPNGTool是一种命令行应用,用于将图像转换为JPNG格式。这个工具的源代码和可执行文件都包含在JPNGTool文件夹中。JPNGTool接受以下参数

inputfile [outputfile] [quality]
  • inputfile是你想要转换的图像文件的路径。理想情况下,这应该是一个带有alpha通道的PNG图像。目前尚未测试其他格式,并且没有alpha通道的图像可能会崩溃。此参数是必需的。
  • outputfile是保存的JPNG文件的路径。如果省略此参数,默认会与inputfile匹配,并添加.jpng扩展名。
  • quality是图像RGB部分的JPEG压缩质量。这应该大于0.0且小于或等于1.0,其中1.0为最高质量。如果省略此参数,默认为0.8。

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;

UIKit/AppKit swizzling

默认情况下,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图像加载函数加载,否则它将传递到原始方法以正常方式加载。

GLKit支持

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. 以发布模式构建,而不是调试模式
  3. 断开与调试器 / Xcode 的连接
  4. 终止应用并重新运行几次以验证

发布说明

版本 1.2.1

  • 在创建JPEG数据时消除了透明区域两侧的白色边框冗余
  • JPNG现在需要在iOS上包含ImageIO框架

版本 1.2

  • 在大多数情况下,JPNG图像现在不再自动解压缩,性能与普通PNG相当(详细信息请参见README)
  • 为CGImageCreateWithJPNGData()添加了forceDecompression选项
  • 添加了JPNG_ALWAYS_FORCE_DECOMPRESSION全局设置
  • 现在符合-Weverything警告级别

版本 1.1.3

  • 修复了创建图像时的内存泄漏问题
  • 修复了与JPNGTool相关的颜色偏移问题
  • 改进了+imageNamed:方法的线程安全性
  • 当iOS收到低内存警告时现在将刷新图像缓存

版本 1.1.2

  • 现在支持更严格的编译器警告设置
  • JPNGTool现在如果输出文件目录不存在则创建它

版本 1.1.1

  • JPNG图像现在与GLKit纹理加载器兼容
  • 修复了处理图像文件扩展名逻辑中的错误
  • 现在符合-Wextra警告级别

版本 1.1

  • 现在在Mac和iOS上所有图像加载方法都支持swizzle
  • 修复了不使用StandardPaths时的错误
  • 添加了测试项目

版本 1.0

  • 首次发布