使用TinyPng来压缩图片

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

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

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;
	}
	
	@Override
	public int hashCode()
	{
		return this.key.hashCode();
	}
	
	@Override
	public 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;
	}
	
	@Override
	public 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;
	}
}

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


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值