如何在移動應用中實現AI畫圖?

比亞的答案發表於2023-04-11

最近AIGC 簡直是殺瘋了,領導動不動就讓我們在APP 裡引入大語言模型,引入AI畫圖……說搞就搞!本期基於最近在app 裡引入AI畫圖小程式的操作,給大家做一波實踐分享。

Scribble Diffusion 是一個簡單的線上服務,它使用 AI 將粗略的草圖轉換為精緻的影像,每一張影像都是不同的(而且沒有版權困擾)。簡單來說,我們只需要「用畫筆描繪一張草圖,在輸入描述後稍等片刻」,隨後就會為你生成一幅畫。這幅畫可以多次生成,每次生成的結果也都大不相同。

Scribble Diffusion 的能力大概是這樣的(左邊是我畫的,右邊是 TA 畫的)

a photo of grassland with cloud(有云朵的草地) the sun setting behind the mountains(山後落日) A lovely kitten(小貓咪呀)

我發現 Scribble Diffusion 作畫的能力非常出乎意料,而且可以根據你的描述來定義不同的照片風格(比如照片,油畫,素描等等),於是就產生了把這個AI 畫圖小程式內嵌到公司App中的想法。另外把小程式內嵌App是透過 FinClip 容器技術實現的。(畢竟開箱即用,也不需要做什麼配置)。

調研官網之後發現官網中的元素非常簡單,正因如此,我覺得把「Scribble Diffusion」搬運到 小程式裡大概要分這樣幾步:

  1. 使用 canvas 實現畫板,能夠在小程式中進行繪畫;
  2. 提供輸入框與生成按鈕,能夠補充圖片的描述和生成的按鈕;
  3. 獲取生成的圖片連結進行展示;

像那些如何寫程式碼,賬號註冊和建立小程式的流程,各位看官可以看這裡:

從零到一,我也能寫小程式

看了這篇文章,即使讓我現在就從頭寫一個能夠正常執行的小程式,也沒有原本想象中的那麼難了。  

使用小程式實現畫板

我們可以使用小程式來實現一個畫板,使用 canvas 標籤實現畫筆功能。使用者可以在畫板上繪製畫作,也可以選擇清空畫板操作。

下面是一個示例程式碼:

<!--畫布區域--> <view>     <canvas id="myCanvas" canvas-id="myCanvas"         disable-scroll="false"         bindtouchstart="touchStart"         bindtouchmove="touchMove"         bindtouchend="touchEnd">     </canvas> </view> <view bindtap="reset">   清空畫板 </view>
Page({   data: {     isProcessing: false,     prompt: '',     scribble: null,     pen : 2, //畫筆粗細預設值     color : '#000000', // 畫筆顏色預設值     result: null,     text: ''   },   startX: 0, //儲存X座標軸變數   startY: 0, //儲存X座標軸變數   onLoad(params) {     wx.createSelectorQuery().select('#myCanvas').context((res) => {       this.context = res.context     }).exec()   },   //手指觸控動作開始   touchStart: function (e) {       //得到觸控點的座標       this.startX = e.changedTouches[0].x       this.startY = e.changedTouches[0].y       // this.context = wx.createContext()       this.context.setStrokeStyle(this.data.color)       this.context.setLineWidth(this.data.pen)       this.context.setLineCap('round') // 讓線條圓潤        this.context.beginPath()   },   //手指觸控後移動   touchMove: function (e) {       var startX1 = e.changedTouches[0].x       var startY1 = e.changedTouches[0].y       this.context.moveTo(this.startX, this.startY)       this.context.lineTo(startX1, startY1)       this.context.stroke()       this.startX = startX1;       this.startY = startY1;                       //只是一個記錄方法呼叫的容器,用於生成記錄繪製行為的actions陣列。context跟<canvas/>不存在對應關係,一個context生成畫布的繪製動作陣列可以應用於多個<canvas/>       wx.drawCanvas({          canvasId: 'myCanvas',          reserve: true,          actions: this.context.getActions() // 獲取繪圖動作陣列       })   },   //手指觸控動作結束   touchEnd: function () {     var imageData =  wx.canvasGetImageData({       canvasId: 'myCanvas',       height: 250,       width: 250,       x: 0,       y: 0,       success(res){         return res.data       }     })   },   //清除畫板   reset: function(){     this.context.clearRect(0, 0, 400, 400);     this.context.draw(true)   } })

提供輸入框和生成按鈕

我們需要提供一個 input 輸入框,供使用者輸入 prompt;同時,我們需要提供一個按鈕,點選時會觸發響應事件,將 canvas 內容生成圖片,同時將 prompt 輸入作為引數,提交給服務端進行圖片生成。

這裡是示例程式碼:

<!-- 輸入框 --> <view>    <view>      <input type="text" name="go"  placeholder="用關鍵詞描述畫的內容" bindinput="update"/>   </view> </view>
Page({   ... 省略上述程式碼   // 更新表單提交按鈕狀態   update(e){     this.setData({       prompt : e.detail.value     })   }, })

獲取生成的圖片連結並展示

當使用者點選生成圖片按鈕後,我們會將 canvas 內容和使用者輸入的 prompt 作為引數提交給服務端進行圖片生成。服務端會返回生成的圖片連結,我們需要將它展示給使用者。

在下面的示例程式碼中,我們服務端傳送 POST 請求,然後解析返回的 JSON 資料,獲取圖片連結,並將其新增到頁面中。使用者就可以看到生成的圖片了。

<!-- 繪圖結果 --> <view wx:if="{{result}}">   <view>     <view>       <image src="{{result}}" mode="aspectFit" />      </view>     <view>       <view bindtap="download">         下載       </view>     </view>   </view> </view>
Page({   ... 省略上述程式碼   async getCanvasImage() {     return new Promise((resolve, reject) => {       wx.canvasToTempFilePath({         x: 0,         y: 0,         width: 250,         height: 250,         destWidth: 250,         destHeight: 250,         canvasId: 'myCanvas',         success(res) {           console.log(res.tempFilePath)           resolve(res.tempFilePath)         },         fail(err) {           console.log(err)         }       })     })   },   async upload(image) {     return new Promise((resolve) => {       wx.uploadFile({         url: 'xxxxxx',         filePath: image,         name: 'file',         success (res){           const data = JSON.parse(res.data)           resolve(data.url)         },         fail(err){           console.log('上傳失敗')           console.log(err)         }       })     })   }, async sleep(time) {     return new Promise((resolve) => {       setTimeout(() => {         resolve()       }, time)     })   },   async handleSubmit(e) {     if (!this.data.prompt) {       return     }     wx.showLoading({       title: '生成中',     })     try {       const prompt = this.data.prompt       const image = await this.getCanvasImage()       this.setData({         error: null,         isProcessing: true       });       const url = await this.upload(image)       console.log('圖片', url)       const body = {         prompt: prompt,         image: url,       };       const response = await my_fetch.fetch( {         url: "{           "Content-Type": "application/json",         },         params: JSON.stringify(body),       });       let prediction = response.data;       console.log('預測', prediction)       if (response.statusCode !== 201) {         wx.showToast({           title: '生成失敗',           duration: 2000         })         this.setData({           error: '生成失敗'         });         return;       }       while (         prediction.status !== "succeeded" &&         prediction.status !== "failed"       ) {         console.log(prediction.status)         await this.sleep(500);         const response = await my_fetch.fetch({           url:"});         prediction = response.data;         if (response.statusCode !== 200) {           this.setData({             error: prediction.detail           });           return;         }       }       if (Array.isArray(prediction.output) && prediction.output.length > 1) {         wx.hideLoading()         this.setData({           isProcessing: false,           result: prediction.output[1]         });       } else {         wx.hideLoading()         wx.showToast({           title: '生成失敗',           duration: 2000         })         this.setData({           isProcessing: false,           error: '生成失敗'         })       }      } catch (error) {       wx.hideLoading()       console.log(error)       wx.showToast({         title: '生成失敗',         duration: 2000       })      }   }, })

生成完小程式之後,再透過  FinClip 上傳小程式就可以在 App 獲得畫板功能啦!我把這個小程式上傳到了 中,你可以掃描下方的二維碼隨意體驗,總的來說,還是挺好玩的。

如果你對小程式與 AI 的結合有什麼奇思妙想,歡迎留言探討!

當然,如果你對   有更多奇怪的想法想付諸實踐,開發者也已經將專案檔案進行了開源,歡迎嘗試~


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70017183/viewspace-2945016/,如需轉載,請註明出處,否則將追究法律責任。

相關文章