用Java實現簡單的“馬賽克拼圖”

先來一張效果圖


這些頭像都來自微信好友的,放大看這不過是一張衆多頭像拼成的大圖,縮小或遠看能夠發現這些頭像其實拼出了一個有趣的圖案。這個實現思路並不複雜(這個思路暫時只針對黑白的圖片如果要支持彩圖會更復雜一點,可以瞭解下 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/"));

    }
}

最後再放幾張效果圖



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章