Go Web生成马赛克图片(1): 单机版
马赛克:
对图片进行mosaic处理,就是说讲图片分割成多个通常是大小相同的矩形截面,然后使用被称为瓷砖图片的新图片区代替截面原有的图片。
所以,马赛克图片让我们在足够远的地方观察或者以斜视的角度观察,会看到图片在进行马赛克处理之前的样子。凑近区观察,就会发现他们是由成百上千张尺寸更小的瓷砖图片组成。
构建生成马赛克图片的Web应用的基本思路就成型了:接收用户上传的目标图片,然后根据目标图片生成相应的马赛克图片。
创建马赛克图片:(不使用第三方库)
- 通过扫描图片目录,使用图片的文件名作为键、图片的平均颜色作为值,构建出一个由瓷砖图片组成的散列,即瓷砖图片数据库。通过计算图片中每个像素的红、绿、蓝颜色的总和并除以像素的总数量,得到一个3元组(图片的平均颜色)
- 根据瓷砖图片的大小,将目标图片切割为一系列尺寸更小的子图片
- 对于目标图片切割出的每张子图片,将他们位于左上方的第一个像素设定为该图片的平均颜色。
- 根据子图片的平均颜色,在瓷砖图片数据库中找出一张平均颜色与之最接近的瓷砖图片,然后在目标图片的相应位置上使用瓷砖图片代替原来的子图片。为了找出最相近的平均颜色,程序需要将子图片的平均颜色以及瓷砖图片的平均颜色都转换为三维空间中的一个点,并计算2点之间的欧几里得距离
- 当1张瓷砖图片被选中后,程序就会把这张图片从瓷砖图片数据库中移除,以此来保证马赛克图片中的每张瓷砖图片都是独一无二不相同的。
package mainimport ("fmt""image""image/color""io/ioutil""math""os"
)// resize an image by its ratio e.g. ratio 2 means reduce the size by 1/2, 10 means reduce the size by 1/10
func resize(in image.Image, newWidth int) image.NRGBA {bounds := in.Bounds()width := bounds.Max.X - bounds.Min.Xratio := width / newWidthout := image.NewNRGBA(image.Rect(bounds.Min.X/ratio, bounds.Min.X/ratio, bounds.Max.X/ratio, bounds.Max.Y/ratio))for y, j := bounds.Min.Y, bounds.Min.Y; y < bounds.Max.Y; y, j = y+ratio, j+1 {for x, i := bounds.Min.X, bounds.Min.X; x < bounds.Max.X; x, i = x+ratio, i+1 {r, g, b, a := in.At(x, y).RGBA()out.SetNRGBA(i, j, color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)})}}return *out
}// find the average color of the picture
func averageColor(img image.Image) [3]float64 {bounds := img.Bounds()r, g, b := 0.0, 0.0, 0.0for y := bounds.Min.Y; y < bounds.Max.Y; y++ {for x := bounds.Min.X; x < bounds.Max.X; x++ {r1, g1, b1, _ := img.At(x, y).RGBA()r, g, b = r+float64(r1), g+float64(g1), b+float64(b1)}}totalPixels := float64(bounds.Max.X * bounds.Max.Y)return [3]float64{r / totalPixels, g / totalPixels, b / totalPixels}
}var TILESDB map[string][3]float64func cloneTilesDB() map[string][3]float64 {db := make(map[string][3]float64)for k, v := range TILESDB {db[k] = v}return db
}// populate a tiles database in memory
func tilesDB() map[string][3]float64 {fmt.Println("Start populating tiles db ...")db := make(map[string][3]float64)files, _ := ioutil.ReadDir("tiles")for _, f := range files {name := "tiles/" + f.Name()file, err := os.Open(name)if err == nil {img, _, err := image.Decode(file)if err == nil {db[name] = averageColor(img)} else {fmt.Println("error in populating tiles db:", err, name)}} else {fmt.Println("cannot open file", name, "when populating tiles db:", err)}file.Close()}fmt.Println("Finished populating tiles db.")return db
}// find the nearest matching image
func nearest(target [3]float64, db *map[string][3]float64) string {var filename stringsmallest := 1000000.0for k, v := range *db {dist := distance(target, v)if dist < smallest {filename, smallest = k, dist}}delete(*db, filename)return filename
}// find the Eucleadian distance between 2 points
func distance(p1 [3]float64, p2 [3]float64) float64 {return math.Sqrt(sq(p2[0]-p1[0]) + sq(p2[1]-p1[1]) + sq(p2[2]-p1[2]))
}// find the square
func sq(n float64) float64 {return n * n
}
上面的代码中,
averageColor函数会把给定图片的每个像素中的红绿蓝3种颜色相加,并将颜色的综合除以图片的像素数量,最后把除法计算的结果记录在一个新创建的3元数组里面。
resize函数会把图片缩放到指定的宽度。
tilesDB函数会通过扫描瓷砖图片所在的目录来创建一个瓷砖图片数据库。(在内存中创建一个瓷砖图片数据库)
瓷砖图片数据库是一个映射,建为字符串,值为三元组。
tilesDB会打开目录中的每张图片,并根据这些图片的平均颜色在映射中创建相应的记录。
为了寻找与目标图片想匹配的瓷砖图片,程序会将tilesDB函数创建的瓷砖图片数据库以及目标图片的平均颜色传入nearest函数。
nearest函数就是寻找与目标图片平均颜色最接近的瓷砖图片。会把瓷砖图片数据库中的所有记录与目标图片的平均颜色进行对比,2者欧几里得距离最短的那条记录就是目标图片平均颜色最为接近的瓷砖图片。然后从数据库中移除被选中的瓷砖图片,并把图片的名字返回给调用者。
distance函数是计算2个三元组之间的欧几里得距离的。sq函数是计算给定数值的平方。
扫描和载入瓷砖图片数据库是费时的,为了效率,每次生成马赛克图片的时候都重复一次这个操作先然是不明智的。
合理的做法是只执行1次这个操作,创建出1个瓷砖图片数据库的原本,然后在每次生成马赛克图片的时候根据这个原本复制出一个独立的副本。cloneTilesDB函数就是在每次需要生成马赛克图片的时候就复制出一个瓷砖图片数据库副本。
然后就是编写web应用:
package mainimport ("bytes""encoding/base64""fmt""html/template""image""image/draw""image/jpeg""net/http""os""strconv""time"// "runtime"
)func main() {// runtime.GOMAXPROCS(runtime.NumCPU())mux := http.NewServeMux()files := http.FileServer(http.Dir("public"))mux.Handle("/static/", http.StripPrefix("/static/", files))mux.HandleFunc("/", upload)mux.HandleFunc("/mosaic", mosaic)server := &http.Server{Addr: "127.0.0.1:8080",Handler: mux,}// building up the source tile databaseTILESDB = tilesDB()fmt.Println("Mosaic server started.")server.ListenAndServe()
}func upload(w http.ResponseWriter, r *http.Request) {t, _ := template.ParseFiles("upload.html")t.Execute(w, nil)
}func mosaic(w http.ResponseWriter, r *http.Request) {t0 := time.Now()// get the content from the POSTed formr.ParseMultipartForm(10485760) // max body in memory is 10MBfile, _, _ := r.FormFile("image")defer file.Close()tileSize, _ := strconv.Atoi(r.FormValue("tile_size"))// decode and get original imageoriginal, _, _ := image.Decode(file)bounds := original.Bounds()// create a new image for the mosaicnewimage := image.NewNRGBA(image.Rect(bounds.Min.X, bounds.Min.X, bounds.Max.X, bounds.Max.Y))// build up the tiles databasedb := cloneTilesDB()// source point for each tile, which starts with 0, 0 of each tilesp := image.Point{0, 0}for y := bounds.Min.Y; y < bounds.Max.Y; y = y + tileSize {for x := bounds.Min.X; x < bounds.Max.X; x = x + tileSize {// use the top left most pixel as the average colorr, g, b, _ := original.At(x, y).RGBA()color := [3]float64{float64(r), float64(g), float64(b)}// get the closest tile from the tiles DBnearest := nearest(color, &db)file, err := os.Open(nearest)if err == nil {img, _, err := image.Decode(file)if err == nil {// resize the tile to the correct sizet := resize(img, tileSize)tile := t.SubImage(t.Bounds())tileBounds := image.Rect(x, y, x+tileSize, y+tileSize)// draw the tile into the mosaicdraw.Draw(newimage, tileBounds, tile, sp, draw.Src)} else {fmt.Println("error:", err, nearest)}} else {fmt.Println("error:", nearest)}file.Close()}}buf1 := new(bytes.Buffer)jpeg.Encode(buf1, original, nil)originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes())buf2 := new(bytes.Buffer)jpeg.Encode(buf2, newimage, nil)mosaic := base64.StdEncoding.EncodeToString(buf2.Bytes())t1 := time.Now()images := map[string]string{"original": originalStr,"mosaic": mosaic,"duration": fmt.Sprintf("%v ", t1.Sub(t0)),}t, _ := template.ParseFiles("results.html")t.Execute(w, images)}
其中mosaic函数就是一个处理器函数(注意go的处理器和处理器函数不是一个东西)在这个歌函数里包含了用于生成马赛克图片的主要逻辑。
首先程序会获取用户上传的目标图片,并从表单中获取瓷砖图片的尺寸。然后程序对目标图片进行解码,创建出一张全新的空白的马赛克图片,复制一份瓷砖图片数据库,并为每张瓷砖图片设置起始点。然后就可以对目标图片分割出的各张瓷砖图片尺寸的子图片进行迭代了。
对于每张被分割的子图片,程序都会把它左上角的第一个像素设置为该图片的平均颜色,然后在瓷砖图片数据库中查找与颜色最为接近的瓷砖图片。在找到匹配的瓷砖图片之后,被调用的函数就会向程序返回该图片的文件名,程序就可以打开这张瓷砖图片并缩放到指定的瓷砖图片尺寸了。缩放操作执行后,程序会把最终得到的瓷砖图片绘制到之前创建的马赛克图片上。
上述方法生成整张马赛克图片后,程序首先会将其编码为JPEG格式的图片,然后将图片编码为base64格式的字符串。
将图片的base64字符串交给模板引擎去处理:
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>Mosaic</title><meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0' name='viewport'><meta content='none' name='robots'><link href='/static/css/bootstrap.min.css' rel='stylesheet' type='text/css'><link href='/static/css/font-awesome.min.css' rel='stylesheet' type='text/css'><script src='/static/js/jquery.min-1.9.1.js' type='text/javascript'></script><script src='/static/js/bootstrap.min.js' type='text/javascript'></script><!--[if lt IE 9]><script src='/static/js/html5.js'></script><![endif]--><style>body { padding-top: 20px; }p {font-size: 16px; line-height: 140%; } .content {background-color: #fff;padding: 20px 50px 50px 50px;} </style> </head> <body><div class='container'><div class="col-md-6"><img src="https://img-blog.csdnimg.cn/2022010709324967338.jpg" width="100%"><div class="lead">Original</div></div><div class="col-md-6"><img src="https://img-blog.csdnimg.cn/2022010709324912395.jpg" width="100%"><div class="lead">Mosaic - {{ .duration }}</div></div><div class="col-md-12 center"><a class="btn btn-lg btn-info" href="/">Go Back</a></div></div> <br></body>
</html>
人傻了~哈哈哈
Go Web生成马赛克图片(1): 单机版相关推荐
- 前端线上图片怎么生成马赛克?
前言 说起图片的马赛克,可能一般都是由后端实现然后传递图片到前端,但是前端也是可以通过canvas来为图片加上马赛克的,下面就通过码上掘金来进行一个简单的实现. 实现 实现过程 <img src ...
- 前端线上图片生成马赛克
前言 说起图片的马赛克,可能一般都是由后端实现然后传递图片到前端,但是前端也是可以通过canvas来为图片加上马赛克的,下面就进行一个简单的实现. 思路 最开始需要实现马赛克功能是需要通过canvas ...
- 美翻朋友圈:用Python生成蒙太奇马赛克图片
题图 | 视觉中国 来源 | ZackSock(ID:ZackSock) 我们有时候会听到这么一个词--"蒙太奇",但却不知道这个词是什么意思.蒙太奇原为建筑学术语,意为构成.装配 ...
- Java Web学习总结(6)——通过Servlet生成验证码图片
2019独角兽企业重金招聘Python工程师标准>>> 一.BufferedImage类介绍 生成验证码图片主要用到了一个BufferedImage类,如下: 创建一个DrawIma ...
- 美翻你的朋友圈,Python生成蒙太奇马赛克图片
一.前言 我们有时候会听到这么一个词–"蒙太奇",但却不知道这个词是什么意思.蒙太奇原为建筑学术语,意为构成.装配.而后又延伸为一种剪辑理论:当不同镜头拼接在一起时,往往又会产生各 ...
- 公众号 自动生成海报 python_美翻你的朋友圈,Python生成蒙太奇马赛克图片
一.前言 我们有时候会听到这么一个词--"蒙太奇",但却不知道这个词是什么意思.蒙太奇原为建筑学术语,意为构成.装配.而后又延伸为一种剪辑理论:当不同镜头拼接在一起时,往往又会产生 ...
- python色块图_美翻你的朋友圈,Python生成蒙太奇马赛克图片
一.前言 我们有时候会听到这么一个词--"蒙太奇",但却不知道这个词是什么意思.蒙太奇原为建筑学术语,意为构成.装配.而后又延伸为一种剪辑理论:当不同镜头拼接在一起时,往往又会产生 ...
- java web 生成验证码_Javaweb开发中通过Servlet生成验证码图片
一.BufferedImage类介绍 生成验证码图片主要用到了一个BufferedImage类,如下: 创建一个DrawImage Servlet,用来生成验证码图片 package gacl.res ...
- php mysql 验证码代码_PHP_PHP 验证码的实现代码,checkcode.php 生成验证码图片, - phpStudy...
PHP 验证码的实现代码 checkcode.php 生成验证码图片,还有变量 $_SESSION[check_pic]. 复制代码 代码如下: session_start(); for($i=0; ...
最新文章
- 容器间通信_Vue组件间通信的6种方式,前端工程师人人都会,网友:太简单了...
- React Native进行签名打包成Apk
- 5G NR — 关键技术
- 01.Python基础-3.集合容器
- Excel批量转csv格式
- 2018华为软件精英挑战赛总结
- 原理_JS引擎对未声明变量的处理
- citrix POC环境准备
- mysql网络订餐系统截屏_在线订餐系统mysql字段
- 性冷淡风的麻将,获红点奖!网友:没有烟火气了
- 关于在nw里使用require('printer')和nw.require('printer')报错的问题
- https 方式使用git@osc设置密码的方式
- Basic Oracle Net Services Client-Side Configuration
- Javascript自动登录B/S系统的简单实现
- 图画日记怎么画_期末到了,孩子不会复习,可以试试画思维导图的方法
- 《我是北大旁听生/郑球洋》
- security update 补丁更新失败
- 死链接检查工具:Xenu 使用教程
- Linux环境下进行本地Blast比对——操作流程
- JVAV中常见的十大异常