场景:
在微信小程序环境下,用户上传个人头像,生成个人的微信小程序菊花维码,可以无限申请并生成二维码,然后二维码中间的圆形为用户自己上传的头像,最后可以通过生成的二维码扫码进入个人简介页面。
因为图片捣腾了一些时间,记录一下,类似问题给的时间都是墨鱼时间,hh
代码部分仅,怎么替换微信二维码中间的图片为用户上传的图片。
这块个人第一次接触,最头疼的是图片缩放失真问题,而且网上查阅了很多文档,最终缩放部分通过Thumbnails解决。相信这部分代码也能解决各位类似的需求
思路: 微信接口返回的二维码作为底图,然后将用户上传的图片裁剪成圆形,放到二维码的中间,将微信给的二维码中间的图片挡住。
会存在的问题:
主要难点:
<dependency>
<groupId>net.coobirdgroupId>
<artifactId>thumbnailatorartifactId>
<version>0.4.11version>
dependency>
// 请求及返回就无所谓了 这边我用的ruoyi的
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.http.HttpUtils;
import com.alibaba.fastjson2.JSON;
import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.geometry.Positions;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;
@RestController
@RequestMapping("/api/wx/access/")
public class ApiWxAccess {
@GetMapping("/token")
public AjaxResult getToken() {
String clientCredential = "xx";
String appid = "xx";
String secret = "xx";
String response = HttpUtils.sendGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=" +
clientCredential + "&appid=" + appid +
"&secret=" + secret);
return AjaxResult.success(response);
}
/**
* 调用微信二维码接口并处理返回
* @param paramsMap 具体参数下面前端中有体现
* @return
* @throws Exception
*/
@PostMapping("/code")
public AjaxResult getWxaCodeUnLimit(@RequestBody Map<String, Object> paramsMap) throws Exception {
// 拼装url
String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + paramsMap.get("token");
String jsonString = JSON.toJSONString(paramsMap.get("params"));
// 调用微信小程序二维码接口,传递接口中需要的参数,并返回为ByteArrayInputStream格式的数据
ByteArrayInputStream inputStream = this.sendPost(url, jsonString);
// ByteArrayInputStream转BufferedImage
BufferedImage wxQrImg = ImageIO.read(inputStream);
// 用户logo
BufferedImage logoImage = ImageIO.read(new URL(paramsMap.get("img").toString()));
// 这里可以随便找个外链图片测试
// BufferedImage logoImage = ImageIO.read(new URL("imgUrl"));
// logo图的宽高
int width = logoImage.getWidth();
int height = logoImage.getHeight();
// 保存正方形的边长
int size = 0;
// 判断那条边的边更长
if (width > height) {
size = height;
} else {
size = width;
}
// 裁剪:获取正中间的正方形,边长为图片宽的值 后面.size方法必须调用 否则异常
logoImage = Thumbnails.of(logoImage).sourceRegion(Positions.CENTER, size, size).size(size, size).asBufferedImage();
// 转成圆形
logoImage = convertCircular(logoImage);
// 缩放:放大微信二维码的底图 目的为了减少对用户上传的图片缩放过小图片失真
wxQrImg = Thumbnails.of(wxQrImg).size(wxQrImg.getHeight() * 2, wxQrImg.getHeight() * 2).asBufferedImage();
// 使用Graphics2D合并图片
Graphics2D g2 = null;
// 读取微信二维码图片
g2 = wxQrImg.createGraphics();
// 合并:并设置偏移量,logo图片大小。具体需要自己按照实际的大小调整
g2.drawImage(logoImage, 177, 177, 290, 290, null);
g2.dispose();
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(wxQrImg, "png", os);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(os.toByteArray());
//返回web前端,这里返回的是base64
byte[] buff = new byte[byteArrayInputStream.available() + 1];
byteArrayInputStream.read(buff, 0, byteArrayInputStream.available());
return AjaxResult.success(buff);
}
/**
* 转成圆形
*
* @param bufferedImage
* @return
* @throws IOException
*/
public static BufferedImage convertCircular(BufferedImage bufferedImage) throws IOException {
//透明底的图片
BufferedImage newbufferedImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Ellipse2D.Double shape = new Ellipse2D.Double(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
Graphics2D g2d = newbufferedImage.createGraphics();
//增加背景透明
newbufferedImage = g2d.getDeviceConfiguration().createCompatibleImage(bufferedImage.getWidth(), bufferedImage.getHeight(), Transparency.TRANSLUCENT);
g2d.dispose();
g2d = newbufferedImage.createGraphics();
//背景透明end
// 使用 setRenderingHint 设置抗锯齿
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setClip(shape);
g2d.drawImage(bufferedImage, 0, 0, null);
// 设置颜色
g2d.setBackground(Color.green);
g2d.dispose();
return newbufferedImage;
}
/**
* 发送http请求访问微信小程序接口获得输入流
*
* @param URL
* @param json
* @return
*/
public ByteArrayInputStream sendPost(String URL, String json) {
InputStream inputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
// 创建默认的httpClient实例.
CloseableHttpClient httpclient = HttpClients.createDefault();
// 创建httppost
HttpPost httppost = new HttpPost(URL);
httppost.addHeader("Content-type", "application/json; charset=utf-8");
httppost.setHeader("Accept", "application/json");
try {
StringEntity s = new StringEntity(json, Charset.forName("UTF-8"));
s.setContentEncoding("UTF-8");
httppost.setEntity(s);
HttpResponse response = httpclient.execute(httppost);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 获取相应实体
HttpEntity entity = response.getEntity();
inputStream = entity.getContent();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
// 创建一个Buffer字符串
byte[] buffer = new byte[1024];
// 每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
// 使用一个输入流从buffer里把数据读取出来
while ((len = inputStream.read(buffer)) != -1) {
// 用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
outStream.write(buffer, 0, len);
}
// 关闭输入流
inputStream.close();
// 把outStream里的数据写入内存
byteArrayInputStream = new ByteArrayInputStream(outStream.toByteArray());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭连接,释放资源
try {
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return byteArrayInputStream;
}
}
拿到接口中返回的流,拼接data:image/png;base64,
,然后渲染即可
<!-- vue渲染 -->
<image :src="img">
<!-- js -->
await uni.request({
url:'https://xxx.xxx.com/wx/access/code',
method:'POST',
data: {
token:this.token,
img:this.img,
params: {
scene:this.id,
page: 'pages/show/index',
width: 320,
// 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
auto_color: false,
line_color: {
"r": "247",
"g": "185",
"b": "55"
}
}
},
}).then(res=>{
this.img= "data:image/png;base64," + 接口返回的图片流; //对数据进行转换操作
})
希望能解决你的问题