兩年前的項目了,今天翻出來看一下。主要還是從需求分析和設計實現兩大塊進行介紹。
(一)需求分析
1、項目背景描述
當時做這個項目的時候,公司正想入局微信小程序,而我此時微信小程序也已經學了半年了,正好派上了用場。由於微信小程序的“用完即走”的產品理念,加上當時AR/VR正火,老師(新中軟研究中心)想着AR/VR的場景特別適合微信小程序的這個產品理念,想着將它們兩個結合起來設計一款基於微信小程序和AR的產品。這個一旦實現,一來可以作爲公司的技術儲備,二來也可以在實踐中提升我們的項目能力。
2、需求描述
-
產品簡要描述
經過討論決定做一款具備教育和娛樂功能的微信小程序,主要面向的是3~12歲的兒童。用戶通過該微信小程序進行拍照/選擇相機照片,然後用戶可以直接使用AR畫筆在所拍人像或物品上面進行二次創作,作品可通過微信小程序進行分享。當然用戶也可以不進行拍照,使用我們的AR畫筆功能直接在攝像頭上面進行繪畫,以實現增強現實的效果。 -
場景描述
剛開始的需求場景大概是這樣的:用戶打開微信—>進入ARpainting微信小程序—>用戶進入首頁—>首頁中頁面以列表方式呈現,底欄是類似於微信底欄的可以進行直接切換—>用戶選擇拍照功能—>用戶進行拍照—>用戶可直接在上面使用AR畫筆塗鴉—>用戶可以實現保存、分享等功能。
然後需求又增加了用戶可以直接通過微信小程序打開攝像頭—>用戶可以直接在攝像頭上面使用畫筆進行塗鴉。
然後又增加了用戶可以通過掃描二維碼,微信小程序在攝像頭上實現顯示設置的標籤,以達到增強現實的效果。然後用戶點擊相應標籤,進入相應的VR頁面(比如說點的是我們學院教學樓的標籤顯示的便是我們教學樓的全景圖片),然後用戶可以查看是否有優惠券等功能。
後面又爲了增加產品的趣味性,結合騰訊優圖的人臉融合API,添加了基於微信小程序的人臉融合功能。
(二)概要設計
1、項目結構圖
2、模塊解釋
bgtuya | 用戶可以進行直接在攝像頭頁面塗鴉 |
---|---|
export_img | 導入圖片模塊 |
first | 首頁 |
logs | 日誌模塊 |
new_board | 畫板模塊 |
my_info | 個人信息模塊 |
preview | 預覽模塊 |
tuya | 塗鴉模塊 |
resource | 所用資源模塊 |
Weixin | 微信卡券開發模塊 |
3、系統流程圖設計
(三)詳細設計與實現
1、所用技術
開發 | 具體實現所用技術/工具 |
---|---|
微信小程序端所用框架 | 微信小程序原生框架MINA |
開發語言(微信小程序端) | JavaScript、wxml、wxss |
數據封裝格式 | json文件格式 |
後端所用框架 | springboot |
項目管理工具 | maven |
數據庫(主要是微信卡券實現) | mysql |
服務端開發所用語言 | java |
開發工具 | IDEA、微信開發者工具 |
開發主要參考 | 微信小程序開發者文檔 |
其他 | 墨刀、processOn、Ali矢量圖庫 |
2、具體模塊設計與實現
以下僅列出重要模塊實現。
- AR繪圖模塊開發
此模塊爲核心功能模塊,難點主要是畫圖功能的實現,以及如何呈現出AR畫布的效果,需要靈活運用微信官方給出的組件。
/***
AR繪圖功能實現
*/
//畫圖事件
pointData:{
begin_x:0,
begin_y:0,
end_x:null,
end_y:null,
},
//開始繪畫
start: function (e) {
var that = this;
that.setData({
showBgSet: false,
showPenSet: false,
});
that.pointData.begin_x = e.touches[0].x;
that.pointData.begin_y = e.touches[0].y;
if (that.data.isClear) { //判斷是否啓用的橡皮擦功能 ture表示清除 false表示畫畫
ctx.setStrokeStyle(that.data.canvasBgData.canvasBgColor); //設置線條樣式 此處設置爲畫布的背景顏色 橡皮擦原理就是:利用擦過的地方被填充爲畫布的背景顏色一致 從而達到橡皮擦的效果
ctx.setLineCap('round'); //設置線條端點的樣式
ctx.setLineJoin('round'); //設置兩線相交處的樣式
ctx.setLineWidth(20); //設置線條寬度
ctx.save(); //保存當前座標軸的縮放、旋轉、平移信息
ctx.beginPath(); //開始一個路徑
ctx.arc(that.pointData.begin_x, that.pointData.begin_y, 5, 0, 2 * Math.PI, true); //添加一個弧形路徑到當前路徑,順時針繪製 這裏總共畫了360度 也就是一個圓形
ctx.fill(); //對當前路徑進行填充
ctx.restore(); //恢復之前保存過的座標軸的縮放、旋轉、平移信息
} else {
ctx.setStrokeStyle(that.data.penData.color);
ctx.setLineWidth(that.data.penData.penSize);
ctx.setLineCap('round'); // 讓線條圓潤
ctx.beginPath();
}
ctx.moveTo(that.pointData.begin_x, that.pointData.begin_y);
ctx.lineTo(that.pointData.begin_x, that.pointData.begin_y);
ctx.stroke();
ctx.draw(true);
},
move: function (e) {
var that = this;
if (that.data.isClear) { //判斷是否啓用的橡皮擦功能 ture表示清除 false表示畫畫
ctx.save(); //保存當前座標軸的縮放、旋轉、平移信息
ctx.moveTo(that.pointData.begin_x, that.pointData.begin_y); //把路徑移動到畫布中的指定點,但不創建線條
ctx.lineTo(e.touches[0].x, e.touches[0].y); //添加一個新點,然後在畫布中創建從該點到最後指定點的線條
ctx.stroke(); //對當前路徑進行描邊
ctx.restore() //恢復之前保存過的座標軸的縮放、旋轉、平移信息
} else {
ctx.moveTo(that.pointData.begin_x, that.pointData.begin_y); //把路徑移動到畫布中的指定點,但不創建線條
ctx.lineTo(e.touches[0].x, e.touches[0].y); //添加一個新點,然後在畫布中創建從該點到最後指定點的線條
ctx.stroke(); //對當前路徑進行描邊
}
ctx.draw(true);
that.pointData.begin_x = e.touches[0].x;
that.pointData.begin_y = e.touches[0].y;
},
end: function (e) {
},
//以下爲自定義點擊事件
openBgSet:function(){
this.setData({
showBgSet:true,
isClear: false
})
},
setBgColor:function (e) {
var that = this;
wx.showModal({
title: '提示',
content: '設置背景將清空畫布',
success: function (res) {
if (res.confirm) {
var color = e.target.dataset.color;
that.data.canvasBgData.canvasBgColor = color;
ctx.rect(0, 0, 1000, 2000);
ctx.setFillStyle(color);
ctx.fill();
ctx.draw();
}
that.setData({
showBgSet: false
})
}
})
},
openPenSet: function () {
this.setData({
showPenSet: true,
isClear: false
})
},
setPenSize:function(e){
var data = this.data.penData;
data.penSize = e.detail.value;
this.setData({
penData: data
})
},
setPenColor:function(e) {
this.data.penData.color = e.target.dataset.color;
this.setData({
showPenSet: false
})
},
openErraserSet: function () {
this.setData({
isClear: !this.data.isClear
})
},
clearAll: function () {
var that = this;
wx.showModal({
title: '提示',
content: '確認清除畫板所有內容',
success: function (res) {
if (res.confirm) {
console.log('用戶點擊確定');
ctx.draw();
that.resetColor();
}
}
})
},
/**
* 保存自己所繪製的塗鴉
*/
下面是配置文件wxml部分關鍵代碼
<!--主要是用到了雙重canvas和相機組件-->
<camera class='myCamera' id='myCamera' device-position='back' flash='auto' >
<cover-view class="controls">
<cover-view class="play" bindtap="play">
<cover-image class="img" src="{{path}}" />
</cover-view>
</cover-view>
<canvas canvas-id="myCanvas" bindtouchstart="start" disable-scroll="true"
bindtouchmove="move" bindtouchend="end" class='canvas'>
</canvas>
</camera>
- 微信小程序人臉融合功能實現
該模塊主要是調用騰訊優圖AI開放平臺的
人臉融合功能,效果如下:
/**
* 人臉融合接口
* url:localhost:8080/face/facemerge/uploadFM
*/
@Slf4j
@Controller
@RequestMapping(value="/face_merge")
@Scope("prototype")
public class FaceMergeController extends FaceMergeBaseController{
/**
* 人臉融合
* @throws Exception
*/
@RequestMapping(value="/uploadFM",method= RequestMethod.POST)
/**
* 對用戶上傳的圖片進行處理
*/
public void UploadBDANIMAL()throws Exception{
TAipPtu aipPtu = new TAipPtu(AIConstant.QQ_AI_APPID,
AIConstant.QQ_AI_APPKEY);
String model = request.getParameter("model");
log.info("model的值是===="+model);
String result = "";
MultipartHttpServletRequest mpRequest = (MultipartHttpServletRequest)this.request;
Iterator iterator = mpRequest.getFileNames();
log.info(iterator.toString());
MultipartFile file = null;
while (iterator.hasNext()) {
file = mpRequest.getFile((String)iterator.next());
/**
* 如果上傳的file不爲空且size不爲0
* 進行人臉融合操作
*/
if ((file != null) && (file.getSize() != 0L)){
log.info("image不爲空");
byte[] image = file.getBytes();
log.debug("image",image);
/**
* 人臉融合結果
*/
String apiPtuResult = aipPtu.faceMerge(image,Integer.parseInt(model));
log.info(apiPtuResult);
/**
* 將返回的數據轉換爲json格式進行返回
*/
PrintUtil.printJson(this.response, apiPtuResult);
} else {
log.error("請檢查上傳文件是否正確");
result = "{\"result\", \"FAIL\",\"msg\":\"服務器出現了一些問題\"}";
PrintUtil.printJson(this.response, result);
}
}
}
}
- 其他模塊
參考我的github,項目地址爲:ARPainting項目
(四)參考文獻
【1】微信小程序官方文檔