I wanted to write a small mobile app for pictures for a long time. Recently, I was on vacation at home, so I took some time to realize my small idea

Application show

screenshots

The online preview

demand

Since it’s a small application, you want the final product to have a applicable scenario and be valuable

Source of demand

The inspiration for this application is that in our previous work and life, we often accidentally got beautiful photos of our colleagues. At this time, we wanted to make this photo into an emoticon package. Generally, we added a few captions to the picture, and an interesting communication tool (emoticon package) was completed

Demand analysis

The dismantling of the application based on the above requirements can be sorted out

  • Users need to upload an image
  • I can add text
  • Text can be styled and rotated to scale
  • We also want to insert some textures
  • Maps can be rotated and scaled
  • Users export pictures to albums

implementation

Making warehouse github.com/luosijie/fa…

If you like my project, you are welcome to give me a star

This application was developed with applets

  • Use the framework: MPX
  • Using technology: applet Canvas

State management

Import {createStore} from '@mpxjs/core' const store = createStore({state: {cavas: null, // cnavas instance CTX: Elements: [], // Canvas element activeIndex: null, // Canvas element activeIndex: null, // Opacity: 1, fillStyle: '#000000', strokeStyle: '#000000' } }, mutations: { setCanvas (state, data) { state.canvas = data }, setCtx (state, data) { state.ctx = data }, setElements (state, data) { state.elements = data }, setMode (state, data) { state.mode = data }, setActiveIndex (state, data) { state.activeIndex = data }, setFontStyle (state, { key, data }) { state.fontStyle[key] = data }, // addText (state) {const size = 50 const string = 'const text = {type: 'text', data: string, scale: 1, size, left: 100, top: 100, rotate: 0, opacity: state.fontStyle.opacity, fillStyle: state.fontStyle.fillStyle, strokeStyle: state.fontStyle.strokeStyle } state.elements.push(text) state.activeIndex = state.elements.length - 1 }, AddSticker (state, data) {state.elements. Push (data) state.activeIndex = state.elements. Length - 1}, DeleteActiveELement (state) {state.elements. Splice (state.activeIndex, 1) state.activeIndex = null}, Clear (state) {state.elements = [] state.activeIndex = null}}}) export default StoreCopy the code

Canvas initialization

// Initialize canvas async initCanvas() {const query = this.createsElectorQuery () query.select ('#canvas').fields({node: true, size: true }) .exec(async res => { const canvas = res[0].node const ctx = canvas.getContext('2d') store.commit('setCanvas', canvas) store.commit('setCtx', ctx) await this.loadImage('/images/icon-rotate.png').then(res => { this.image.rotate = res }) canvas.width = res[0].width * this.dpr canvas.height = res[0].height * this.dpr ctx.scale(this.dpr, this.dpr) this.drawGrid() }) }Copy the code

Draw pictures

@param {Object} ele canvas element */ drawImage(ele) {this.ctx.save() const width = ele.width const height = ele.height const centerX = ele.left + ele.width / 2 const centerY = ele.top + ele.height / 2 this.ctx.translate(centerX,  centerY) this.ctx.rotate(ele.rotate) this.ctx.drawImage(ele.data, ele.left - centerX, ele.top - centerY, width, height) this.ctx.restore() }Copy the code

Rendering text

@param {Object} ele Canvas element */ drawText(ele) {this.ctx.save() const width = ele.size * ele.data.length const height = ele.size const centerX = ele.left + width / 2 const centerY = ele.top + height / 2 this.ctx.translate(centerX, centerY) this.ctx.rotate(ele.rotate) this.ctx.font = `${ele.size}px bold sans-serif` this.ctx.globalAlpha = ele.opacity this.ctx.fillStyle = ele.fillStyle this.ctx.strokeStyle = ele.strokeStyle // this.ctx.lineWidth = 2 this.ctx.textBaseline = 'top' console.log('draw-text', ele) this.ctx.fillText(ele.data, ele.left - centerX, ele.top - centerY) this.ctx.strokeText(ele.data, ele.left - centerX, ele.top - centerY) this.ctx.restore() }Copy the code

Draw control element

initController(ele) { const cs = this.convert2ControllerSize(ele) this.ctx.save() this.ctx.strokeStyle = '#eee' This.ctx. rotate(cs.rotate) this.ctx.rotate(cs.rotate) this.ctx.setLineDash([10, 5], 5) this.ctx.strokeRect(cs.left - cs.centerX, cs.top - cs.centerY, cs.width, Rotate this.ctx.drawImage(this.image. Rotate, cs.left + cs.width-10-cs.centerx, rotate this.image. cs.top + cs.height - 10 - cs.centerY, 20, 20) this.ctx.restore() }Copy the code

Canvas rendering function

RenderCanvas () {this.ctx.clearRect(0, 0, this.ctx.canvas. this.ctx.canvas.height) this.drawGrid() console.log('draw-background', this.background) if (this.background) this.drawImage(this.background) for (let i = 0; i < this.elements.length; I ++) {const ele = this.elements[I] // Render background if (ele. Type === 'background') {this.drawimage (ele)} if (ele. Type === = 'sticker') {this.drawimage (ele)} // Render text if (ele. Type === 'text') {this.drawtext (ele)} // select element and add control element if (this.activeIndex === i) { this.initController(ele) } } }Copy the code

Event listeners

mobile

// handleMove(e) {console.log('mouse-move', e) if (e.touches.length > 1) return const x = e.touches[0].x const y = e.touches[0].y const dx = this.startTouches[0].x - x const dy = this.startTouches[0].y - y const elements = this.elements.slice() elements[this.activeIndex || 0].left = this.startSelected.left - dx elements[this.activeIndex || 0].top = this.startSelected.top - dy store.commit('setElements', elements) }Copy the code

rotating

HandleRotate (e) {console.log('handleRotate') const start = this.starttouches [0] const end = e.touches[0] const center = { x: this.startSelected.centerX, y: this.startSelected.centerY } const startLength = Math.sqrt((center.x - start.x) ** 2 + (center.y - start.y) ** 2) const endLength = Math.sqrt((center.x - end.x) ** 2 + (center.y - end.y) ** 2) const radian = this.convert2Radian(start, end, center) const scale = endLength / startLength const elements = this.elements.slice() const selected = Elements [this activeIndex] / / rotate selected. Rotate = this. StartSelected. Rotate radian / / scaling the if (selected. Type = = = 'text') { selected.left = this.startSelected.centerX - this.startSelected.size * this.startSelected.data.length * scale / 2 selected.top = this.startSelected.centerY - this.startSelected.size * scale / 2 selected.size = this.startSelected.size * scale } if (selected.type === 'sticker') { selected.left = this.startSelected.centerX - this.startSelected.width * scale / 2 selected.top = this.startSelected.centerY - this.startSelected.height * scale / 2 selected.width = this.startSelected.width * scale selected.height = this.startSelected.height * scale } store.commit('setElements', elements) }Copy the code

The zoom

HandleScale (e) {if (e.touches.length! == 2 || this.mode ! == 'background') return const startLength = Math.sqrt( (this.startTouches[0].x - this.startTouches[1].x) ** 2 + (this.startTouches[0].y - this.startTouches[1].y) ** 2 ) const endLength = Math.sqrt( (e.touches[0].x - e.touches[1].x) ** 2 + (e.touches[0].y - e.touches[1].y) ** 2 ) const scale = endLength / startLength const elements = this.elements.slice() const selected = elements[this.activeIndex || 0] selected.left = this.startSelected.centerX - this.startSelected.width * scale / 2 selected.top = this.startSelected.centerY - this.startSelected.height * scale / 2 selected.width = this.startSelected.width * scale selected.height = this.startSelected.height * scale // elements[this.activeIndex || 0].scale = this.startSelected.scale * scale store.commit('setElements', elements) }Copy the code

Export images

export() { if (! Store. State. Elements. Length) {wx. ShowToast ({title: 'put some things to export, icon:' none '}) return} wx. ShowModal ({title: 'Hint ', content: 'Images will be saved to mobile album ', success(res) {if (res.confirm) {console.log('export-canvas', store.state.ctx) const canvas = store.state.canvas wx.canvasToTempFilePath({ x: 0, y: 0, width: canvas.width, height: canvas.height, canvas, complete(res) { if (res.errMsg === 'canvasToTempFilePath:ok') { wx.saveImageToPhotosAlbum({ filePath: Res.tempfilepath, success(res) {wx.showtoast ({title: 'saved successfully ', icon:' None '})}})}}})}})}Copy the code

The project continues to update and optimize, interested friends can pay attention to it, exchange and learn together

Making warehouse github.com/luosijie/fa…