Mapbox Sprite精灵图生成

出处:ATtuing - 博客园https://www.cnblogs.com/ATtuing/p/9273391.html

1.什么是sprite文件

sprite 文件主要是将一堆小图生成一种大图的方法,并且将每张小图的位置信息保存下来,方便读取。在网络请求中会减少请求的数量,mapbox借鉴前端中CSS Sprite方法存储图标信息的。sprite.png文件保存图标,sprite.json保存名称及位置信息,下图图展示的是小图标与大图文件的示例。下面我讲一下两种文件转换。

md_beee6768.png

转为

md_d949a4ef.png

2.实现的功能

此基础上将小图转大图功能用JavaScript实现。使用Vue、Element实现。

演示地址:https://c317.gitee.io/myb_style/html/creat_MBSprite.html

md_30d36403.png
3.具体实现方法

1.js获取图片像素

function getXY(canvas, x, y) {
                let ctx = canvas.getContext("2d");
                // 获取画布上的图像像素矩阵
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let w = imageData.width
                let data = imageData.data
                let color = []
                color[0] = data[(y * w + x) * 4]
                color[1] = data[(y * w + x) * 4 + 1]
                color[2] = data[(y * w + x) * 4 + 2]
                color[3] = data[(y * w + x) * 4 + 3]
                return color
            }

2.js设置图片像素

//创建canvas
let editMap = document.createElement('canvas');
editMap.width = allwidth;//设置宽度
editMap.height = allheight;//设置高度
let editCxt = editMap.getContext("2d");
//获取ImageData
let imageData = editCxt.getImageData(0, 0, allwidth, allheight);
function setXY(imageData, x, y, color) {
                let w = imageData.width
                let data = imageData.data
                data[(y * w + x) * 4] = color[0]
                data[(y * w + x) * 4 + 1] = color[1]
                data[(y * w + x) * 4 + 2] = color[2]
                data[(y * w + x) * 4 + 3] = color[3]
                imageData.data = data;
            }

3.小图转大图

将小图标合成一张sprite大图并在sprite.json中记录生成的位置信息,这里最主要的就是图标的摆放规则。

(1)获取所有的图标文件,按照高度从小到大排列

(2)根据大图生成的默认宽度,循环小图片,形成一行一行的图片集合。

(3)根据行数和宽度生成大图的宽度。

(4)循环小图标,在大图中画出小图标,并记录位置信息。

实现成果与核心代码如下:

md_c076a122.png
function creatSprite(paramlist) {
                //图片默认宽度为255
                let allwidth = 255;
                let rowparams = [], paramnowlist = [];
                let countnum = 0;
                for (let i = 0; i < paramlist.length; i++) {
                    countnum += paramlist[i].width;
                    if (countnum > allwidth) {
                        i = i - 1;
                        countnum = 0;
                        rowparams.push(paramnowlist);
                        paramnowlist = [];
                    } else {
                        paramnowlist.push(paramlist[i]);
                    }
                    if (i === paramlist.length - 1) {
                        rowparams.push(paramnowlist);
                        break;
                    }
                }
                //计算应有的高度
                let allheight = 0;
                rowparams.forEach(item => {
                    allheight += Math.max.apply(Math, item.map(m => m.height));
                })
                //计算应有的宽度
                allwidth = 0
                rowparams[0].forEach(item => {
                    allwidth += item.width;
                })
                if (allwidth > 200) allwidth = 255;
                console.log(allwidth)
                let spritejson = "{\n";
                //开始画大图
                let editMap = document.createElement('canvas');
                editMap.width = allwidth;
                editMap.height = allheight;
                let editCxt = editMap.getContext("2d");
                let editImageData = editCxt.getImageData(0, 0, allwidth, allheight);
                //保存起始高度
                let heighttemp = 0;
                for (let i = 0; i < rowparams.length; i++) {
                    let tempwidthnum = 0;
                    for (let j = 0; j < rowparams[i].length; j++) {
                        let map = rowparams[i][j].canvas;
                        //循环小图片
                        for (let x = 0; x < map.width; x++) {
                            for (let y = 0; y < map.height; y++) {
                                //获取像素
                                let color = this.getXY(map, x, y);
                                this.setXY(editImageData, x + tempwidthnum, y + heighttemp, color);
                            }
                        }
                        spritejson += "  \"" + rowparams[i][j].name.replace("-", "/").replace("&", ":") + "\":{\"x\":";
                        spritejson += tempwidthnum + ",\"y\":" + heighttemp + ",\"width\":" + rowparams[i][j].width;
                        spritejson += ",\"height\":" + rowparams[i][j].height + ",\"pixelRatio\":1,\"sdf\":false},\n";
                        //增加宽度
                        tempwidthnum += rowparams[i][j].width;
                    }
                    heighttemp += Math.max.apply(Math, rowparams[i].map(m => m.height));
                }
                //保存大图
                editCxt.putImageData(editImageData, 0, 0);
                this.editURL = editMap.toDataURL("image/png");//取得图像的数据URI

                spritejson = spritejson.substring(0, spritejson.lastIndexOf(','));
                spritejson += "\n}";
                this.spritejson = spritejson
            }

