用Java实现简单的“马赛克拼图”

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Kurozaki_Kun/article/details/79724146

先来一张效果图


这些头像都来自微信好友的,放大看这不过是一张众多头像拼成的大图,缩小或远看能够发现这些头像其实拼出了一个有趣的图案。这个实现思路并不复杂(这个思路暂时只针对黑白的图片如果要支持彩图会更复杂一点,可以了解下 foto-mosaik-edda)

  • 首先需要一张“参考图片”和大量小图片(最好都是方形的,头像是个很好的选择)
  • 计算每一张方形小图片的平均灰度值,计算公式为 gray = (77*red + 150*green + 29*blue + 128) >> 8,这个公式是根据人对于不同色彩敏感程度而得出的
  • 每张图片计算出的平均灰度值都在0~255,再按照灰度值区间分成4组,每个区间的跨度是256/4=64,当然也可以多一点,这是为了更容易匹配对应的像素点。
  • 将参考图片压缩成较小的尺寸,上面这张图是压成50x50,计算每个像素点的灰度值,同样划分为4个区间,最后在区间对应分组里随机选择一张图片摆在对应的像素点上。
  • 这个随机选择的算法也是有一定讲究的,我们希望的效果是尽量避免相同的图片相邻摆放

代码实现

GraphicsMosaicHandler.java(用于处理主要逻辑)

package com.yotwei.core;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by YotWei on 2018/3/26.
 */
public class GraphicsMosaicHandler {

    public int handle(String srcPath, String dstPath) throws Exception {
        //read origin image
        Util.message("正在读入图片...");
        BufferedImage inputImg = ImageIO.read(new File(srcPath));
        BufferedImage outputImg = new BufferedImage(
                inputImg.getWidth() * GMConfig.MOSAIC_WIDTH,
                inputImg.getHeight() * GMConfig.MOSAIC_HEIGHT,
                BufferedImage.TYPE_INT_RGB);

        //read mosaic images
        Util.message("正在读入马赛克...");
        Map<String, List<String>> filenames = new HashMap<>();
        Map<String, FileNameList> fnameMap = new HashMap<>();
        File pDir = new File(GMConfig.MOSAIC_PROC_PATH);

        for (String name : pDir.list()) {
            String key = name.substring(0, name.indexOf("_"));
            if (!filenames.containsKey(key)) {
                filenames.put(key, new ArrayList<>());
            }
            filenames.get(key).add(name);
        }
        for (Map.Entry<String, List<String>> entry : filenames.entrySet()) {
            fnameMap.put(entry.getKey(), new FileNameList(entry.getValue()));
        }
        int rgb, gray;
        String k;
        Util.message("正在绘制...");
        for (int y = 0; y < inputImg.getHeight(); y++) {
            for (int x = 0; x < inputImg.getWidth(); x++) {
                rgb = inputImg.getRGB(x, y);
                gray = ((77 * ((rgb & 0xff0000) >> 16))
                        + (150 * ((rgb & 0x00ff00) >> 8))
                        + (29 * (rgb & 0x0000ff))) >> 8;
                k = "L" + (gray / GMConfig.GRAY_LEVEL_INTERVAL);
                BufferedImage mosaicImg = ImageIO
                        .read(new File(GMConfig.MOSAIC_PROC_PATH, fnameMap.get(k).next()));
                outputImg.getGraphics().drawImage(mosaicImg,
                        x * GMConfig.MOSAIC_WIDTH,
                        y * GMConfig.MOSAIC_HEIGHT,
                        GMConfig.MOSAIC_WIDTH,
                        GMConfig.MOSAIC_HEIGHT, null);
            }
        }
        Util.message("绘制完成,正在写入文件...");
        ImageIO.write(outputImg, "jpg", new File(dstPath, "out_" + System.currentTimeMillis() + ".jpg"));
        Util.message("写入成功!");
        return 0;
    }

