utkayd Posted August 22, 2019 Share Posted August 22, 2019 Hello, I work at a startup that makes games for Children with special needs. I'm currently designing a coloring book app, and I set my mind on using Pixi.js for the task but I kinda am lost right now. The game should pretty much work like the scratchcard example(https://pixijs.io/examples/#/demos-advanced/scratchcard.js) except for when the user changes the current brush, the image to reveal should change but changing its texture directly changes the previously scratched parts, they should remain the previous image to reveal, so basically what I'm trying to do is to copy the current state of the canvas to the background and change the image to reveal but somehow I couldn't manage to pull off the task although it sounds fairly easy. I couldn't find my way out of the documentation either, so maybe if someone knows how to tackle this issue, I'd be very glad. The code I came up with so far is here, import { Point } from "../types/types"; import * as PIXI from "pixi.js"; import { tsParenthesizedType, thisExpression } from "@babel/types"; export default class Drawer2 { group: any; currentColor: string; currentWidth: number; onShape: boolean = false; //PIXI RELATED STUFF pixiApp: PIXI.Application; brush: PIXI.Graphics; renderTexture: PIXI.RenderTexture; historyRenderTexture: PIXI.RenderTexture; renderTextureSprite: PIXI.Sprite; historyRenderTextureSprite: PIXI.Sprite; renderContainer: PIXI.Container; dragging: boolean = false; imageToReveal: PIXI.Sprite; background: PIXI.Sprite; resources: any; constructor(color: string, width: number) { this.currentWidth = width; this.currentColor = color; this.pixiApp = new PIXI.Application({ width: window.innerWidth, height: window.innerHeight, }); console.log(this.pixiApp.stage); document.body.appendChild(this.pixiApp.view); this.brush = new PIXI.Graphics(); this.brush.beginFill(0xffffff); this.brush.drawCircle(0, 0, 50); this.brush.endFill(); this.pixiApp.loader.add("t2", "/textures/wrapping.jpeg"); this.pixiApp.loader.add("t1", "/textures/white.jpg"); this.background = new PIXI.Sprite(); this.imageToReveal = new PIXI.Sprite(); this.renderTexture = PIXI.RenderTexture.create(); this.historyRenderTexture = PIXI.RenderTexture.create(); this.renderTextureSprite = new PIXI.Sprite(); this.historyRenderTextureSprite = new PIXI.Sprite(); this.renderContainer = new PIXI.Container(); this.pixiApp.loader.load(this.preparePixi.bind(this)); } convertFromHexToNumericColor(color: string) { return parseInt(`0x${color.replace(/#/g, "")}`); } changeColor(color: string) { this.imageToReveal.tint = this.convertFromHexToNumericColor(color); this.brush.clear(); console.log(color); this.brush.beginFill(this.convertFromHexToNumericColor(color)); console.log(`Hex color:${this.convertFromHexToNumericColor(color)}`); this.brush.drawCircle(0, 0, 50); this.brush.endFill(); } changeWidth(width: number) { this.currentWidth = width; this.brush.width = this.currentWidth; this.brush.height = this.currentWidth; } pointerMove = (event: any) => { if (this.dragging) { this.brush.position.copyFrom(event.data.global); this.pixiApp.renderer.render( this.brush, this.renderTexture, false, undefined, false ); console.log("pointerMove"); } }; pointerDown = (event: any) => { this.dragging = true; this.pointerMove(event); console.log("pointerDown"); console.log(`Current brush with is ${this.brush.width}`); }; pointerUp = (event: any) => { this.dragging = false; console.log("pointerUp"); this.snap(); }; snap() { this.historyRenderTextureSprite.texture = this.renderTexture.clone(); this.renderTexture = PIXI.RenderTexture.create({ width: this.pixiApp.screen.width, height: this.pixiApp.screen.height, }); this.renderTextureSprite = new PIXI.Sprite(this.renderTexture); this.historyRenderTexture.update(); this.renderTexture.update(); } preparePixi(loader: any, resources: any) { this.resources = resources; this.background = new PIXI.Sprite(this.resources.t1.texture); this.background.name = "background"; this.imageToReveal.tint = this.convertFromHexToNumericColor( this.currentColor ); this.pixiApp.stage.addChild(this.background); this.background.width = this.pixiApp.screen.width; this.background.height = this.pixiApp.screen.height; this.imageToReveal = new PIXI.Sprite(this.resources.t1.texture); this.imageToReveal.name = "imageToReveal"; this.pixiApp.stage.addChild(this.imageToReveal); this.imageToReveal.width = this.pixiApp.screen.width; this.imageToReveal.height = this.pixiApp.screen.height; this.renderTexture = PIXI.RenderTexture.create({ width: this.pixiApp.screen.width, height: this.pixiApp.screen.height, }); this.historyRenderTexture = PIXI.RenderTexture.create({ width: this.pixiApp.screen.width, height: this.pixiApp.screen.height, }); this.renderTextureSprite = new PIXI.Sprite(this.renderTexture); this.historyRenderTextureSprite = new PIXI.Sprite( this.historyRenderTexture ); this.pixiApp.stage.addChild(this.historyRenderTextureSprite); this.pixiApp.stage.addChild(this.renderTextureSprite); this.imageToReveal.mask = this.renderTextureSprite; this.pixiApp.renderer.render( this.historyRenderTextureSprite, this.historyRenderTexture ); this.pixiApp.stage.interactive = true; this.pixiApp.stage.on("pointerdown", this.pointerDown); this.pixiApp.stage.on("pointerup", this.pointerUp); this.pixiApp.stage.on("pointermove", this.pointerMove); } } Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted August 22, 2019 Share Posted August 22, 2019 Hello, and welcome to the forums! I like drawing apps and i helped with several before. Basically, you want a textured brush, right? Graphics.beginTextureFill might help with that, you just have to clear() and drawCircle(x, y, r) every time you draw a brush. Usually I use multiple brushes (whole stroke!), and render() a container that has them - its more efficient per frame. There are other tricks, like, how to make transparent (alpha=0.5) brush and not override brushes in the same stroke. As for your original question - you have to wait some time before someone reponses, because my telepathic abilities aren't enough. My post was written without preparation, i didnt even look at your demo yet. Precise answer takes time to prepare. zafer and utkayd 1 1 Quote Link to comment Share on other sites More sharing options...
utkayd Posted August 23, 2019 Author Share Posted August 23, 2019 Hello Ivan, Thanks a lot for your response! Yes, exactly, I want a textured brush, but the problem is it doesn't move as it should. Above are the 2 pics I attach one showing it should, one showing how it currently is after I added the Graphics.beginTextureFill part. How it currently looks like https://ibb.co/SXZvqzC How it should look like: https://ibb.co/HKJP8PD Below is my code snippet of how I prepare the brush this.brush.clear(); this.brush.beginTextureFill(this.resources.t2.texture); this.brush.drawCircle(0, 0, 50); this.brush.endFill(); and how I update the x,y,r of drawCircle this.brush.position.copyFrom(event.data.global); // this.brush.clear(); this.brush.beginTextureFill(this.resources.t2.texture); this.brush.drawCircle( this.brush.position.x, this.brush.position.y, this.currentWidth ); this.brush.endFill(); Obviously, I'm missing something about your advice, but I can't see it. ivan.popelyshev 1 Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted August 23, 2019 Share Posted August 23, 2019 Usually its good to use "position" because it affects transform and not the shapes inside graphics. But in your case, you have to rebuild shape every time because UV's are changing, that means you have to change ONLY the shape, not the position. store position somewhere else, not in "position" field. this.brush.position.set(0, 0); this.brush.clear(); this.brush.beginTextureFill(this.resources.t2.texture); this.brush.drawCircle( event.data.global.x, event.data.global.y, this.currentWidth ); this.brush.endFill(); If you want to apply brush multiple times you can just drawCircle multiple points in it. I'm not saying that its best practice, but just if you understand how transforms and texture shift work (and they work like in a Adobe Flash), then this method is probably the best for the case. There's also "matrix" param in beginTextureFill that can help you . Of course when you experiment more, you can start using position + custom matrix inside beginTextureFill but at this point im afraid to suggest that, it will only confuse you. zafer and utkayd 1 1 Quote Link to comment Share on other sites More sharing options...
utkayd Posted August 23, 2019 Author Share Posted August 23, 2019 Worked like a charm! You are a lifesaver, Ivan, thanks a lot. zafer and ivan.popelyshev 2 Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted August 23, 2019 Share Posted August 23, 2019 Thanks for kind words! Hope that helps you in future when you'll need to make more operations with graphic shapes, transforms and texture fills zafer 1 Quote Link to comment Share on other sites More sharing options...
Moppel Posted August 23, 2019 Share Posted August 23, 2019 For my app I also implemented freehand drawing but I use a two-stage approach: first I print circles on a renderTexture (that covers the viewport) wherever the cursor goes, and once the user emits a mouseup-event, I re-render the whole thing on a regular layer using a non-closed polygon along with simplify.js to reduce the number of vertices. For really quick movements there were 'gaps' in my implementation (since the mouse-emit-rate seems limited), so I also used interpolation to make that smooth. Works also really nice ivan.popelyshev 1 Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted August 23, 2019 Share Posted August 23, 2019 ^^^ Good approach. Do you want to publish simple demo of this drawer later? We lack of good drawing apps examples. https://github.com/wonderunit/alchemancy uses complex filter for brush and i cant explain its positioning properly Quote Link to comment Share on other sites More sharing options...
Moppel Posted August 23, 2019 Share Posted August 23, 2019 Its a little bit spaghetti code in my app (Angular + Akita + PIXI.js) its a part of a robotics application that I am not allowed to share entirely (would also not work without the backend)) but I can strip out the drawing part and assemble a small demo for the community ivan.popelyshev 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.