4.完整代码

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>生成Mapbox Sprite(精灵图)</title>
    <!-- import CSS -->
    <link href="https://cdn.bootcss.com/element-ui/2.4.5/theme-chalk/index.css" rel="stylesheet">
    <style>
        html, body, #app{
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            position: absolute;
        }
        .upload, .creat{
            border-radius: 4px;
            background: #d3dce6;
            height: 100%;
        }
        .but{
            height: 100%;
            display: flex;
            align-items:center;
            justify-content:center;
        }
    </style>
</head>
<body>
<div id="app">
    <el-row style="height: 100%">
        <el-col class="upload" :span="11">
            <el-upload
                    action="https://jsonplaceholder.typicode.com/posts/"
                    list-type="picture-card"
                    accept="image/*"
                    :on-preview="handlePreview"
                    :on-success="handleSuccess"
                    :on-remove="handleRemove" multiple>
                <i class="el-icon-plus"></i>
                <div slot="tip" class="el-upload__tip">只能上传图片格式文件,且不超过500kb</div>
            </el-upload>
            <el-dialog :visible.sync="dialogVisible">
                <img width="100%" :src="dialogImageUrl" alt="">
            </el-dialog>
        </el-col>
        <el-col class="but" :span="2">
            <el-button type="primary" @click="image">转换</el-button>
        </el-col>
        <el-col class="creat" :span="11">
            <el-row style="height: 45%">
                <img :src="editURL" style="border:1px solid #6f6f6f">
            </el-row>
            <el-row style="height: 10%">
                <el-button type="primary" @click="downloadImg">下载图片</el-button>
                <el-button type="primary" @click="downloadJSON">下载JSON</el-button>
            </el-row>
            <el-row style="height: 45%">
                <el-input
                    type="textarea"
                    :rows="18"
                    placeholder="JSON内容"
                    v-model="spritejson"></el-input>
            </el-row>
        </el-col>
    </el-row>
</div>
</body>
<!-- import Vue before Element -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://cdn.bootcss.com/element-ui/2.4.5/index.js"></script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                dialogImageUrl: '',
                dialogVisible: false,
                disabled: false,
                fileList: [],
                canvas: [],
                paramList: [],//List<Param>
                rowparams: [],//List<List<Param>>
                editURL:'',
                spritejson:''
            }
        },
        mounted() {
        },
        methods: {
            handleRemove(file, fileList) {
                this.fileList = fileList;
            },
            handlePreview(file) {
                this.dialogImageUrl = file.url;
                this.dialogVisible = true;
            },
            handleSuccess(response, file, fileList) {
                this.fileList = fileList;
            },
            image() {
                let paramlist = [];
                if (this.fileList.length === 0) return;
                this.fileList.forEach(file => {
                    let image = new Image();
                    image.src = file.url;
                    let canvas = document.createElement('canvas');
                    canvas.width = image.width;
                    canvas.height = image.height;
                    canvas.getContext("2d").drawImage(image, 0, 0);
                    paramlist.push({
                        name: file.name,
                        x: 0, y: 0,
                        width: image.width,
                        height: image.height,
                        canvas: canvas
                    })
                })
                paramlist.sort(function (a, b) {
                    return a.height - b.height
                })
                this.paramList = paramlist;
                this.creatSprite(paramlist);
            },
            creatSprite(paramlist) {
                //图片默认宽度为255
                let allwidth = 255;
                let rowparams = [], paramnowlist = [];
                let countnum = 0;
                for (let i = 0; i < paramlist.length; i++) {
                    countnum += paramlist[i].width;
                    if (countnum > allwidth) {
                        i = i - 1;
                        countnum = 0;
                        rowparams.push(paramnowlist);
                        paramnowlist = [];
                    } else {
                        paramnowlist.push(paramlist[i]);
                    }
                    if (i === paramlist.length - 1) {
                        rowparams.push(paramnowlist);
                        break;
                    }
                }
                //计算应有的高度
                let allheight = 0;
                rowparams.forEach(item => {
                    allheight += Math.max.apply(Math, item.map(m => m.height));
                })
                //计算应有的宽度
                allwidth = 0
                rowparams[0].forEach(item => {
                    allwidth += item.width;
                })
                if (allwidth > 200) allwidth = 255;
                console.log(allwidth)
                let spritejson = "{\n";
                //开始画大图
                let editMap = document.createElement('canvas');
                editMap.width = allwidth;
                editMap.height = allheight;
                let editCxt = editMap.getContext("2d");
                let editImageData = editCxt.getImageData(0, 0, allwidth, allheight);
                //保存起始高度
                let heighttemp = 0;
                for (let i = 0; i < rowparams.length; i++) {
                    let tempwidthnum = 0;
                    for (let j = 0; j < rowparams[i].length; j++) {
                        let map = rowparams[i][j].canvas;
                        //循环小图片
                        for (let x = 0; x < map.width; x++) {
                            for (let y = 0; y < map.height; y++) {
                                //获取像素
                                let color = this.getXY(map, x, y);
                                this.setXY(editImageData, x + tempwidthnum, y + heighttemp, color);
                            }
                        }
                        spritejson += "  \"" + rowparams[i][j].name.replace("-", "/").replace("&", ":") + "\":{\"x\":";
                        spritejson += tempwidthnum + ",\"y\":" + heighttemp + ",\"width\":" + rowparams[i][j].width;
                        spritejson += ",\"height\":" + rowparams[i][j].height + ",\"pixelRatio\":1,\"sdf\":false},\n";
                        //增加宽度
                        tempwidthnum += rowparams[i][j].width;
                    }
                    heighttemp += Math.max.apply(Math, rowparams[i].map(m => m.height));
                }
                //保存大图
                editCxt.putImageData(editImageData, 0, 0);
                this.editURL = editMap.toDataURL("image/png");//取得图像的数据URI

                spritejson = spritejson.substring(0, spritejson.lastIndexOf(','));
                spritejson += "\n}";
                this.spritejson = spritejson
            },
            getXY(canvas, x, y) {
                let ctx = canvas.getContext("2d");
                // 获取画布上的图像像素矩阵
                let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                let w = imageData.width
                let data = imageData.data
                let color = []
                color[0] = data[(y * w + x) * 4]
                color[1] = data[(y * w + x) * 4 + 1]
                color[2] = data[(y * w + x) * 4 + 2]
                color[3] = data[(y * w + x) * 4 + 3]
                return color
            },
            setXY(imageData, x, y, color) {
                let w = imageData.width
                let data = imageData.data
                data[(y * w + x) * 4] = color[0]
                data[(y * w + x) * 4 + 1] = color[1]
                data[(y * w + x) * 4 + 2] = color[2]
                data[(y * w + x) * 4 + 3] = color[3]
                imageData.data = data;
            },
            downloadImg() {
                if (this.editURL === null || this.editURL === '') return;
                if (this.spritejson === null || this.spritejson === '') return;
                // 将图片的src属性作为URL地址
                let a = document.createElement('a')
                let event = new MouseEvent('click')
                a.download = 'sprite'
                a.href = this.editURL
                a.dispatchEvent(event);
            },
            downloadJSON(){
                let pom = document.createElement('a');
                pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.spritejson));
                pom.setAttribute('download', 'sprite.json');
                if (document.createEvent) {
                    let event = document.createEvent('MouseEvents');
                    event.initEvent('click', true, true);
                    pom.dispatchEvent(event);
                } else {
                    pom.click();
                }
            }
        }
    })
</script>
</html>
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • CSS雪碧,即CSS Sprite,也有人叫它CSS精灵,是一种CSS图像合并技术,该方法是将小图标和背景图像合并...
    ColinLiu123阅读 890评论 0 0
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,596评论 1 45
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,093评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 6,907评论 0 2
  • 今天上午陪老妈看病,下午健身房跑步,晚上想想今天还没有断舍离,马上做,衣架和旁边的的布衣架,一看乱乱,又想想自己是...
    影子3623253阅读 2,927评论 3 8