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>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

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