当前位置: 首页 > 工具软件 > YYImage > 使用案例 >

YYImage 源码解析

祁永嘉
2023-12-01

如何禁止某个类调用某些方法

@interface YYImageEncoder : NSObject
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
@end

// - 实现 不可以直接调用 init 方法 可以调用 initWithType: 并且在initWithType中调用的是 [super init];
- (instancetype)init {
    @throw [NSException exceptionWithName:@"YYImageEncoder init error" reason:@"YYImageEncoder must be initialized with a type. Use 'initWithType:' instead." userInfo:nil];
    return nil;
}
- (instancetype)initWithType:(YYImageType)type {
    self = [super init];
    if (!self) return nil;
    return self;
}

解析 CGBitmapInfo

请先阅读 谈谈 iOS 中图片的解压缩
CGBitmapInfo : alpha 的信息 | 颜色分量是否为浮点数 | 像素格式的字节顺序 (用这三个数据来表示CGBitmapInfo)

  • alpha 的信息 :

    • 是否有 alpha 信息
      CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
      BOOL hasAlpha = NO;
      if (alphaInfo == kCGImageAlphaPremultipliedLast ||
      alphaInfo == kCGImageAlphaPremultipliedFirst ||
      alphaInfo == kCGImageAlphaFirst ||
      alphaInfo == kCGImageAlphaLast) {
      hasAlpha = YES;
      }

    • 如果包含 alpha ,那么 alpha 信息所处的位置,在像素的最低有效位,比如 RGBA ,还是最高有效位,比如 ARGB ;
      如果包含 alpha ,那么每个颜色分量是否已经乘以 alpha 的值,这种做法可以加速图片的渲染时间,因为它避免了渲染时的额外乘法运算。比如,对于 RGB 颜色空间,用已经乘以 alpha 的数据来渲染图片,每个像素都可以避免 3 次乘法运算,红色乘以 alpha ,绿色乘以 alpha 和蓝色乘以 alpha
      CGImageAlphaInfo info = (hasAlpah) ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;

  • 至于颜色分量是否为浮点数,这个就比较简单了,直接逻辑或 kCGBitmapFloatComponents 就可以了; 我们一般用不上这个值

  • 们知道字节顺序的值应该使用的是 32 位的主机字节顺序 kCGBitmapByteOrder32Host ,这样的话不管当前设备采用的是小端模式还是大端模式,字节顺序始终与其保持一致。

[代码]

    CGImageRef imageRef = img.CGImage;
    size_t wid = CGImageGetWidth(imageRef);
    size_t hei = CGImageGetHeight(imageRef);
    
    // - 位图是否包含透明度
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
    BOOL hasAlpha = NO;
    if (alphaInfo == kCGImageAlphaPremultipliedLast ||
        alphaInfo == kCGImageAlphaPremultipliedFirst ||
        alphaInfo == kCGImageAlphaFirst ||
        alphaInfo == kCGImageAlphaLast) {
        hasAlpha = YES;
    }

    // - 位图的字节存出顺序
    CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;

    // - 位图的透明度的信息的位置
    bitmapInfo |= ((hasAlpha) ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst);
    
    // - 色彩空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
    // - 创建图像上下文
    CGContextRef context = CGBitmapContextCreate(NULL, wid, hei, 8, 0, colorSpace, bitmapInfo);
    
    // - 将原始位图, 绘制到图形上下文;
    CGContextDrawImage(context, CGRectMake(0, 0, wid, hei), imageRef);
    
    // - 创建一个新的解压后的位图
    CGImageRef newImage = CGBitmapContextCreateImage(context);

YYImage中timer 和 target 不引起循环引用的方式(使用消息转发)

// - 声明  YYWeakProxy
@interface YYWeakProxy : NSProxy
@property (nullable, nonatomic, weak, readonly) id target;
@end

