Luban(鲁班)—Image compression with efficiency very close to WeChat Moments/可能是最接近微信朋友圈的图片压缩算法

Overview

Luban

Build Status Download

Luban(鲁班) —— Android图片压缩工具,仿微信朋友圈压缩策略。

Luban-turbo —— 鲁班项目的turbo版本,查看trubo分支

写在前面

家境贫寒,工作繁忙。只能不定期更新,还望网友们见谅!

项目描述

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

于是自然想到App巨头“微信”会是怎么处理,Luban(鲁班)就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法。

因为有其他语言也想要实现Luban,所以描述了一遍算法步骤

因为是逆向推算,效果还没法跟微信一模一样,但是已经很接近微信朋友圈压缩后的效果,具体看以下对比!

效果与对比

内容 原图 Luban Wechat
截屏 720P 720*1280,390k 720*1280,87k 720*1280,56k
截屏 1080P 1080*1920,2.21M 1080*1920,104k 1080*1920,112k
拍照 13M(4:3) 3096*4128,3.12M 1548*2064,141k 1548*2064,147k
拍照 9.6M(16:9) 4128*2322,4.64M 1032*581,97k 1032*581,74k
滚动截屏 1080*6433,1.56M 1080*6433,351k 1080*6433,482k

导入

implementation 'top.zibin:Luban:1.1.8'

使用

方法列表

方法 描述
load 传入原图
filter 设置开启压缩条件
ignoreBy 不压缩的阈值,单位为K
setFocusAlpha 设置是否保留透明通道
setTargetDir 缓存压缩图片路径
setCompressListener 压缩回调接口
setRenameListener 压缩前重命名接口

异步调用

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

同步调用

同步方法请尽量避免在主线程调用以免阻塞主线程,下面以rxJava调用为例

Flowable.just(photos)
    .observeOn(Schedulers.io())
    .map(new Function<List<String>, List<File>>() {
      @Override public List<File> apply(@NonNull List<String> list) throws Exception {
        // 同步方法直接返回压缩后的文件
        return Luban.with(MainActivity.this).load(list).get();
      }
    })
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe();

RELEASE NOTE

Here

License

