一、前言

图片基本上是现在移动端无法躲避的内容 最根底的用户头像、相册、评论都有触及

触及到了前端与后端的交互 那么一定会考虑到图片紧缩

本篇文章便是作者近期触及到图片紧缩所踩到的一些坑

二、一些根底知识

所谓巨细

图片分为两个巨细 分辨率巨细文件巨细

一般文件巨细与分辨率巨细是有正相关联系的

分辨率高 * 分辨率宽 = 像素点数量

像素点的格局化方式、文件格局、紧缩率 等等影响到 文件巨细

而图片紧缩的最终意图 便是 获取更小的图片文件巨细

实际与里国际

一般我们看到的某张图片是JPG(JPEG)/PNG/WEBP格局存在的

但他其实仅仅一种文件格局

在镜子后面与内存打交道的是图片的数据流 Android-BitMap iOS-NSData 其他大部分是 [Bit]

一般经过挑选图片的路径后会得到一串图片的数据流 之后再去转化成某些图片的格局 呈现在手机上

在转化图片的期间能够挑选的紧缩率 但一般会造成不可逆的效果 也便是无法再将某些图片转回数据流

三、紧缩办法

一般简略紧缩流程分为两个部分 设置图像分辨率设置紧缩质量

大部分语言也都支撑这个内容 但是寻觅这两个数值甜点方位 是解决图片紧缩的重点

在此笔者介绍两个用到感觉还不错的办法

二分法

+ (NSData *)imageDataWithLimitByteSize:(NSUInteger)maxLength image:(UIImage *)image {
    //首先判断原图巨细是否在要求内,如果满足要求则不进行紧缩
    CGFloat compression = 1;
    NSData *data = UIImageJPEGRepresentation(image, compression);
    if (data.length < maxLength) return data;
    //原图巨细超过范围,先进行“压处理”,这里 紧缩比 选用二分法进行处理,6次二分后的最小紧缩比是0.015625,现已够小了
    CGFloat max = 1;
    CGFloat min = 0;
    for (int i = 0; i < 6; ++i) {
        compression = (max + min) / 2;
        data = UIImageJPEGRepresentation(image, compression);
        if (data.length < maxLength * 0.9) {
            min = compression;
        } else if (data.length > maxLength) {
            max = compression;
        } else {
            break;
        }
    }
    //判断“压处理”的效果是否符合要求,符合要求就
    UIImage *resultImage = [UIImage imageWithData:data];
    if (data.length < maxLength) return data;
    //缩处理,直接用巨细的份额作为缩处理的份额进行处理,由于有取整处理,所以一般是需求两次处理
    NSUInteger lastDataLength = 0;
    while (data.length > maxLength && data.length != lastDataLength) {
        lastDataLength = data.length;
        //获取处理后的尺度
        CGFloat ratio = (CGFloat)maxLength / data.length;
        CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)),
                                 (NSUInteger)(resultImage.size.height * sqrtf(ratio)));
        //经过图片上下文进行处理图片
        UIGraphicsBeginImageContext(size);
        [resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
        resultImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        //获取处理后图片的巨细
        data = UIImageJPEGRepresentation(resultImage, compression);
    }
    return data;
}

大致思路便是经过指定紧缩后的文件巨细承认紧缩次数

主要缺点便是没有动态紧缩 对某些upload有硬性图片巨细的要求的能够运用

笔者遇到过一个其他问题 便是经过这个办法紧缩出的是NSData 有时上传仍然需求运用UIImage 然后再经过转化一层后就又超过了巨细范围 但这是UIKit框架问题 在这里不再深入赘述

luban

安卓圈曾诞生过一个被誉为挨近微信朋友圈紧缩算法的东西

作者称其为luban 是其在发送100张图片后对微信算法的总结与概括 宣布了一个第三方库

主要源码也很简略 这里也对其进行简略剖析