// - 定义 YYWeakProxy 
@implementation YYWeakProxy
+ (instancetype)proxyWithTarget:(id)target {
    return [[YYWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
    return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
    return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
    return [_target isEqual:object];
}
- (NSUInteger)hash {
    return [_target hash];
}
- (Class)superclass {
    return [_target superclass];
}
- (Class)class {
    return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
    return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
    return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
    return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
    return YES;
}
- (NSString *)description {
    return [_target description];
}
- (NSString *)debugDescription {
    return [_target debugDescription];
}
@end

// - 使用 YYWeakProxy
 _link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(step:)];

image 转 像素数据


// - 强转的宏定义
#define typeChange(type, instance) *((type *)&instance)

// - 32位转换
static inline uint32_t yy_swap_endian_uint32(uint32_t value) {
    return
    (uint32_t)((value & 0x000000FFU) << 24) |
    (uint32_t)((value & 0x0000FF00U) <<  8) |
    (uint32_t)((value & 0x00FF0000U) >>  8) |
    (uint32_t)((value & 0xFF000000U) >> 24) ;
}

// - 在色值前边是加入透明度
static inline uint32_t yy_addApha_uint32(uint32_t value) {
    return value | 0xFF000000;
}


// - 像素结构体
typedef struct QGPixStruct {
    UInt8 pixA;
    UInt8 pixR;
    UInt8 pixG;
    UInt8 pixB;
}QGPix;

@implementation QGImageTool

/** data 转 image */
+ (UIImage * )imageFromeData:(void *)imageData imageSize:(CGSize)imageSize{
    // - 调用函数
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(imageData,
                                                 imageSize.width,
                                                 imageSize.height,
                                                 8,
                                                 imageSize.width * 4,
                                                 colorSpace,
                                                 kCGBitmapByteOrder32Host |
                                                 kCGImageAlphaPremultipliedFirst);
    
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    
    // - 释放内存
    CGImageRelease(imageRef);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    return image;
}

/** image 转 data */
+(void *)dataFromeImage:(UIImage *)image{

    // -   方案一 : 先绘制图形上下文, 然后初始化一个内存空间, 之后把像素信息复制到申请的内存空间中
    return ({
        // - 函数调用条件
        CGImageRef imageRef = [image CGImage];
        size_t wid = CGImageGetWidth(imageRef);
        size_t hei = CGImageGetHeight(imageRef);
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        
        // - 使用 coreGraphics 方式解码
        // - 调用函数
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     wid,
                                                     hei,
                                                     8,
                                                     0,
                                                     colorSpace,
                                                     kCGBitmapByteOrder32Host |
                                                     kCGImageAlphaPremultipliedFirst);
        CGContextDrawImage(context, CGRectMake(0, 0, wid, hei), imageRef);
        void *data = malloc(wid * hei * 4);
        memcpy(data, CGBitmapContextGetData(context), wid * hei * 4);
        
        // - 释放内存
        CGColorSpaceRelease(colorSpace);
        CGContextRelease(context);
        
	// - 使用 imageIO 方式解码图片
        CGImageRef newImageSource;
        CFDataRef dataFromImageDataProvider =  CGDataProviderCopyData(CGImageGetDataProvider(newImageSource));
        imageData = (GLubyte *)CFDataGetBytePtr(dataFromImageDataProvider);
        
        data;
    });
    
    // - 方案二 : 在创建图形上下文时候直接将像素信息赋值到内存中
    return ({
        // - 函数调用条件
        CGImageRef imageRef = [image CGImage];
        size_t wid = CGImageGetWidth(imageRef);
        size_t hei = CGImageGetHeight(imageRef);
        size_t size = wid * hei * 4;
        void* data = malloc(size);
        memset(data, 0, size);
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        
        // - 调用函数
        CGContextRef context = CGBitmapContextCreate(data, wid, hei, 8, wid * 4, colorSpace, kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
        CGContextDrawImage(context, CGRectMake(0, 0, wid, hei), imageRef);
        
        // - 释放内存
        CGColorSpaceRelease(colorSpace);
        CGContextRelease(context);
        
        data;
    });
    
}

 /** 把图片中的颜色替换为另一个颜色 */
+(UIImage *)repalceImage:(UIImage *)image color:(int)sourceColor toColor:(int)targetColor{
    
    CGFloat width = image.size.width;
    CGFloat height = image.size.height;

    uint32_t *data = (uint32_t *)[self dataFromeImage:image];
    
    for (int i = 0 ; i < width * height; i++) {
        uint32_t *souPix = (data + i);
        if (*souPix == yy_addApha_uint32(sourceColor)) {
            *souPix = yy_addApha_uint32(targetColor);
        }
        
        ({ // - 类型转换(自己写代码) 使用 yy_swap_endian_uint32 让结构体是正常的存储方式
            uint32_t result = yy_swap_endian_uint32(*souPix);
            QGPix pix = *((QGPix *)&result);
            NSLog(@"%x - %x - %x - %x", pix.pixA, pix.pixR, pix.pixG, pix.pixB);
        });
        
        ({// - 强制类型转换(宏定义) 使用 yy_swap_endian_uint32 让结构体是正常的存储方式
            uint32_t result = yy_swap_endian_uint32(*souPix);
            QGPix pix = typeChange(QGPix, result);
            NSLog(@"%x - %x - %x - %x", pix.pixA, pix.pixR, pix.pixG, pix.pixB);
        });
    }
    
    UIImage *img = [self imageFromeData:data imageSize:CGSizeMake(width, height)];
    free(data);
    
    return img;
}

@end

两种图片解码的方式

1、CGContextDrawImage方式(CoreGraphics)以上就是这种方法
        使用 CGBitmapContextCreate 函数创建一个位图上下文;
        使用 CGContextDrawImage 函数将原始位图绘制到上下文中;
        使用 CGBitmapContextCreateImage 函数创建一张新的解压缩后的位图。
     2、CGImageGetDataProvider方式(ImageIO)
        使用 CGImageSourceCreateWithData(data) 创建ImageSource。
        使用 CGImageSourceCreateImageAtIndex(source) 创建一个未解码的 CGImage。
        使用 CGImageGetDataProvider(image) 获取这个图片的数据源。
        使用 CGDataProviderCopyData(provider) 从数据源获取直接解码的数据。

CoreFoundation 中的字节序转换函数

// - 函数有很多, 这里只是记录下, 需要时候查阅 在 CFByteOrder.h 文件中
CFSwapInt32HostToLittle()
CFSwapInt32HostToBig()

大端转换成小端:

// - 逆序四个字节或两个字节
#define YY_FOUR_CC(c1,c2,c3,c4) ((uint32_t)(((c4) << 24) | ((c3) << 16) | ((c2) << 8) | (c1)))
#define YY_TWO_CC(c1,c2) ((uint16_t)(((c2) << 8) | (c1)))


// - 这里用的是 | 逻辑运算符, 我之前用的是+ 就显得有些 low 了 需要学习下.
// - 逻辑运算的 |  很多时候, 就相当于 +;

// - 16位转换
static inline uint16_t yy_swap_endian_uint16(uint16_t value) {
    return
    (uint16_t) ((value & 0x00FF) << 8) |
    (uint16_t) ((value & 0xFF00) >> 8) ;
}

// - 32位转换
static inline uint32_t yy_swap_endian_uint32(uint32_t value) {
    return
    (uint32_t)((value & 0x000000FFU) << 24) |
    (uint32_t)((value & 0x0000FF00U) <<  8) |
    (uint32_t)((value & 0x00FF0000U) >>  8) |
    (uint32_t)((value & 0xFF000000U) >> 24) ;
}

大小端存储转换 (非原著作者, 这里是保存,方便以后查找, 原著地址: https://github.com/zhu410289616/RHSocketKit)

//
//  RHSocketUtils.h
//  RHSocketKitDemo
//
//  Created by zhuruhong on 15/12/23.
//  Copyright © 2015年 zhuruhong. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface RHSocketUtils : NSObject

/**
 *  反转字节序列
 *
 *  @param srcData 原始字节NSData
 *
 *  @return 反转序列后字节NSData
 */
+ (NSData *)dataWithReverse:(NSData *)srcData;

/** 将数值转成字节。编码方式:低位在前,高位在后 */
+ (NSData *)byteFromInt8:(int8_t)val;
+ (NSData *)bytesFromInt16:(int16_t)val;
+ (NSData *)bytesFromInt32:(int32_t)val;
+ (NSData *)bytesFromInt64:(int64_t)val;
+ (NSData *)bytesFromValue:(int64_t)value byteCount:(int)byteCount;
+ (NSData *)bytesFromValue:(int64_t)value byteCount:(int)byteCount reverse:(BOOL)reverse;

/** 将字节转成数值。解码方式:前序字节为低位,后续字节为高位 */
+ (int8_t)int8FromBytes:(NSData *)data;
+ (int16_t)int16FromBytes:(NSData *)data;
+ (int32_t)int32FromBytes:(NSData *)data;
+ (int64_t)int64FromBytes:(NSData *)data;
+ (int64_t)valueFromBytes:(NSData *)data;
+ (int64_t)valueFromBytes:(NSData *)data reverse:(BOOL)reverse;

/** 将字符串转换为data。例如:返回8个字节的data, upano --> <7570616e 6f000000> */
+ (NSData *)dataFromNormalString:(NSString *)normalString byteCount:(int)byteCount;

/** 16进制字符串转换为data。24211D3498FF62AF  -->  <24211D34 98FF62AF> */
+ (NSData *)dataFromHexString:(NSString *)hexString;

/** data转换为16进制。<24211D34 98FF62AF>  -->  24211D3498FF62AF */
+ (NSString *)hexStringFromData:(NSData *)data;

/** hex字符串转换为ascii码。00de0f1a8b24211D3498FF62AF -->  3030646530663161386232343231314433343938464636324146 */
+ (NSString *)asciiStringFromHexString:(NSString *)hexString;

/** ascii码转换为hex字符串。343938464636324146 --> 498FF62AF */
+ (NSString *)hexStringFromASCIIString:(NSString *)asciiString;

// Return line separators.
+ (NSData *)CRLFData;
+ (NSData *)CRData;
+ (NSData *)LFData;
+ (NSData *)ZeroData;

@end


//
//  RHSocketUtils.m
//  RHSocketKitDemo
//
//  Created by zhuruhong on 15/12/23.
//  Copyright © 2015年 zhuruhong. All rights reserved.
//

#import "RHSocketUtils.h"

@implementation RHSocketUtils

/**
 *  反转字节序列
 *
 *  @param srcData 原始字节NSData
 *
 *  @return 反转序列后字节NSData
 */
+ (NSData *)dataWithReverse:(NSData *)srcData
{
//    NSMutableData *dstData = [[NSMutableData alloc] init];
//    for (NSUInteger i=0; i<srcData.length; i++) {
//        [dstData appendData:[srcData subdataWithRange:NSMakeRange(srcData.length-1-i, 1)]];
//    }//for
    
    NSUInteger byteCount = srcData.length;
    NSMutableData *dstData = [[NSMutableData alloc] initWithData:srcData];
    NSUInteger halfLength = byteCount / 2;
    for (NSUInteger i=0; i<halfLength; i++) {
        NSRange begin = NSMakeRange(i, 1);
        NSRange end = NSMakeRange(byteCount - i - 1, 1);
        NSData *beginData = [srcData subdataWithRange:begin];
        NSData *endData = [srcData subdataWithRange:end];
        [dstData replaceBytesInRange:begin withBytes:endData.bytes];
        [dstData replaceBytesInRange:end withBytes:beginData.bytes];
    }//for
    
    return dstData;
}

+ (NSData *)byteFromInt8:(int8_t)val
{
    NSMutableData *valData = [[NSMutableData alloc] init];
    [valData appendBytes:&val length:1];
    return valData;
}

+ (NSData *)bytesFromInt16:(int16_t)val
{
    NSMutableData *valData = [[NSMutableData alloc] init];
    [valData appendBytes:&val length:2];
    return valData;
}

+ (NSData *)bytesFromInt32:(int32_t)val
{
    NSMutableData *valData = [[NSMutableData alloc] init];
    [valData appendBytes:&val length:4];
    return valData;
}

+ (NSData *)bytesFromInt64:(int64_t)val
{
    NSMutableData *tempData = [[NSMutableData alloc] init];
    [tempData appendBytes:&val length:8];
    return tempData;
}

+ (NSData *)bytesFromValue:(int64_t)value byteCount:(int)byteCount
{
    NSMutableData *valData = [[NSMutableData alloc] init];
    int64_t tempVal = value;
    int offset = 0;
    
    while (offset < byteCount) {
        unsigned char valChar = 0xff & tempVal;
        [valData appendBytes:&valChar length:1];
        tempVal = tempVal >> 8;
        offset++;
    }//while
    
    return valData;
}

+ (NSData *)bytesFromValue:(int64_t)value byteCount:(int)byteCount reverse:(BOOL)reverse
{
    NSData *tempData = [self bytesFromValue:value byteCount:byteCount];
    if (reverse) {
        return tempData;
    }
    
    return [self dataWithReverse:tempData];
}

+ (int8_t)int8FromBytes:(NSData *)data
{
    NSAssert(data.length >= 1, @"uint8FromBytes: (data length < 1)");
    
    int8_t val = 0;
    [data getBytes:&val length:1];
    return val;
}

+ (int16_t)int16FromBytes:(NSData *)data
{
    NSAssert(data.length >= 2, @"uint16FromBytes: (data length < 2)");
    
    int16_t val = 0;
    [data getBytes:&val length:2];
    return val;
}

+ (int32_t)int32FromBytes:(NSData *)data
{
    NSAssert(data.length >= 4, @"uint16FromBytes: (data length < 4)");
    
    int32_t val = 0;
    [data getBytes:&val length:4];
    return val;
}

+ (int64_t)int64FromBytes:(NSData *)data
{
    NSAssert(data.length >= 8, @"uint16FromBytes: (data length < 8)");
    
    int64_t val = 0;
    [data getBytes:&val length:8];
    return val;
}

+ (int64_t)valueFromBytes:(NSData *)data
{
    NSUInteger dataLen = data.length;
    int64_t value = 0;
    int offset = 0;
    
    while (offset < dataLen) {
        int32_t tempVal = 0;
        [data getBytes:&tempVal range:NSMakeRange(offset, 1)];
        value += (tempVal << (8 * offset));
        offset++;
    }//while
    
    return value;
}

+ (int64_t)valueFromBytes:(NSData *)data reverse:(BOOL)reverse
{
    NSData *tempData = data;
    if (reverse) {
        tempData = [self dataWithReverse:tempData];
    }
    return [self valueFromBytes:tempData];
}

/** 将字符串转换为data。例如:返回8个字节的data, upano --> <7570616e 6f000000> */
+ (NSData *)dataFromNormalString:(NSString *)normalString byteCount:(int)byteCount
{
    NSAssert(byteCount > 0, @"byteCount <= 0");
    
    char normalChar[byteCount];
    memset(normalChar, 0, byteCount);
    memcpy(normalChar, [normalString UTF8String], MIN(normalString.length, byteCount));
    return [[NSData alloc] initWithBytes:normalChar length:byteCount];
}

+ (NSData *)dataFromHexString:(NSString *)hexString
{
    NSAssert((hexString.length > 0) && (hexString.length % 2 == 0), @"hexString.length mod 2 != 0");
    NSMutableData *data = [[NSMutableData alloc] init];
    for (NSUInteger i=0; i<hexString.length; i+=2) {
        NSRange tempRange = NSMakeRange(i, 2);
        NSString *tempStr = [hexString substringWithRange:tempRange];
        NSScanner *scanner = [NSScanner scannerWithString:tempStr];
        unsigned int tempIntValue;
        [scanner scanHexInt:&tempIntValue];
        [data appendBytes:&tempIntValue length:1];
    }
    return data;
}

+ (NSString *)hexStringFromData:(NSData *)data
{
    NSAssert(data.length > 0, @"data.length <= 0");
    NSMutableString *hexString = [[NSMutableString alloc] init];
    const Byte *bytes = data.bytes;
    for (NSUInteger i=0; i<data.length; i++) {
        Byte value = bytes[i];
        Byte high = (value & 0xf0) >> 4;
        Byte low = value & 0xf;
        [hexString appendFormat:@"%x%x", high, low];
    }//for
    return hexString;
}

+ (NSString *)asciiStringFromHexString:(NSString *)hexString
{
    NSMutableString *asciiString = [[NSMutableString alloc] init];
    const char *bytes = [hexString UTF8String];
    for (NSUInteger i=0; i<hexString.length; i++) {
        [asciiString appendFormat:@"%0.2X", bytes[i]];
    }
    return asciiString;
}

+ (NSString *)hexStringFromASCIIString:(NSString *)asciiString
{
    NSMutableString *hexString = [[NSMutableString alloc] init];
    const char *asciiChars = [asciiString UTF8String];
    for (NSUInteger i=0; i<asciiString.length; i+=2) {
        char hexChar = '\0';
        
        //high
        if (asciiChars[i] >= '0' && asciiChars[i] <= '9') {
            hexChar = (asciiChars[i] - '0') << 4;
        } else if (asciiChars[i] >= 'a' && asciiChars[i] <= 'z') {
            hexChar = (asciiChars[i] - 'a' + 10) << 4;
        } else if (asciiChars[i] >= 'A' && asciiChars[i] <= 'Z') {
            hexChar = (asciiChars[i] - 'A' + 10) << 4;
        }//if
        
        //low
        if (asciiChars[i+1] >= '0' && asciiChars[i+1] <= '9') {
            hexChar += asciiChars[i+1] - '0';
        } else if (asciiChars[i+1] >= 'a' && asciiChars[i+1] <= 'z') {
            hexChar += asciiChars[i+1] - 'a' + 10;
        } else if (asciiChars[i+1] >= 'A' && asciiChars[i+1] <= 'Z') {
            hexChar += asciiChars[i+1] - 'A' + 10;
        }//if
        
        [hexString appendFormat:@"%c", hexChar];
    }
    return hexString;
}

// Return line separators.
+ (NSData *)CRLFData
{
    return [NSData dataWithBytes:"\x0D\x0A" length:2];
}

+ (NSData *)CRData
{
    return [NSData dataWithBytes:"\x0D" length:1];
}

+ (NSData *)LFData
{
    return [NSData dataWithBytes:"\x0A" length:1];
}

+ (NSData *)ZeroData
{
    return [NSData dataWithBytes:"" length:1];
}

@end

 类似资料: