1.直接上代码,首先创建一个PuzzleHelper 拼图助手方便生成滑块
说明:
1.滑块的背景图在资源库中的图片随机。
2.滑块的边缘凹凸随机。
3.滑块的位置随机。
你可能会出现的问题:
1.图片转BASE64我用的工具类,你需要自己去网上找一个。
2.你可能报错Can’t read input file!,是因为图片的地址不正确,所以找不到图片。
import com.ruoyi.common.utils.sign.Base64;
import com.ruoyi.model.PuzzleInfo;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;
public class PuzzleHelper {
//大图宽度
private static final Integer bigWidth = 4000;
//大图高度
private static final Integer bigHeight = 2000;
//边距(上、右、下小图距离大图最近距离,左边需要留大点边距方便拖动),同时也是内凹点和外凸点到正方形的边距
private static final Integer margin = 100;
//小正方形边长,制作拼图的轮廓
private static final Integer square = 400;
//小圆半径,拼图的凹凸轮廓
private static final Integer circle = 80;
//阴影宽度
private static final Integer shadow=10;
//图片压缩倍数
private static final Integer scale=10;
/**
* @Title: getImageBASE64
* @Description: 图片转BASE64
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
byte[] imagedata = null;
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ImageIO.write(image,"png",bao);
imagedata=bao.toByteArray();
String BASE64IMAGE = Base64.encode(imagedata).trim();
BASE64IMAGE = BASE64IMAGE.replaceAll("\r|\n", ""); //删除 \r\n
return BASE64IMAGE;
}
/**
* 改变图片大小
*
* @param image 原图
* @param width 目标宽度
* @param height 目标高度
* @return 目标图
*/
public static BufferedImage resizeImage(final Image image, int width, int height,boolean type) {
BufferedImage bufferedImage;
if (type){
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}
else {
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
final Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setComposite(AlphaComposite.Src);
//below three lines are for RenderingHints for better image quality at cost of higher processing time
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.drawImage(image, 0, 0, width, height, null);
graphics2D.dispose();
return bufferedImage;
}
/**
* 随机正方形坐标和边缘凹凸信息
*/
public static Integer[] getPosAndOutline(){
//前端将图约束在300*150的框内,所以计算时除以10防止坐标约束时(除以10)出现小数
//最小高度,只需排除边距加外凸
Integer minY = (2*margin)/10;
//最大高度,排除边距,正方形边长,上下两个外凸(极限)情况下,增加的高度。
Integer maxY = (bigHeight - (square + 3 * margin))/10;
//最小宽度,左边留一个半身位方便移动,身位宽度仍是极限(正方形边长+左右两个外凸)
Integer minX = ((square + margin * 2) * 3 / 2)/10;
//最大宽度,排除边距,正方形边长,左右两个外凸。
Integer maxX = (bigWidth - (square + 3 * margin))/10;
//获取正方形的位置,乘以10计算时保持正常大小,传给前端时需约束处理(除以10)
Integer y = (minY + (int) (Math.random() * (maxY + 1 - minY)))*10;
Integer x = (minX + (int) (Math.random() * (maxX + 1 - minX)))*10;
Integer[] p=new Integer[10];
//正方形x坐标
p[0]=x;
//正方形y坐标
p[1]=y;
//获取边缘凹凸信息
//0没有 1内凹圆弧 2外凸圆弧
Integer top = new Random().nextInt(3);
Integer left = new Random().nextInt(3);
Integer down = new Random().nextInt(3);
Integer right = new Random().nextInt(3);
//上方凹凸信息
p[2]=top;
//左侧凹凸信息
p[3]=left;
//下方凹凸信息
p[4]=down;
//右侧凹凸信息
p[5]=right;
return p;
}
/**
* 获取小图轮廓位置信息,值为1的地方是小图需要从原图取色,大图需要在原图基础上模糊的位置
*/
public static Integer[][] getSmallImg(Integer[] pos) {
//正方形x坐标
Integer x=pos[0];
//正方形y坐标
Integer y=pos[1];
//小图x坐标(小图没有左侧外凸时x坐标与正方形一致)
Integer posX=null==pos[8]?pos[0]:pos[8];
//小图y坐标(小图没有上方外凸时y坐标与正方形一致)
Integer posY=null==pos[9]?pos[1]:pos[9];
//0没有 1内凹圆弧 2外凸圆弧
Integer top = pos[2];
Integer left =pos[3];
Integer down = pos[4];
Integer right = pos[5];
//取色模板,值为1的地方是小图需要从原图取色,大图需要在原图基础上模糊的位置
Integer[][] templateImage = new Integer[bigHeight][bigWidth];
double rPow = Math.pow(circle, 2);
//模板只需要计算小图范围,其他地方不用管,阴影范围需要从小图边界向外延申shadow长度作为参照
for (int i = posY-shadow; i <= posY+pos[7]+shadow; i++) {
for (int j = posX-shadow; j <= posX+pos[6]+shadow; j++) {
//先判断正方形
if ((i >= y && i <= (y + square) && j >= x && j <= (x + square))) {
templateImage[i][j] = 1;
} else {
templateImage[i][j] = 0;
}
//不在上方圆内
if (top == 1) {
Integer tx = x + square / 2;
Integer ty = y + (margin - circle);
//当前点在上方圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 0;
}
}
//在上方圆内
if (top == 2) {
Integer tx = x + square / 2;
Integer ty = y - (margin - circle);
//当前点在上方圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内补上
if (res <= rPow) {
templateImage[i][j] = 1;
}
}
//不在左侧圆内
if (left == 1) {
Integer tx = x + (margin - circle);
Integer ty = y + square / 2;
//当前点在左侧圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 0;
}
}
//在左侧圆内
if (left == 2) {
Integer tx = x - (margin - circle);
Integer ty = y + square / 2;
//当前点在左侧圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 1;
}
}
//不在下方圆内
if (down == 1) {
Integer tx = x + square / 2;
Integer ty = y + square - (margin - circle);
//当前点在左侧圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 0;
}
}
//在下方圆内
if (down == 2) {
Integer tx = x + square / 2;
Integer ty = y + square + (margin - circle);
//当前点在左侧圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 1;
}
}
//不在右侧圆内
if (right == 1) {
Integer tx = x + square - (margin - circle);
Integer ty = y + square / 2;
//当前点在左侧圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 0;
}
}
//在右侧圆内
if (right == 2) {
Integer tx = x + square + (margin - circle);
Integer ty = y + square / 2;
//当前点在左侧圆形的位置内外
double res = Math.pow(j - tx, 2) + Math.pow(i - ty, 2);
//在圆内挖掉
if (res <= rPow) {
templateImage[i][j] = 1;
}
}
}
}
return templateImage;
}
/**
* 从原图中裁剪拼图
* @param oriImage
* @param targetImage
* @param templateImage
* @param pos
*/
private static void cutByTemplate(BufferedImage oriImage, BufferedImage targetImage, Integer[][] templateImage,Integer[] pos) {
int[][] martrix = new int[3][3];
int[] values = new int[9];
//小图x坐标(小图没有左侧外凸时x坐标与正方形一致)
Integer posX=null==pos[8]?pos[0]:pos[8];
Integer posY=null==pos[9]?pos[1]:pos[9];
//创建shape区域
for (int i = posY; i <=posY+pos[7]; i++) {
for (int j = posX; j <= posX+pos[6]; j++) {
int rgb = templateImage[i][j];
// 原图中对应位置变色处理
int rgb_ori = oriImage.getRGB(j, i);
int top=1;
int left=1;
int down=1;
int right=1;
if (i>shadow-1)
{
top= templateImage[i-shadow][j];
}
if (j>shadow-1){
left = templateImage[i][j-shadow];
}
if (i<bigHeight-shadow){
down = templateImage[i+shadow][j];
}
if (j<bigWidth-shadow){
right = templateImage[i][j+shadow];
}
if (rgb == 1) {
targetImage.setRGB(j-posX, i-posY, rgb_ori);
//抠图区域高斯模糊
readPixel(oriImage, j, i, values);
fillMatrix(martrix, values);
oriImage.setRGB(j, i, avgMatrix(martrix));
Color white = new Color(230,230,230);
Color gray=new Color(40,40,40);
Color black=new Color(20,20,20);
//上方是图区外,当前为顶部边界,加重阴暗
if (top==0){
oriImage.setRGB(j, i,black.getRGB());
targetImage.setRGB(j-posX, i-posY,white.getRGB());
}
//左侧是图区外,当前为左侧边界,加重阴暗
if (left==0){
oriImage.setRGB(j, i,black.getRGB());
targetImage.setRGB(j-posX, i-posY,white.getRGB());
}
//下方是图区外,当前为下方边界,加重光亮
if (down==0){
oriImage.setRGB(j, i,white.getRGB());
targetImage.setRGB(j-posX, i-posY,white.getRGB());
}
//右侧是图区外,当前为右侧边界,加重光亮
if (right==0){
oriImage.setRGB(j, i,white.getRGB());
targetImage.setRGB(j-posX, i-posY,white.getRGB());
}
}
// else {
// //这里把背景设为透明
// targetImage.setRGB(j, i, rgb_ori & 0x00ffffff);
// }
}
}
}
private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
int xStart = x - 1;
int yStart = y - 1;
int current = 0;
for (int i = xStart; i < 3 + xStart; i++)
for (int j = yStart; j < 3 + yStart; j++) {
int tx = i;
if (tx < 0) {
tx = -tx;
} else if (tx >= img.getWidth()) {
tx = x;
}
int ty = j;
if (ty < 0) {
ty = -ty;
} else if (ty >= img.getHeight()) {
ty = y;
}
pixels[current++] = img.getRGB(tx, ty);
}
}
private static void fillMatrix(int[][] matrix, int[] values) {
int filled = 0;
for (int i = 0; i < matrix.length; i++) {
int[] x = matrix[i];
for (int j = 0; j < x.length; j++) {
x[j] = values[filled++];
}
}
}
private static int avgMatrix(int[][] matrix) {
int r = 0;
int g = 0;
int b = 0;
for (int i = 0; i < matrix.length; i++) {
int[] x = matrix[i];
for (int j = 0; j < x.length; j++) {
if (j == 1) {
continue;
}
Color c = new Color(x[j]);
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
}
return new Color(r / 8, g / 8, b / 8).getRGB();
}
public static PuzzleInfo getPuzzleInfo() throws IOException {
//随机背景图
int i = new Random().nextInt(10);
String path ="puzzle/source"+i+".jpg";
//拼图原图
// ClassPathResource classPathResource = new ClassPathResource(path);
InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
// Image bi = ImageIO.read(classPathResource.getFile());
Image bi = ImageIO.read(inputStream);
//规范原图的大小
BufferedImage oriImage=resizeImage(bi,bigWidth,bigHeight,true);
//获取正方形的位置和边缘凹凸信息
Integer[] pos = getPosAndOutline();
//计算拼图的真实大小和拼图的位置信息
Integer width=square+1;
Integer height =square+1;
if (pos[2]==2){
//上方突出,真实高度加一个margin,真实坐标,正方形y坐标减去一个margin
height+=margin;
pos[9]=pos[1]-margin;
}
if (pos[3]==2){
width+=margin;
pos[8]=pos[0]-margin;
}
if (pos[4]==2){
height+=margin;
}
if (pos[5]==2){
width+=margin;
}
pos[6]=width;
pos[7]=height;
//创建拼图
BufferedImage targetImage= new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
//获取拼图的裁剪信息(0不要1裁剪)
Integer[][] templateImage = getSmallImg(pos);
//裁剪拼图
cutByTemplate(oriImage,targetImage,templateImage,pos);
PuzzleInfo puzzleInfo=new PuzzleInfo();
// puzzleInfo.setBigImg(getImageBASE64(oriImage));
// puzzleInfo.setSmallImg(getImageBASE64(targetImage));
puzzleInfo.setBigImg(getImageBASE64(resizeImage(oriImage,bigWidth/scale,bigHeight/scale,true)));
puzzleInfo.setSmallImg(getImageBASE64(resizeImage(targetImage,width/scale,height/scale,false)));
puzzleInfo.setPosX(pos[8]==null?pos[0]/10:pos[8]/10);
puzzleInfo.setPosY(pos[9]==null?pos[1]/10:pos[9]/10);
puzzleInfo.setSmallWidth(width/10);
puzzleInfo.setSmallHeight(height/10);
puzzleInfo.setBigWidth(bigWidth/10);
puzzleInfo.setBigHeight(bigHeight/10);
return puzzleInfo;
}
public static void main(String[] args) throws IOException {
File file1=new File("E:/img/small.png");
File file2=new File("E:/img/big.jpg");
File file= new File("E:/img/source.jpg");
Image bi = ImageIO.read(file);
BufferedImage oriImage=resizeImage(bi,bigWidth,bigHeight,true);
Integer[] pos = getPosAndOutline();
Integer width=square+1;
Integer height =square+1;
if (pos[2]==2){
height+=margin;
pos[9]=pos[1]-margin;
}
if (pos[3]==2){
width+=margin;
pos[8]=pos[0]-margin;
}
if (pos[4]==2){
height+=margin;
}
if (pos[5]==2){
width+=margin;
}
pos[6]=width;
pos[7]=height;
BufferedImage targetImage= new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
Integer[][] templateImage = getSmallImg(pos);
cutByTemplate(oriImage,targetImage,templateImage,pos);
ImageIO.write(oriImage, "jpg", file2);
ImageIO.write(targetImage, "png", file1);
}
}
2.接受结果的实体类
需要注意:
1.bigImg是大图的base64,smallImg是小图的base64。
2.posY 是小图相对大图的y坐标,posX是小图相对大图的x坐标,我们拿到PuzzleInfo 对象时,需要把posX保存在缓存中,并赋值0。
前端将移动距离传回后端,只需与缓存中的posX对比,差值的绝对值小于5即可。
import lombok.Data;
@Data
public class PuzzleInfo {
private String bigImg;
private String smallImg;
private Integer posY;
private Integer posX;
private Integer smallWidth;
private Integer smallHeight;
private Integer bigWidth;
private Integer bigHeight;
}
准备图片资源
保持格式和图片名如下,除非修改获取资源部分的代码,否则报错找不到图片资源。如果保持这样还报错,说明图片未编译到target目录,把target删除,重新编译。