使用TinyPng来压缩图片

来源:互联网 发布:有什么好的优化软件 编辑:程序博客网 时间:2024/04/28 10:01

图片资源在软件安装包中所占的分量很重,减少图片大小,安装包的体积改变立竿见影。

怎么压图呢?自己对图像处理也没有研究啊,只能捡现成的。网上一搜,压图工具很多,但测试之后,恕我直言,很多都是垃圾,有的压图之后图片质量下降严重,有的压图之后图片体积减小微乎其微,更有甚者,能把图片越压越大的。

TinyPng(https://tinypng.com/)是个好同志,TinyPng能够对PNG和JPG/JPEG格式图片进行有效压缩,图片体积大幅减小,可达六七成,并且失真较小。


TinyPng并不是一个本地软件,在其官网可直接进行图片压缩操作,然而,对于成百上千的图片,在官网操作不太方便,因此好的选择是利用其提供的API开发一个图片压缩工具。

TinyPng提供免费服务和收费服务,免费帐号一个月只能压缩500张图片,帐号申请很简单,不详述。

使用其API,通过java开发一个压图工具,可以为该工具配置一堆的TinyPng帐号,免费帐号或收费帐号都可,工具自动检查帐号的可用性,跟进帐号使用情况,剔除无效帐号,总是使用有效的帐号来操作。因此,如果为该工具配置多个免费帐号,则可认为可以不受限制地使用TinyPng进行图片压缩了。


工具图:


压缩率很不错:



核心代码1(TinyPngKey),定义了每个TinyPng帐号对应的key,帐号类型等:

package com.dancen.ant.png.domain;/** *  * @author dancen * */public class TinyPngKey{private String key;private String account;private boolean isFree;public TinyPngKey(String key, String account, boolean isFree){this.setKey(key);this.setAccount(account);this.setFree(isFree);}public String getKey(){return this.key;}public String getAccount(){return this.account;}public boolean isFree(){return this.isFree;}private void setKey(String key){if(null == key || key.isEmpty()){throw new IllegalArgumentException("the key is null");}this.key = key;}private void setAccount(String account){this.account = account;}private void setFree(boolean isFree){this.isFree = isFree;}@Overridepublic int hashCode(){return this.key.hashCode();}@Overridepublic boolean equals(Object obj){boolean rs = false;if(obj == this){rs = true;}else if(null != obj && obj instanceof TinyPngKey){TinyPngKey tinyPngKey = (TinyPngKey)obj;rs = this.key.equals(tinyPngKey.getKey());}return rs;}}


核心代码2(TinyPng),包含TinyPng的每一个帐号信息可帐号的压图数目:

package com.dancen.ant.png.domain;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;import java.util.Map.Entry;import com.dancen.ant.config.ConfigWriterForPng;/** *  * @author dancen * */public class TinyPng{public static final String API_URL = "https://api.tinify.com/shrink";public static final int MAX_COUNT_PER_MONTH = 500;private String apiUrl;private int maxCountPerMonth;private Map<TinyPngKey, Integer> keyMap;//key以及当月使用次数public TinyPng(String apiUrl, int maxCountPerMonth, Map<TinyPngKey, Integer> keyMap){this.setApiUrl(apiUrl);this.setMaxCountPerMonth(maxCountPerMonth);this.setKeyMap(keyMap);}public String getApiUrl(){return this.apiUrl;}public int getMaxCountPerMonth(){return this.maxCountPerMonth;}public synchronized Map<TinyPngKey, Integer> getKeyMap(){return new HashMap<TinyPngKey, Integer>(this.keyMap);}public synchronized void updateKeyMap(TinyPngKey key, int count){if(null != key){this.keyMap.put(key, count);}}public synchronized void addCount(TinyPngKey key, int add){if(null != key){Integer temp = this.keyMap.get(key);this.keyMap.put(key, null == temp ? add : add + temp);}}/** * 获取可用的key * @param toDoCount 将要使用key的次数 * @return */public synchronized TinyPngKey getKey(int toDoCount){TinyPngKey rs = null;for(Entry<TinyPngKey, Integer> entry : this.keyMap.entrySet()){TinyPngKey key = entry.getKey();Integer temp = entry.getValue();int count = null == temp ? 0 : temp;if(count + toDoCount <= this.maxCountPerMonth || !key.isFree()){rs = key;}}return rs;}public void dispose(){ConfigWriterForPng.getInstance().saveKeyCountMap(this.keyMap);}private void setApiUrl(String apiUrl){if(null == apiUrl || apiUrl.isEmpty()){apiUrl = API_URL;}this.apiUrl = apiUrl;}private void setMaxCountPerMonth(int maxCountPerMonth){if(0 >= maxCountPerMonth){maxCountPerMonth = MAX_COUNT_PER_MONTH;}this.maxCountPerMonth = maxCountPerMonth;}private void setKeyMap(Map<TinyPngKey, Integer> keyMap){this.keyMap = new LinkedHashMap<TinyPngKey, Integer>();this.keyMap.putAll(keyMap);}}


核心代码3(PngCompressRunnable)图片压缩工作线程:

package com.dancen.ant.png.handler;import java.io.InputStream;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.concurrent.CountDownLatch;import javax.xml.bind.DatatypeConverter;import com.dancen.ant.file.domain.FileItem;import com.dancen.ant.gui.common.FileItemPanel;import com.dancen.ant.png.CopyHelper;import com.dancen.ant.png.ProgressUpdater;import com.dancen.ant.png.domain.PngResult;import com.dancen.ant.png.domain.TinyPng;import com.dancen.ant.png.domain.TinyPngKey;import com.dancen.util.MyLogManager;/** *  * @author dancen * */public class PngCompressRunnable extends PngRunnable{private TinyPng tinyPng;public PngCompressRunnable(TinyPng tinyPng, FileItemPanel fileItemPanel, CountDownLatch countDownLatch){super(fileItemPanel, countDownLatch);this.setTinyPng(tinyPng);}public TinyPng getTinyPng(){return this.tinyPng;}@Overridepublic void run(){TinyPngKey tinyPngKey = this.tinyPng.getKey(1);FileItem fileItem = this.fileItemPanel.getFileItem();String inputPath = fileItem.getLocalPath();String outputPath = fileItem.getExtPath();ProgressUpdater progressUpdater = new PngProgressUpdater(this.fileItemPanel);    CopyHelper copyHelper = new CopyHelper();    copyHelper.setProgressUpdater(progressUpdater);    try{if(null == tinyPngKey){progressUpdater.setIndeterminate(false);progressUpdater.setText("TinyPngKey depleted");this.rs = PngResult.createFailureResult("no tinyPngKey is available");}else{progressUpdater.setIndeterminate(false);progressUpdater.setText("正在上传 ...");this.tinyPng.addCount(tinyPngKey, 1);HttpURLConnection connection = (HttpURLConnection) new URL(this.tinyPng.getApiUrl()).openConnection();String auth = DatatypeConverter.printBase64Binary(("api:" + tinyPngKey.getKey()).getBytes("UTF-8"));    connection.setRequestProperty("Authorization", "Basic " + auth);    connection.setDoOutput(true);    connection.setConnectTimeout(10000);connection.setReadTimeout(50000);    OutputStream requestOutputStream = connection.getOutputStream();    copyHelper.copy(inputPath, requestOutputStream);    progressUpdater.setText("上传完成");    progressUpdater.setIndeterminate(true);    progressUpdater.setText("正在压缩 ...");    int code = connection.getResponseCode();        if(201 == code)    {    progressUpdater.setText("压缩完成,准备下载 ...");String url = connection.getHeaderField("location");connection = (HttpURLConnection) new URL(url).openConnection();connection.setConnectTimeout(10000);connection.setReadTimeout(30000);InputStream responseInputStream = connection.getInputStream();progressUpdater.setIndeterminate(false);    progressUpdater.setText("正在下载 ...");copyHelper.copy(responseInputStream, outputPath, connection.getContentLength(), true);progressUpdater.setText("下载完成");this.rs = PngResult.createSuccessResult(String.format("Compress %s > %s successful", inputPath, outputPath));MyLogManager.getInstance().info(this.rs.getInfo());}    else    {    progressUpdater.setIndeterminate(false);    progressUpdater.setText("压缩失败:" + code);    this.rs = PngResult.createFailureResult(String.format("Compress %s > %s failed, errorCode = %s", inputPath, outputPath, code));    MyLogManager.getInstance().warn(this.rs.getInfo());        if(429 == code)    {    this.tinyPng.addCount(tinyPngKey, this.tinyPng.getMaxCountPerMonth());    }    }}}catch(Exception e){e.printStackTrace();MyLogManager.getInstance().error(String.format("Compress %s > %s failed", inputPath, outputPath), e);progressUpdater.setIndeterminate(false);progressUpdater.setText("发生异常:" + e.getClass().getSimpleName());this.rs = PngResult.createFailureResult(e.getMessage());}finally{this.countDownLatch.countDown();}}private void setTinyPng(TinyPng tinyPng){if(null == tinyPng){throw new IllegalArgumentException("the tinyPng is null");}this.tinyPng = tinyPng;}}

我申请了七八个免费帐号,能够满足使用需求了:


0 0