Copyright 2016 Zheng Zibin

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • 讨论帖,新需求与新想法集中营

    讨论帖,新需求与新想法集中营

    您这个是图片上传的时候,本地图片压缩吗? 我之前也研究过微信朋友圈的图片压缩,当时得到的结果是: 第一步进行采样率压缩; 第二步进行宽高的等比例压缩(微信对原图和缩略图限制了最大长宽或者最小长宽); 第三步就是对图片的质量进行压缩(一般75或者70); 第四部就是采用webP的格式。 经过这四部的处理,基本上和微信朋友圈的效果一致,包括文件大小和显示效果。 (PS:最近看到微信朋友圈好像换了图片格式,这还有待考证)

    opened by wo327808864 18
  • 批量压缩有问题

    批量压缩有问题

    for (int i = 0; i < activity.selectedImgList.size(); i++) {
            final ImageItem img = activity.selectedImgList.get(i);
    
            if (img.size / 1024 < 500) {
                /*不压缩  直接添加*/
                compressedImgList.add(new File(img.path));
                checkComplete();
            } else {
                /*压缩后再添加*/
                compressImg(new File(img.path), i);
            }
    
        }
    

    上面是添加压缩任务

    然后开始压缩,结果 compressedImgList.add(file);的时候添加的是重复的图片 private void compressImg(final File imgFile, int position) { L.i("压缩图片"); Luban.get(activity) .load(imgFile) .putGear(Luban.THIRD_GEAR) .setFilename(imgFile.getName()) .setCompressListener(new OnCompressListener() { @Override public void onStart() {

                        Log.i("开始压缩,原图大小:", imgFile.length() / 1024 + "k");
                    }
    
                    @Override
                    public void onSuccess(File file) {
                        Log.i("压缩完成,压缩后大小:", file.length() / 1024 + "k");
                        compressedImgList.add(file);
                        L.i("添加的文件名是:" + file.getName());
    
                        checkComplete();
    
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        /*压缩失败  添加原图*/
                        L.i("压缩失败  添加原图");
                        compressedImgList.add(imgFile);
                        checkComplete();
                    }
                }).launch();
    }
    

    下面是打印日志

    I/压缩完成,压缩后大小:: 191k 10-27 15:02:26.023 28680-28680/com.ship.yzq.ship I/shipApp: 添加的文件名是:IMG_20161027_142337.jpg.jpg 10-27 15:02:26.605 28680-28680/com.ship.yzq.ship I/压缩完成,压缩后大小:: 178k 10-27 15:02:26.606 28680-28680/com.ship.yzq.ship I/shipApp: 添加的文件名是:IMG_20161027_142331.jpg.jpg 10-27 15:02:26.682 28680-28680/com.ship.yzq.ship I/压缩完成,压缩后大小:: 184k 10-27 15:02:26.682 28680-28680/com.ship.yzq.ship I/shipApp: 添加的文件名是:IMG_20161027_142331.jpg.jpg 10-27 15:02:26.781 28680-28680/com.ship.yzq.ship I/压缩完成,压缩后大小:: 189k 10-27 15:02:26.781 28680-28680/com.ship.yzq.ship I/shipApp: 添加的文件名是:IMG_20161027_142327.jpg.jpg 10-27 15:02:26.807 28680-28680/com.ship.yzq.ship I/shipApp: IMG_20161027_142337.jpg.jpg 10-27 15:02:26.807 28680-28680/com.ship.yzq.ship I/shipApp: IMG_20161027_142331.jpg.jpg 10-27 15:02:26.807 28680-28680/com.ship.yzq.ship I/shipApp: IMG_20161027_142331.jpg.jpg 10-27 15:02:26.807 28680-28680/com.ship.yzq.ship I/shipApp: IMG_20161027_142327.jpg.jpg

    opened by yuzhiqiang1993 9
  • Luban  压缩后得到的文件路径问题

    Luban 压缩后得到的文件路径问题

    Luban.get(getContext()) .load(new File(item.path)) //传人要压缩的图片 .putGear(Luban.THIRD_GEAR) //设定压缩档次,默认三挡 .setFilename(SDCardUtils.SdPath(getContext(), "qa") + System.currentTimeMillis() + "." + FileUtils.getFilenameExtension(item.path))

    为了在压缩后有时是没有后缀,所以设置文件路径名称,使其压缩后按该文件路径跟名称保存. 但是,目前情况是得到的文件结果不可预料...有时候是按设置的路径名称,有时候是原路径,设置的没有起作用.对于该方法到底是起到一个什么作用疑惑.我就是想压缩后按我给的路径生成文件,希望能给出具体的解决方案.

    opened by BloodBamboo 6
  • 小公告

    小公告

    1、我最近在找工作,Luban 也是这两天突然爆发起来,如果有问题请各位包涵 2、图片压缩是安卓开发的刚需,我希望能靠大家一起讨论完善 Luban 3、如果想把 Luban 用上线上产品的大大请直接用源码,并针对自家产品优化,不要因为 Luban 而对产品造成影响 4、我会不断花时间优化 Luban 项目,希望大家多多支持 5、想加入讨论的大家是希望建Q群,微信群,还是继续在 issues 中讨论,可以在本 issues 下发表意见

    opened by Curzibn 6
  • 好像是导致不清晰的原因

    好像是导致不清晰的原因

    213, 214 行 width 和 height 转变成了 最短边 和 最长边

            width = thumbW > thumbH ? thumbH : thumbW;
            height = thumbW > thumbH ? thumbW : thumbH;
    

    可是后面有用它来当做宽高来计算 (取名字 多么重要)

       thumbW = width / 2;
       thumbH = height / 2;
    
       thumbW = width / 4;
        thumbH = height / 4;
    

    是不是想写 ?

    thumbW/=2;
    
    opened by u787i 5
  • 循环压缩图片 可能会把压缩的图片输出在相同的文件地址导致覆盖

    循环压缩图片 可能会把压缩的图片输出在相同的文件地址导致覆盖

    rt,我是这么循环压缩的

    for(String fileUrl : fileList){
                Luban.get(this).putGear(Luban.THIRD_GEAR)
                .load(new File(fileUrl))
    //            .setFilename(getCurrentPhotoFile().getAbsolutePath())
                .setCompressListener(new OnCompressListener() {
    
                    @Override
                    public void onSuccess(File file) {
                        DLog.i("完成剪裁:" + file.getAbsolutePath());
                        mCompressList.add(file.getAbsolutePath());
                        if(mCompressList.size() == fileList.size()){
                            uploadFile(mCompressList);
                        }
                    }
    
                    @Override
                    public void onStart() {
                        DLog.i("开始剪裁");
                    }
    
                    @Override
                    public void onError(Throwable e) {
                        DLog.i("error:" + e.toString());
                    }
                }).launch();
    

    结果发现剪裁完的地址可能会出现相同的结果。

    opened by Baifann 5
  • java.lang.NoClassDefFoundError: Failed resolution of: Lrx/Observable;

    java.lang.NoClassDefFoundError: Failed resolution of: Lrx/Observable;

    public void dealImg(ArrayList fileList){ files.clear(); for (int i = 0; i < fileList.size(); i++) { File file = fileList.get(i); Luban.get(this) .load(file) //传人要压缩的图片 .putGear(Luban.THIRD_GEAR) //设定压缩档次,默认三挡 .setCompressListener(new OnCompressListener() { @Override public void onStart() {

                        }
    
                        @Override
                        public void onSuccess(File file) {
                            files.add(file);
                        }
    
                        public void onError(Throwable e) {
    
                        }
                    }).launch();    //启动压缩
        }
    }
    

    java.lang.NoClassDefFoundError: Failed resolution of: Lrx/Observable; at top.zibin.luban.Luban.launch(Luban.java:113) at com.xinji.jugongyouyue.workeractivity.WorkerActivityAddProject.dealImg(WorkerActivityAddProject.java:304) at com.xinji.jugongyouyue.workeractivity.WorkerActivityAddProject.onActivityResult(WorkerActivityAddProject.java:296) at android.app.Activity.dispatchActivityResult(Activity.java:6378) at android.app.ActivityThread.deliverResults(ActivityThread.java:3970) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4017) at android.app.ActivityThread.access$1300(ActivityThread.java:178) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602) at android.os.Handler.dispatchMessage(Handler.java:111) at android.os.Looper.loop(Looper.java:192) at android.app.ActivityThread.main(ActivityThread.java:5784) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1084) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:850) Caused by: java.lang.ClassNotFoundException: Didn't find class "rx.Observable" on path: DexPathList[[zip file "/data/app/com.xinji.jugongyouyue-1/base.apk"],nativeLibraryDirectories=[/data/app/com.xinji.jugongyouyue-1/lib/arm, /vendor/lib, /system/lib]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) at java.lang.ClassLoader.loadClass(ClassLoader.java:511) at java.lang.ClassLoader.loadClass(ClassLoader.java:469) at top.zibin.luban.Luban.launch(Luban.java:113)  at com.xinji.jugongyouyue.workeractivity.WorkerActivityAddProject.dealImg(WorkerActivityAddProject.java:304)  at com.xinji.jugongyouyue.workeractivity.WorkerActivityAddProject.onActivityResult(WorkerActivityAddProject.java:296)  at android.app.Activity.dispatchActivityResult(Activity.java:6378)  at android.app.ActivityThread.deliverResults(ActivityThread.java:3970)  at android.app.ActivityThread.handleSendResult(ActivityThread.java:4017)  at android.app.ActivityThread.access$1300(ActivityThread.java:178)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1602)  at android.os.Handler.dispatchMessage(Handler.java:111)  at android.os.Looper.loop(Looper.java:192)  at android.app.ActivityThread.main(ActivityThread.java:5784)  at java.lang.reflect.Method.invoke(Native Method)  at java.lang.reflect.Method.invoke(Method.java:372)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1084)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:850)  Suppressed: java.lang.ClassNotFoundException: rx.Observable at java.lang.Class.classForName(Native Method) at java.lang.BootClassLoader.findClass(ClassLoader.java:781) at java.lang.BootClassLoader.loadClass(ClassLoader.java:841) at java.lang.ClassLoader.loadClass(ClassLoader.java:504) ... 16 more

    opened by andansky 5
  • Engine.compress(Engine.java:79) this line throws java.lang.NullPointerException,please fixed.thx

    Engine.compress(Engine.java:79) this line throws java.lang.NullPointerException,please fixed.thx

    日志如下:

    Attempt to invoke virtual method 'boolean android.graphics.Bitmap.compress(android.graphics.Bitmap$CompressFormat, int, java.io.OutputStream)' on a null object reference

    top.zibin.luban.Engine.compress(Engine.java:79)

    opened by steven-gao 4
  • java.lang.NoSuchFieldException

    java.lang.NoSuchFieldException

    e.e.d.b.an.a(UnsafeAccess.java:109) 3 ...... 4 Caused by: 5 java.lang.NoSuchFieldException:producerIndex 6 java.lang.Class.getDeclaredField(Class.java:596) 7 e.e.d.b.an.a(UnsafeAccess.java:106) 8 e.e.d.b.n.(MpmcArrayQueue.java:31) 9 e.e.d.j.a(ObjectPool.java:153) 10 e.e.d.j.(ObjectPool.java:57) 11 e.e.d.j.(ObjectPool.java:36) 12 e.e.d.o$1.(RxRingBuffer.java:280) 13 e.e.d.o.(RxRingBuffer.java:280) 14 e.d.a(Observable.java:6394) 15 top.zibin.luban.b.a(Luban.java:135)

    是什么问题啊?

    opened by shenminjie 4
  • 压缩时一直loading,且有不小的概率会Crash

    压缩时一直loading,且有不小的概率会Crash

    华为mate30Pro,鸿蒙系统 v3.10.6 已经设置targetSdk = 29和android:requestLegacyExternalStorage="true"

    public class ImageCompressEngine implements CompressFileEngine {
    
        @Override
        public void onStartCompress(Context context, ArrayList<Uri> source, OnKeyValueResultCallbackListener call) {
            Luban.with(context).load(source).ignoreBy(500)
                    .filter(path -> PictureMimeType.isUrlHasImage(path) && !PictureMimeType.isHasHttp(path))
                    .setCompressListener(new OnNewCompressListener() {
                        @Override
                        public void onStart() {
    
                        }
    
                        @Override
                        public void onSuccess(String source, File compressFile) {
                            LogUtils.i("localMediaCompress",source);
                            if (call != null) {
                                call.onCallback(source, compressFile.getAbsolutePath());
                            }
                        }
    
                        @Override
                        public void onError(String source, Throwable e) {
                            LogUtils.i("localMediaCompress", source);
                            LogUtils.i("localMediaCompress", e.getMessage());
                            if (call != null) {
                                call.onCallback(source, null);
                            }
                        }
                    }).launch();
        }
    }
    

    稍微大点的图片,2-3M的,手机拍照的,就会loading很长时间,一张差不多2分钟左右吧,且大几率会crash

    opened by jerry9433 3
  • android.graphics.Bitmap.compres()方法在安卓11上代码执行报错

    android.graphics.Bitmap.compres()方法在安卓11上代码执行报错

    执行代码:Luban.with(context).load(fileList).get(); 运行设备: 荣耀magic ui 安卓11设备上报错, 小米安卓12上代码正常执行 报错信息: Attempt to invoke virtual method 'boolean android.graphics.Bitmap.compress(android.graphics.Bitmap$CompressFormat, int, java.io.OutputStream)' on a null object reference

    opened by pigMan15 1
  • android Q适配

    android Q适配

    android Q上若传文件Uri,使用Uri类型的InputStreamProvider

    new InputStreamProvider() {
    @Override
                    public InputStream open() throws IOException {
                        return context.getContentResolver().openInputStream(uri);
                    }
    
                    @Override
                    public String getPath() {
                        return uri.getPath();
                    }
    
                    @Override
                    public boolean isUri() {
                        return true;
                    }
    
                    @Override
                    public long getLength() {
                        try {
                            switch (uri.getScheme()) {
                                case ContentResolver.SCHEME_FILE:
                                    return new File(uri.getPath()).length();
                                case ContentResolver.SCHEME_CONTENT:
                                    Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
                                    long tempLength = 0;
                                    if (cursor != null && cursor.moveToFirst()) {
                                        tempLength = new File(cursor.getString(cursor.getColumnIndex("_data"))).length();
                                        cursor.close();
                                    }
                                    return tempLength;
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return 0;
                    }
                }
    

    在原Luban compress方法中

    private File compress(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) {
                if (mCompressionPredicate.apply(path.getPath())
                        && Checker.SINGLE.needCompress(mLeastCompressSize, path)) {
                    result = new Engine(path, outFile, focusAlpha).compress();
                } else {
                    result = new File(path.getPath());
                }
            } else {
                result = Checker.SINGLE.needCompress(mLeastCompressSize, path) ?
                        new Engine(path, outFile, focusAlpha).compress() :
                        new File(path.getPath());
            }
    
            return result;
        }
    

    若Checker.SINGLE.needCompress()返回false,直接返回uri.getPath() = eg:/external/images/media/1947 这个路径是公共目录,不能直接通过uri访问 但通过根据InputStreamProvider的open方法返回的InputStream,可以获取到源文件数据,copy到私有目录即可

    private File compress(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) {
                if (mCompressionPredicate.apply(path.getPath())
                        && Checker.SINGLE.needCompress(mLeastCompressSize, path)) {
                    result = new Engine(path, outFile, focusAlpha).compress();
                } else {
                    // android Q后拷贝文件到私有目录
                    result = path.isUri() ? new Mold(path, outFile).copy() : new File(path.getPath());
                }
            } else {
                result = Checker.SINGLE.needCompress(mLeastCompressSize, path) ?
                        new Engine(path, outFile, focusAlpha).compress() :
                        // android Q后拷贝文件到私有目录
                        (path.isUri() ? new Mold(path, outFile).copy() : new File(path.getPath()));
            }
    
            return result;
        }
    

    /**

    • copy file */
    class Mold {
        private InputStreamProvider srcImg;
        private File tagImg;
    
        Mold(InputStreamProvider srcImg, File tagImg) {
            this.tagImg = tagImg;
            this.srcImg = srcImg;
        }
    
        File copy() throws IOException {
            InputStream inputStream = srcImg.open();
            FileOutputStream fos = new FileOutputStream(tagImg);
            byte[] b = new byte[1024];
            while ((inputStream.read(b)) != -1) {
                fos.write(b);// 写入数据
            }
            inputStream.close();
            fos.flush();
            fos.close();
            return tagImg;
        }
    }
    
    opened by yongshengdev 0
  • Improve GRADLE build Performance

    Improve GRADLE build Performance

    Parallel test execution maxParallelForks. Gradle can run multiple test cases in parallel by setting maxParallelForks.

    Disable report generation. We can conditionally disable it by setting reports.html.required = false; reports.junitXml.required = false. If you need to generate reports, add -PcreateReports to the end of Gradle's build command line.

    ===================== If there are any inappropriate modifications in this PR, please give me a reply and I will change them.

    opened by shisheng-1 0
