utkayd

Need some help with a Free Drawing App

Recommended Posts

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);
  }
}

 

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

 

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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 :)

Share this post


Link to post
Share on other sites

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 :)

Share this post


Link to post
Share on other sites

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.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.