private int computeSize() {
    srcWidth = srcWidth % 2 == 1 ? srcWidth + 1 : srcWidth;
    srcHeight = srcHeight % 2 == 1 ? srcHeight + 1 : srcHeight;
    int longSide = Math.max(srcWidth, srcHeight);
    int shortSide = Math.min(srcWidth, srcHeight);
    float scale = ((float) shortSide / longSide);
    if (scale <= 1 && scale > 0.5625) {
      if (longSide < 1664) {
        return 1;
      } else if (longSide < 4990) {
        return 2;
      } else if (longSide > 4990 && longSide < 10240) {
        return 4;
      } else {
        return longSide / 1280 == 0 ? 1 : longSide / 1280;
      }
    } else if (scale <= 0.5625 && scale > 0.5) {
      return longSide / 1280 == 0 ? 1 : longSide / 1280;
    } else {
      return (int) Math.ceil(longSide / (1280.0 / scale));
    }
  }
  private Bitmap rotatingImage(Bitmap bitmap, int angle) {
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
  }
  File compress() throws IOException {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = computeSize();
    Bitmap tagBitmap = BitmapFactory.decodeStream(srcImg.open(), null, options);
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    if (Checker.SINGLE.isJPG(srcImg.open())) {
      tagBitmap = rotatingImage(tagBitmap, Checker.SINGLE.getOrientation(srcImg.open()));
    }
    tagBitmap.compress(focusAlpha ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, 60, stream);
    tagBitmap.recycle();
    FileOutputStream fos = new FileOutputStream(tagImg);
    fos.write(stream.toByteArray());
    fos.flush();
    fos.close();
    stream.close();
    return tagImg;
  }
}

源码优化

金无足赤人无完人 仔细审视这段代码其实还能发现一些问题

else {
    // return longSide / 1280 == 0 ? 1 : longSide / 1280;
    // 代码之前现已判断了 longSide < 1664 不会再出现 < 1280 的状况了
    return longSide / 1280;
}
else {
    // return (int) Math.ceil(longSide / (1280.0 / scale));
    // 由于上面现已对scale进行核算 最终的回来能够优化一下公式
    // float scale = ((float) shortSide / longSide);
    return (int) Math.ceil(shortSide / 1280.0)
}

以及现在的luban对JPG的紧缩率是固定承认的60% 紧缩率其实能够再做调整

比如根据修正尺度前的图片巨细与修正尺度后的图片巨细进行比较

在动态的设定紧缩率也是不错的挑选

唏嘘

不管代码怎么 作者仅仅对发送了100次图片后的逆向破解就得到了次算法 且忘我开源 实属不易

转瞬24开年WXG的年终开奖 高达20个月 微信在本钱的推进下还在不断的飞速发展

当年号称能够比美微信朋友圈图片紧缩算法的luban 现在五、六年后是否还能一战?

却得知作者同日子对线去了 github库已多年没有更新

昙花一现的奇迹 V.S. 柴米油盐的心酸

只能让人感到无尽的唏嘘

交融算法

没办法 笔者也需求与日子对线 在比照紧缩作用之后 编写了一套Unity的Texutre紧缩算法

现在自测作用不错 也是对上述内容的一个总结

private static int EnsureEven(int size)
{
    return size % 2 == 1 ? size + 1 : size;
}
private static int ComputeCompressSize(int width, int height)
{
    var originalWidth = EnsureEven(width);
    var originalHeight = EnsureEven(height);
    var longSide = Math.Max(originalWidth, originalHeight);
    var shortSide = Math.Min(originalWidth, originalHeight);
    var aspectRatio = (double) shortSide / (double) longSide;
    return aspectRatio switch
    {
        <= 1 and > 0.5625 => longSide switch
        {
            < 1664 => 1,
            < 4990 => 2,
            > 4990 and < 10240 => 4,
            _ => longSide / 1280,
        },
        <= 0.5625 and > 0.5 => longSide <= 1280 ? 1 : longSide / 1280,
        _ => (int) Mathf.Ceil((float) (longSide / 1280.0)) // 在笔者的环境下 不需求保存长图的尺度 故挑选紧缩程度更大的效果
    };
}
public static Texture2D CompressTexture(Texture2D sourceTexture)
{
    var size = ComputeCompressSize(sourceTexture.width, sourceTexture.height);
    var aspectRatio = Math.Pow(0.9f, size);
    var targetWidth = (int) (sourceTexture.width * aspectRatio);
    var targetHeight = (int) (sourceTexture.height * aspectRatio);
    return ResizeTexture(sourceTexture, targetWidth, targetHeight);
}

四、尾语

不寻求极限、不触及底层 这些简略的算法到现在也能打

这篇文章是这几天我对效果的一个总结

也期望能使后人的路走得再轻松一点

祝好

参考文章

Curzibn/Luban: Luban(鲁班)—Image compression with efficiency very close to WeChat Moments/可能是最挨近微信朋友圈的图片紧缩算法 (github.com)

可能是最详细的Android图片紧缩原理剖析(二)—— 鲁班紧缩算法解析 – (juejin.cn)

GuoZhiQiang/Luban_iOS: Wiki (github.com)

Luban紧缩实现剖析 | Mycroft