Releases(turbo-1.0.0)
Owner
郑梓斌
Android developer
郑梓斌
An image loading and caching library for Android focused on smooth scrolling

Glide | View Glide's documentation | 简体中文文档 | Report an issue with Glide Glide is a fast and efficient open source media management and image loading

Bump Technologies 33.2k Jan 7, 2023
Android Asynchronous Networking and Image Loading

Android Asynchronous Networking and Image Loading Download Maven Git Features Kotlin coroutine/suspend support Asynchronously download: Images into Im

Koushik Dutta 6.3k Dec 27, 2022
A powerful image downloading and caching library for Android

Picasso A powerful image downloading and caching library for Android For more information please see the website Download Download the latest AAR from

Square 18.4k Jan 6, 2023
Image loading for Android backed by Kotlin Coroutines.

An image loading library for Android backed by Kotlin Coroutines. Coil is: Fast: Coil performs a number of optimizations including memory and disk cac

Coil 8.8k Jan 8, 2023
An Android transformation library providing a variety of image transformations for Glide.

Glide Transformations An Android transformation library providing a variety of image transformations for Glide. Please feel free to use this. Are you

Daichi Furiya 9.7k Dec 30, 2022
An Android transformation library providing a variety of image transformations for Picasso

Picasso Transformations An Android transformation library providing a variety of image transformations for Picasso. Please feel free to use this. Are

