Canvas linear gradient taking the value of another input type color field

So I have this simple image editor where I’m using a canvas to draw in a user-selected image and some texts. That is the user can upload an image and then if they want they can add texts or just change the gradient of the image.

Right now the app works perfectly fine except there is one issue.

How to find the issue? Do the following:

  1. Upload a random image.
  2. Choose a color from Text 2 Shadow Offset X Color Picker.
  3. Increase the slider beside the Text 2 Shadow Offset X Color Picker.
  4. Then increase the slider of Image Gradient and Opacity.

The image will have a gradient and take the value of the Text 2 Shadow Offset X Color Picker instead of Image Gradient and Opacity Color Picker which has default black color.

This is the code:

const canvasTxt                 = window.canvasTxt.default;
const canvas                    = document.getElementById('canvas');
const ctx                       = canvas?.getContext('2d');
const btnDownload               = document.querySelector('.btnDownload');
const fileUpload                = document.querySelector('.file-upload');

const text1                     = document.getElementById('text1');
const textForm1                 = document.getElementById('text1-form');
const text2                     = document.getElementById('text2');
const textForm2                 = document.getElementById('text2-form');
const text2ShadowColor          = document.getElementById('text2shadowcolor');
const text2ShadowOffsetY        = document.getElementById('text2shadowoffy');

const imageForm                 = document.getElementById('image-form');
const imageGrad                 = document.getElementById('gradientcolor');
const imageGradOpacity          = document.getElementById('gradientopacity');


$(fileUpload).on('change', function(e) {
      let imgObj          = new Image();
      imgObj.onload       = draw;
      imgObj.onerror      = failed;
      imgObj.src          = URL.createObjectURL(this.files[0]);

      imgManipulation( e, imgObj );
});    

const imgManipulation = ( e, imgObj ) => {
    $(textForm1).on('change keyup input', updateCanvas);
    $(textForm2).on('change keyup input', updateCanvas);
    $(imageForm).on('change keyup input', updateCanvas);

    function updateCanvas(e) {
        e.preventDefault();
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(imgObj, 0, 0);

        createGradient($(imageGrad).val(), $(imageGradOpacity).val());

  
        // TEXT1 STYLES based on user input
        canvasTxt.fontSize      = 30;
        canvasTxt.drawText(
            ctx, 
            $(text1).val(), 
            0, 
            0, 
            200, 
            200
        );


        // TEXT2 STYLES
        canvasTxt.font          = 20;

        canvasTxt.drawText(
            ctx, 
            $(text2).val(),
            20, 
            20, 
            200, 
            200
        );
    }
};

function hexToRgb(hex) {
    var result = /^#?([a-fd]{2})([a-fd]{2})([a-fd]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16)
    } : null;
};

