Luban的使用和源码解析

###Luban的使用和源码解析:
目前做App开发总绕不开图片这个元素。但是随着手机拍照分辨率的提升,图片的压缩成为一个很重要的问题。单纯对图片进行裁切,压缩已经有很多文章介绍。但是裁切成多少,压缩成多少却很难控制好,裁切过头图片太小,质量压缩过头则显示效果太差.

Luban(鲁班)就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法.

异步调用:

Luban内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可:

Luban.with(this)
    .load(photos)
    .ignoreBy(100)
    .setTargetDir(getPath())
    .filter(new CompressionPredicate() {
      @Override
      public boolean apply(String path) {
        return !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"));
      }
    })
    .setCompressListener(new OnCompressListener() {
      @Override
      public void onStart() {
        // TODO 压缩开始前调用,可以在方法内启动 loading UI
      }

      @Override
      public void onSuccess(File file) {
        // TODO 压缩成功后调用,返回压缩后的图片文件
      }

      @Override
      public void onError(Throwable e) {
        // TODO 当压缩过程出现问题时调用
      }
    }).launch();
源码解析:

1.with():使用的建造者模式:

public static Builder with(Context context) {
  return new Builder(context);
}

2.load():支持下面几种重构方法:

public Builder load(InputStreamProvider inputStreamProvider) {
  mStreamProviders.add(inputStreamProvider);
  return this;
}

public Builder load(final File file) {
  mStreamProviders.add(new InputStreamAdapter() {
    @Override
    public InputStream openInternal() throws IOException {
      return new FileInputStream(file);
    }

    @Override
    public String getPath() {
      return file.getAbsolutePath();
    }
  });
  return this;
}

public Builder load(final String string) {
  mStreamProviders.add(new InputStreamAdapter() {
    @Override
    public InputStream openInternal() throws IOException {
      return new FileInputStream(string);
    }

    @Override
    public String getPath() {
      return string;
    }
  });
  return this;
}

public <T> Builder load(List<T> list) {
  for (T src : list) {
    if (src instanceof String) {
      load((String) src);
    } else if (src instanceof File) {
      load((File) src);
    } else if (src instanceof Uri) {
      load((Uri) src);
    } else {
      throw new IllegalArgumentException("Incoming data type exception, it must be String, File, Uri or Bitmap");
    }
  }
  return this;
}

public Builder load(final Uri uri) {
  mStreamProviders.add(new InputStreamAdapter() {
    @Override
    public InputStream openInternal() throws IOException {
      return context.getContentResolver().openInputStream(uri);
    }

    @Override
    public String getPath() {
      return uri.getPath();
    }
  });
  return this;
}

2.ignoreBy():设置当图片压缩到小于、等于这个值时不再压缩,单位是KB,默认是100KB.

3.setTargetDir():设置缓存的默认根路径地址。默认是应用的缓存目录加luban_disk_cache文件夹。

/**
 * Returns a directory with the given name in the private cache directory of the application to
 * use to store retrieved media and thumbnails.
 *
 * @param context   A context.
 * @param cacheName The name of the subdirectory in which to store the cache.
 * @see #getImageCacheDir(Context)
 */
private static File getImageCacheDir(Context context, String cacheName) {
  File cacheDir = context.getExternalCacheDir();
  if (cacheDir != null) {
    File result = new File(cacheDir, cacheName);
    if (!result.mkdirs() && (!result.exists() || !result.isDirectory())) {
      // File wasn't able to create a directory, or the result exists but not a directory
      return null;
    }
   return result;
  }
 if (Log.isLoggable(TAG, Log.ERROR)) {
  Log.e(TAG, "default disk cache dir is null");
  }
 return null;
}

4,在调用Luban的lunch(),开启一个异步压缩线程就开始进行压缩。

/**
* start asynchronous compress thread
*/
private void launch(final Context context) {
if (mStreamProviders == null || mStreamProviders.size() == 0 && mCompressListener != null) {
  mCompressListener.onError(new NullPointerException("image file cannot be null"));
}

Iterator<InputStreamProvider> iterator = mStreamProviders.iterator();

while (iterator.hasNext()) {
  final InputStreamProvider path = iterator.next();

  AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
    @Override
    public void run() {
      try {
        mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_START));

        File result = compress(context, path);

        mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_SUCCESS, result));
      } catch (IOException e) {
        mHandler.sendMessage(mHandler.obtainMessage(MSG_COMPRESS_ERROR, e));
      }
    }
  });

  iterator.remove();
 }
}