Daichi Furiya 1.7k Jan 5, 2023
Library to handle asynchronous image loading on Android.

WebImageLoader WebImageLoader is a library designed to take to hassle out of handling images on the web. It has the following features: Images are dow

Alexander Blom 102 Dec 22, 2022
🍂 Jetpack Compose image loading library which can fetch and display network images using Glide, Coil, and Fresco.

?? Jetpack Compose image loading library which can fetch and display network images using Glide, Coil, and Fresco.

Jaewoong Eum 1.4k Jan 2, 2023
Image Picker for Android 🤖

Image Picker for Android ??

Esa Firman 1k Dec 31, 2022
ZoomableComposeImage - A zoomable image for jetpack compose

ZoomableComposeImage - A zoomable image for jetpack compose

RERERE 10 Dec 11, 2022
ComposeImageBlurhash is a Jetpack Compose component with the necessary implementation to display a blurred image

compose-image-blurhash ComposeImageBlurhash is a Jetpack Compose component with the necessary implementation to display a blurred image while the real

Orlando Novas Rodriguez 24 Nov 18, 2022
Easy to use, lightweight custom image view with rounded corners.

RoundedImageView Easy to use, lightweight custom image view with rounded corners. Explore the docs » View Demo · Report Bug · Request Feature About Th