    public void processOriginResources() throws IOException {
        String procPath = GMConfig.MOSAIC_PROC_PATH;
        File dir = new File(procPath);

        if (!dir.isDirectory() || dir.list().length == 0) {
            if (!dir.exists() && !dir.mkdirs()) {
                throw new IOException("无法创建文件夹: " + procPath);
            }

            String originPath = GMConfig.MOSAIC_ORIGIN_PATH;
            File originDir = new File(originPath);
            if (!originDir.isDirectory()) {
                throw new IOException(String.format("文件夹\"%s\"不存在", originPath));
            }

            //开始读取图片源地
            ExecutorService executor = Executors.newFixedThreadPool(10);
            for (File file : originDir.listFiles()) {
                BufferedImage readImg = ImageIO.read(file);
                if (readImg == null) {
                    continue;
                }
                executor.execute(new CompressTaskRunnable(readImg));
            }
            executor.shutdown();
        }
    }

    static class FileNameList {

        public FileNameList(List<String> list) {
            if (list != null) {
                this.list = list;
                Collections.shuffle(list);
            }
        }

        public String next() {
            if (list == null) {
                return null;
            }
            if (counter == list.size()) {
                counter = 0;
                Collections.shuffle(list);
            }
            return list.get(counter++);
        }

        private List<String> list;
        private int counter = 0;
    }

    static class CompressTaskRunnable implements Runnable {

        private BufferedImage src;

        CompressTaskRunnable(BufferedImage src) {
            this.src = src;
        }

        @Override
        public void run() {
            BufferedImage compress = Util.compressImage(src, GMConfig.MOSAIC_WIDTH, GMConfig.MOSAIC_HEIGHT);
            int gray = Util.getAvgGray(compress);
            try {
                String fileName = "L" + (gray / GMConfig.GRAY_LEVEL_INTERVAL) + "_" +
                        System.currentTimeMillis() + ".jpg";
                ImageIO.write(compress, "jpg", new File(GMConfig.MOSAIC_PROC_PATH, fileName));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Util.java

package com.yotwei.core;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Collections;

/**
 * 工具类
 */
public class Util {
    public static BufferedImage compressImage(BufferedImage src, int dstWidth, int dstHeight) {
        BufferedImage compress = new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_RGB);
        compress.getGraphics().drawImage(
                src.getScaledInstance(dstWidth, dstHeight, Image.SCALE_SMOOTH),
                0, 0, dstWidth, dstHeight, null);
        return compress;
    }

    /**
     * 平均灰度值
     */
    public static int getAvgGray(BufferedImage img) {
        int x, y, temp, graySum = 0;
        for (y = 0; y < img.getHeight(); y++) {
            for (x = 0; x < img.getWidth(); x++) {
                temp = img.getRGB(x, y);
                graySum += (((77 * ((temp & 0xff0000) >> 16))
                        + (150 * ((temp & 0x00ff00) >> 8))
                        + (29 * (temp & 0x0000ff))) >> 8);
            }
        }
        return graySum / (img.getWidth() * img.getHeight());
    }

    public static void message(String message) {
        System.out.println(message);
    }
}

CMConfig.java(常量都在这里)

package com.yotwei.core;

/**
 * Created by YotWei on 2018/3/26.
 */
public class GMConfig {

    public static final String MOSAIC_ORIGIN_PATH = "weixinAvatars/";    //头像原文件夹
    public static final String MOSAIC_PROC_PATH = "process-resources/";    //对文件进一步处理后把图片放在这里

    public static final int MOSAIC_WIDTH = 32;    //马赛克图片的宽度
    public static final int MOSAIC_HEIGHT = 32;    //马赛克图片的高度

    public static final int GRAY_LEVEL_INTERVAL = 256 / 4;
}

调用

package com.yotwei.client;

import com.yotwei.core.GraphicsMosaicHandler;


/**
 * Created by YotWei on 2018/3/26.
 */
public class GMClient {

    public static void main(String[] args) throws Exception {
        GraphicsMosaicHandler handler = new GraphicsMosaicHandler();
        System.out.println("GM process with return code: " + handler.handle("imgs/trust.png","imgs/"));

    }
}

最后再放几张效果图



猜你喜欢

转载自blog.csdn.net/Kurozaki_Kun/article/details/79724146