private File compressReal(Context context, InputStreamProvider path) throws IOException {
File result;

File outFile = getImageCacheFile(context, Checker.SINGLE.extSuffix(path));

if (mRenameListener != null) {
  String filename = mRenameListener.rename(path.getPath());
  outFile = getImageCustomFile(context, filename);
}

if (mCompressionPredicate != null) {
  //mLeastCompressSize:就是设置的ignoreBy大小,默认是100KB
  if (mCompressionPredicate.apply(path.getPath())
      && Checker.SINGLE.needCompress(mLeastCompressSize, path.getPath())) {
    result = new Engine(path, outFile, focusAlpha).compress();
  } else {
    result = new File(path.getPath());
  }
} else {
  result = Checker.SINGLE.needCompress(mLeastCompressSize, path.getPath()) ?
      new Engine(path, outFile, focusAlpha).compress() :
      new File(path.getPath());
}

return result;
}

5.Checker的needCompress():

mLeastCompressSize:就是设置的ignoreBy大小,默认是100KB
boolean needCompress(int leastCompressSize, String path) {
if (leastCompressSize > 0) {
  File source = new File(path);
  return source.exists() && source.length() > (leastCompressSize << 10);
}
return true;
}

6.Engine的compress():

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;
}

7.Engine的computeSize():重点

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));
}
}

8.Engine的rotatingImage():重点

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);
}

9.Checker的getOrientation():重点

private int getOrientation(byte[] jpeg) {
if (jpeg == null) {
  return 0;
}

int offset = 0;
int length = 0;

// ISO/IEC 10918-1:1993(E)
while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
  int marker = jpeg[offset] & 0xFF;

  // Check if the marker is a padding.
  if (marker == 0xFF) {
    continue;
  }
  offset++;

  // Check if the marker is SOI or TEM.
  if (marker == 0xD8 || marker == 0x01) {
    continue;
  }
  // Check if the marker is EOI or SOS.
  if (marker == 0xD9 || marker == 0xDA) {
    break;
  }

  // Get the length and check if it is reasonable.
  length = pack(jpeg, offset, 2, false);
  if (length < 2 || offset + length > jpeg.length) {
    Log.e(TAG, "Invalid length");
    return 0;
  }

  // Break if the marker is EXIF in APP1.
  if (marker == 0xE1 && length >= 8
      && pack(jpeg, offset + 2, 4, false) == 0x45786966
      && pack(jpeg, offset + 6, 2, false) == 0) {
    offset += 8;
    length -= 8;
    break;
  }

  // Skip other markers.
  offset += length;
  length = 0;
}

// JEITA CP-3451 Exif Version 2.2
if (length > 8) {
  // Identify the byte order.
  int tag = pack(jpeg, offset, 4, false);
  if (tag != 0x49492A00 && tag != 0x4D4D002A) {
    Log.e(TAG, "Invalid byte order");
    return 0;
  }
  boolean littleEndian = (tag == 0x49492A00);

  // Get the offset and check if it is reasonable.
  int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
  if (count < 10 || count > length) {
    Log.e(TAG, "Invalid offset");
    return 0;
  }
  offset += count;
  length -= count;

  // Get the count and go through all the elements.
  count = pack(jpeg, offset - 2, 2, littleEndian);
  while (count-- > 0 && length >= 12) {
    // Get the tag and check if it is orientation.
    tag = pack(jpeg, offset, 2, littleEndian);
    if (tag == 0x0112) {
      int orientation = pack(jpeg, offset + 8, 2, littleEndian);
      switch (orientation) {
        case 1:
          return 0;
        case 3:
          return 180;
        case 6:
          return 90;
        case 8:
          return 270;
      }
      Log.e(TAG, "Unsupported orientation");
      return 0;
    }
    offset += 12;
    length -= 12;
  }
}

Log.e(TAG, "Orientation not found");
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
鲁班h5页面生成工具系统源码是指鲁班提供的一套开源的用于生成H5页面的工具系统的代码。这套系统源码包含了鲁班H5页面生成工具的核心功能的实现代码,可以用于二次开发和定制化。 鲁班H5页面生成工具系统源码的主要功能包括页面的模板选择、页面元素的拖拽和放置、页面的样式和布局设置,以及页面的预览和生成等。鲁班H5页面生成工具系统源码采用了现代化的前端技术,利用HTML、CSS和JavaScript等语言实现了页面的可视化设计和动态交互效果。通过使用该系统源码可以快速开发和生成各种精美的H5页面,满足用户的个性化需求。 使用鲁班H5页面生成工具系统源码可以带来诸多好处。首先,该系统源码提供了丰富的页面模板供用户选择,可以节省用户设计页面的时间和精力。其次,系统源码支持页面元素的拖拽和放置,用户可以根据自己的需求自由设计页面布局和样式。再次,系统源码提供了实时预览功能,用户可以随时查看页面的效果,方便调整和修改。最后,系统源码还支持页面的生成和导出,用户可以将生成的H5页面分享给其他人或发布到互联网上。 总之,鲁班H5页面生成工具系统源码是一套功能强大、易于使用的工具系统,可以帮助用户快速生成H5页面,并满足个性化需求。通过二次开发和定制化,用户可以进一步扩展该系统的功能和适应各种场景的需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值