Melik Mehmet Özyildirim 6 Dec 23, 2021
load-the-image Apply to compose-jb(desktop), Used to load network and local pictures.

load-the-image load-the-image Apply to compose-jb(desktop), Used to load network and local pictures. ?? Under construction It may change incompatibly

lt_taozi 13 Dec 29, 2022
Compose Image library for Kotlin Multiplatform.

Compose ImageLoader Compose Image library for Kotlin Multiplatform. Setup Add the dependency in your common module's commonMain sourceSet kotlin {

Seiko 45 Dec 29, 2022
An android image compression library.

Compressor Compressor is a lightweight and powerful android image compression library. Compressor will allow you to compress large photos into smaller

Zetra 6.7k Dec 31, 2022
An android image compression library.

Compressor Compressor is a lightweight and powerful android image compression library. Compressor will allow you to compress large photos into smaller

Zetra 6.7k Jan 9, 2023
Big image viewer supporting pan and zoom, with very little memory usage and full featured image loading choices. Powered by Subsampling Scale Image View, Fresco, Glide, and Picasso. Even with gif and webp support! 🍻

BigImageViewer Big image viewer supporting pan and zoom, with very little memory usage and full featured image loading choices. Powered by Subsampling

Piasy 3.9k Dec 30, 2022
Notes is a simple and private notes app. Organize your thoughts, discoveries, and ideas and simplify planning important moments in your life with your digital notepad.

Notes Example Download Download the latest version of the Android app from this link. Building Using Android Studio Clone the repo, open it in Android

Dmitry Savin 1 Jan 3, 2022
A High Copy WeChat ,SNS APP (高仿微信)

WeChat Social apps are popular all over the world, such as Facebook, Line, Whatsapp, and Kakao. Do you still worry about their own social project modu

Juns Allen 5.2k Dec 29, 2022
proguard resource for Android by wechat team

AndResGuard Read this in other languages: English, 简体中文. AndResGuard is a tooling for reducing your apk size, it works like the ProGuard for Java sour

shwenzhang 8.1k Jan 9, 2023