function createGradient(hex, alpha) {

    const r = hexToRgb(hex).r.toString();
    const g = hexToRgb(hex).g.toString();
    const b = hexToRgb(hex).b.toString();

    var gradient =  ctx.createLinearGradient(800, 0, 0, 0);
    gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${alpha})`);

    ctx.save() // <----------- ADD
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.restore() // <----------- ADD
};


function draw() {
    canvas.width        = this.naturalWidth;
    canvas.height       = this.naturalHeight;
    const nw            = this.naturalWidth;
    const nh            = this.naturalHeight;

    ctx.drawImage(this, 0, 0, nw, nh);
};

function failed() {
    console.error("The provided file couldn't be loaded as an Image media");
};


$(btnDownload).on('click', function(e) {
    const a = document.createElement('a');
    document.body.appendChild(a);
    a.href = canvas.toDataURL();
    a.download = "canvas-image.png";
    a.click();
    document.body.removeChild(a);
});    
#canvas{
    background-color: transparent; 
    width: 30%; 
    height: auto;
    border: 1px solid #777;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/[email protected]/build/index.js"></script>

<canvas id="canvas" width="800" height="500"></canvas>

<div>
    <input type="file" class="file-upload" />
    <button class="btnDownload">Download</button>
</div>


<div>
  <form id="text1-form">
    <input type="text" id="text1" placeholder="text 1"/> 
  </form>
</div>

<div>
  <form id="text2-form">
    <input type="text" id="text2" placeholder="text 2"/> 
  </form>
</div>

<div>
  <h2>Image Gradient and Opacity</h2>
  <form id="image-form">
    <input type="color" id="gradientcolor" value="#000000" />
    <input type="range" id="gradientopacity" min="0" max="1" value="0" step="0.05" />
  </form>
</div>


<div>
  <h2>Text2 Shadow Offset X</h2>
  <input type="color" id="text2shadowcolor" value="#000000" />
  <input type="range" id="text2shadowoffy" min="0" max="40" value="0" />
</div>

Rundown of the code:

1: First I have the fileUpload event listener. It takes in an image from the user and creates an image object and draws it on the canvas. Then imgManipulation function is called with the imgObj and event as its arguments.

  1. The imgManipulation function starts off with the input event listeners for the texts. That is whenever there is a change in the input, that is a user writes something, updateCanvas function is called.

  2. The updateCanvas function actually draws the texts on the image. I’m using a package called canvasTxt which helps the text to be multiline.

  3. The createGradient function call inside updateCanvas is the image gradient for the image.

I tried moving the createGradient function call below the drawTexts but then the gradient comes to the top of everything. The texts become darker too.

How can I make the gradient take it’s value only instead of shadow color?

Your help would be highly appreciated.

Thanks in advance

Answer

The shadowColor still work when drawing ‘Gradient’ next time. Add ctx.save() before, and ctx.restore() after.

const canvasTxt                 = window.canvasTxt.default;
const canvas                    = document.getElementById('canvas');
const ctx                       = canvas?.getContext('2d');
const btnDownload               = document.querySelector('.btnDownload');
const fileUpload                = document.querySelector('.file-upload');

const text1                     = document.getElementById('text1');
const textForm1                 = document.getElementById('text1-form');
const text2                     = document.getElementById('text2');
const textForm2                 = document.getElementById('text2-form');
const text2ShadowColor          = document.getElementById('text2shadowcolor');
const text2ShadowOffsetY        = document.getElementById('text2shadowoffy');

const imageForm                 = document.getElementById('image-form');
const imageGrad                 = document.getElementById('gradientcolor');
const imageGradOpacity          = document.getElementById('gradientopacity');


$(fileUpload).on('change', function(e) {
      let imgObj          = new Image();
      imgObj.onload       = draw;
      imgObj.onerror      = failed;
      imgObj.src          = URL.createObjectURL(this.files[0]);

      imgManipulation( e, imgObj );
});    

const imgManipulation = ( e, imgObj ) => {
    $(textForm1).on('change keyup input', updateCanvas);
    $(textForm2).on('change keyup input', updateCanvas);
    $(imageForm).on('change keyup input', updateCanvas);

    function updateCanvas() {
        e.preventDefault();
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(imgObj, 0, 0);

        createGradient($(imageGrad).val(), $(imageGradOpacity).val());

  
        // TEXT1 STYLES based on user input
        canvasTxt.fontSize      = 30;
        canvasTxt.drawText(
            ctx, 
            $(text1).val(), 
            0, 
            0, 
            0, 
            0
        );


        // TEXT2 STYLES
        ctx.save() // <----------- ADD
        ctx.shadowColor         = $(text2ShadowColor).val();
        ctx.shadowOffsetY       = $(text2ShadowOffsetY).val();
        canvasTxt.font          = 20;

        canvasTxt.drawText(
            ctx, 
            $(text2).val(),
            20, 
            20, 
            0, 
            0
        );
        ctx.restore() // <----------- ADD
    }
};

function hexToRgb(hex) {
    var result = /^#?([a-fd]{2})([a-fd]{2})([a-fd]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16)
    } : null;
};

function createGradient(hex, alpha) {

    const r = hexToRgb(hex).r.toString();
    const g = hexToRgb(hex).g.toString();
    const b = hexToRgb(hex).b.toString();

    var gradient =  ctx.createLinearGradient(800, 0, 0, 0);
    gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${alpha})`);

    ctx.save() // <----------- ADD
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.restore() // <----------- ADD
};


function draw() {
    canvas.width        = this.naturalWidth;
    canvas.height       = this.naturalHeight;
    const nw            = this.naturalWidth;
    const nh            = this.naturalHeight;

    ctx.drawImage(this, 0, 0, nw, nh);
};

function failed() {
    console.error("The provided file couldn't be loaded as an Image media");
};


$(btnDownload).on('click', function(e) {
    const a = document.createElement('a');
    document.body.appendChild(a);
    a.href = canvas.toDataURL();
    a.download = "canvas-image.png";
    a.click();
    document.body.removeChild(a);
});    
#canvas{
    background-color: transparent; 
    width: 30%; 
    height: auto;
    border: 1px solid #777;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://unpkg.com/[email protected]/build/index.js"></script>

<canvas id="canvas" width="800" height="500"></canvas>

<div>
    <input type="file" class="file-upload" />
    <button class="btnDownload">Download</button>
</div>


<div>
  <form id="text1-form">
    <input type="text" id="text1" placeholder="text 1"/> 
  </form>
</div>

<div>
  <form id="text2-form">
    <input type="text" id="text2" placeholder="text 2"/> 
  </form>
</div>

<div>
  <h2>Image Gradient and Opacity</h2>
  <form id="image-form">
    <input type="color" id="gradientcolor" value="#000000" />
    <input type="range" id="gradientopacity" min="0" max="1" value="0" step="0.05" />
  </form>
</div>


<div>
  <h2>Text2 Shadow Offset X</h2>
  <input type="color" id="text2shadowcolor" value="#000000" />
  <input type="range" id="text2shadowoffy" min="0" max="40" value="0" />
</div>

Leave a Reply

Your email address will not be published. Required fields are marked *