#StackBounty: #html #canvas #html5-video Capturing a sectioned image from a canvas with responsive layout

Bounty: 100

I have a canvas with various div elements serving as boxes that represent areas of the canvas to capture. When the capture takes place, it isn’t exactly as displayed on the screen. How can I track and responsively capture the div sections as they appear?

As an example, attempting to capture the large purple section:

enter image description here

enter image description here

Here is the code from the component:

<template>
  <div>
    <div class="video-wrapper">
      <canvas ref="the-canvas" class="canvas">
      </canvas>
      <video class="video" ref="video" autoplay />
      <div :class="profileCenter.name" :style="profileToStyle(profileCenter)" />
      <div :class="profileTopLeft.name" :style="profileToStyle(profileTopLeft)" />
      <div :class="profileTopMiddle.name" :style="profileToStyle(profileTopMiddle)" />
      <div :class="profileTopRight.name" :style="profileToStyle(profileTopRight)" />
    </div>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Watch } from 'vue-property-decorator'
import Header from '@/components/Header.vue'

@Component({ components: { Header } })
export default class Test extends Vue {
  private userMedia: MediaStream | null = null
  private winHeight: number = window.innerHeight
  private winWidth: number = window.innerWidth

  private profileCenter: any = { name: 'profile-box', height: 350, width: 250, top: 40, left: 50, transform: [-50, -50] }
  private profileTopLeft: any = { name: 'profile-box', height: 100, width: 100, top: 20, left: 20, transform: [-50, -50] }
  private profileTopMiddle: any = { name: 'profile-box', height: 100, width: 100, top: 20, left: 50, transform: [-50, -50] }
  private profileTopRight: any = { name: 'profile-box', height: 100, width: 100, top: 20, left: 80, transform: [-50, -50] }

  async mounted () {
    this.$nextTick(() => {
      window.addEventListener('resize', () => {
        this.updateSizes()
      })
    })
    this.userMedia = await navigator.mediaDevices.getUserMedia({ video: true, audio: false })

    setTimeout(() => {
      const video: HTMLVideoElement = document.getElementsByClassName('video')[0] as HTMLVideoElement
      const canvas: HTMLCanvasElement = document.getElementsByClassName('canvas')[0] as HTMLCanvasElement
      const targetSquare: HTMLDivElement = document.getElementsByClassName('profile-box')[0] as HTMLDivElement
      const targetDims = targetSquare.getBoundingClientRect()
      canvas.height = targetDims.height
      canvas.width = targetDims.width
      console.log(canvas.width, canvas.height)
      console.log(video.videoWidth, video.videoHeight)
      const ctx = canvas.getContext('2d')
      ctx!.drawImage(video, targetDims.left, targetDims.top, targetDims.width, targetDims.height, 0, 0, targetDims.width, targetDims.height)
      window.open(canvas.toDataURL())
    }, 3000)
  }

  private updateSizes (): void {
    this.winHeight = window.innerHeight
    this.winWidth = window.innerWidth
  }

  private profileToStyle (profile: { height: number, width: number, top: number, left: number, transform: number[] }): string {
    return `height: ${profile.height}px; min-height: ${profile.height}px; width: ${profile.width}px; min-width: ${profile.width}px; top: ${profile.top}%; left: ${profile.left}%; transform: translate(${profile.transform[0]}%, ${profile.transform[1]}%)`
  }

  @Watch('userMedia')
  private userMediaWatcher () {
    this.$refs.video.srcObject = this.userMedia
  }
}
</script>

<style>
.container--fluid {
  height: 100%;
  overflow: hidden;
}

.video-wrapper {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
  height: 100%; 
  overflow: hidden;
}

.video-wrapper video {
  min-width: 100%; 
  min-height: 100%; 

  width: auto;
  height: auto;

  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

.canvas {
  position: absolute;
  /* width: 480px; */
  /* height: 640px; */
}

.profile-box{
  position: absolute;
  border: 2px solid purple;
}
</style>


Get this bounty!!!

#StackBounty: #canvas #chart.js ChartJS add text to canvas in linechart

Bounty: 500

I have a few linecharts all pulling data from MariaDB, which is populated from my Rpi Zero weatherstation. So far i have had a secondary yaxis for displaying todays highest and lowest values, but I would rather have it placed as 2 lines in the topleft corner of the charts. I have been trying several approaches found here and on chartjs documentation, but to no avail. How would I go about it to make the below code show text on the canvas?

<html>
<head>
<title>Temperatur</title>
<style type="text/css">
body {
  font-family: Roboto;
  background-color: #242e42;
  color: white;
  width: 98%;
    }

#chart-container {
    width: 100%;
    height: 100%;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/0.7.0/chartjs-plugin-datalabels.min.js"></script>

</head>
<body>
    <div id="chart-container">
        <canvas id="tempgraphCanvas" style="width:100%;height:100%;"></canvas>
    </div>
    <script>
        $(document).ready(function () {
            showtempGraph();
        });

        function showtempGraph()
        {
            {
                $.post("temperaturedata.php",
                function (data)
                {
                    console.log(data);
                    var temptime = [];
                    var temp = [];

                    for (var i in data) {
            temptime.push(data[i].timestamp);
            temp.push(data[i].temperature);
                    }

        var tempmin = Math.min(...temp);
        var tempmax = Math.max(...temp);

                    var charttempdata = {
            labels: temptime,
            datasets: [
            {
                label: 'Temperatur',
                pointRadius: 3,
                backgroundColor: 'rgba(26, 137, 245, 0.2)',
                borderColor: 'rgba(26, 137, 245, 1)',
                hoverBackgroundColor: 'rgba(255, 255, 255, 0)',
                hoverBorderColor: 'rgba(255, 255, 255, 0)',
                pointBackgroundColor: 'rgba(12, 68, 122,1)',
                pointHoverBorderColor: "rgba(255, 255, 255, 0.8)",
                data: temp,
                datalabels: {
                    align: function(context) {
                        return context.active ? 'left' : 'left';                    
                    }
                }
            }
            ]
                    };

                    var graphtempTarget = $("#tempgraphCanvas");

                    var linetempGraph = new Chart(graphtempTarget, {
            type: 'line',
            data: charttempdata,
            options: {
                plugins: {
                    datalabels: {
                        backgroundColor: null,
                        borderColor: null,
                        borderRadius: function(context) {
                            return context.active ? 0 :0;
                        },
                        borderWidth: 1,
                        color: 'rgba(255, 255, 255, 1)',
                        font: {
                            size: 18,
                            color: 'rgba(255, 255, 255, 1)'
                        },
                        formatter: function(value, context) {
                            value = Math.round(value * 100) / 100;
                            if (context.dataIndex === context.dataset.data.length - 1) {
                                return value + '°C';
                            } else {
                                return context.active
                                    ? value + '°C'
                                    : ''
                                }
                        },
                        offset: 8,
                        padding: 0,
                        textAlign: 'center'
                    }
                },
                maintainAspectRatio: false,
                tooltips: {
                    enabled: false,
                },
                legend: {
                    display: false,
                },
                responsive: true,
                scales: {
                    xAxes: [{
                        type: 'time',
                        time: {
                            displayFormats: {
                                hour: 'HH:mm'
                            },
                            tooltipFormat: 'HH:mm',
                        },
                        unit : 'day',
                        gridLines: {
                            color: '#999999',
                            lineWidth: 1
                        },
                        ticks: {
                            fontColor: "#fff",
                        }                               
                    }],
                    yAxes: [
                    { 
                        type: 'linear',
                        position: 'left',
                        gridLines: {
                            color: '#999999',
                            lineWidth: 1
                        },
                        ticks: {
                            fontColor: "rgba(255, 255, 255, 1)",
                            }
                    }, {
                        type: 'linear',
                        position: 'right',
                        afterUpdate: function(scaleInstance) {
                            console.dir(scaleInstance);
                        },
                        ticks: {
                            stepSize: tempmin - tempmax,
                            min: tempmin,
                            max: tempmax,
                            mirror: true,
                            padding: -200,
                            fontColor: "rgba(255, 255, 255, 1)",
                            fontSize: 14,
                            callback: function(value) {
                                if ( value === tempmin) {
                                    return ' Dagens laveste temperatur = ' + value + '°C';
                                } else {
                                    return ' Dagens højeste temperatur = ' + value + '°C';
                                }
                            }
                        },
                        gridLines: {
                            drawOnChartArea: false,
                        },
                        scaleLabel: {
                            display: true,
                            labelString: '°C',
                            fontColor: "rgba(255, 255, 255, 1)",
                            fontSize: 14,
                            fontStyle: 'bold'
                        }                               
                    }]
                },
            }
                    });
                });
            }
        }
        </script>

</body>
</html>


Get this bounty!!!

#StackBounty: #javascript #html #canvas #html5-canvas #fabricjs FabricJS Alternating between canvas elements?

Bounty: 50

Let’s say I have two canvas elements:

<canvas id="c1" width="400" height="300"></canvas>
<canvas id="c2" width="400" height="300"></canvas>
<canvas id="c3" width="400" height="300"></canvas>

From looking at the docs, it tells me that I can use this to convert the canvas into a FabricJS canvas.

var c = new fabric.Canvas('c1', {
    preserveObjectStacking: true,
    isDrawingMode: false
});

Now, if I wanted to do something with this new canvas, adding a circle for instance, I could use this function:

function AddCircle() {
  var object = new fabric.Circle({
      radius: 15,
      fill: 'blue',
      left: 100,
      top: 100
  });
  c.add(object);
}

Now, this is all well and good for c1 but c2 and c3 won’t do anything because it’s never being initialized. So is it possible to alternate between FabricJS canvas elements?

I’d like the functionality to be similar to activeCanvas.add(object) instead of just c.add(object).

Before you respond, please note that I’m well aware that I could just use the same method for the c2 element, but I’d like to alternate between canvas elements instead as I have something planned for that.


Get this bounty!!!

#StackBounty: #javascript #canvas #fabricjs Apply effects/filters to more than just image objects in FabricJS

Bounty: 50

I’ve been using FabricJS for a while now for my interactive comic builder and for the most part, it’s been great. A limitation however is that – to date – I haven’t been able to find any way to make objects that aren’t images apply effects.

For instance, this guide shows how to render effects over an image object but let’s say for example, I wanted to apply the effect to shapes like rectangles and circles drawn with Fabric or text objects. Is there a way to do this already?

I’m not too interested in presets such as sepia or vintage etc, I really only need the ability to blur, lighten/darken and saturate/unsaturate.


Get this bounty!!!

#StackBounty: #javascript #canvas #html5-canvas #fabricjs #fabricjs2 FabricJS clippath border color and move the overlay image behind c…

Bounty: 50

I am trying to add the clippath area(rect object) over canvas using fabricJS. Currently i set Overlay Image to the canvas and in addition to that am adding clippath area to place objects. I successfully able to place the object inside the clippath, but the problem is whatever image i place it inside clippath has opacity(means object also has transparency), i know is because i set transparent image as overlay which is always top over other object.

My query is

  1. Can i set the clippath area alone to ignore overlay? Or atleast to set the preference like clippath is first priority and next overlay?
  2. Currently clippath has no border and i couldn’t know where the clippath is? So is there a way to set the border over clippath area?

Here is my below code

HTML

<canvas id="ex8" width="300" height="400"></canvas>

JS

(function() {
  var canvas = new fabric.Canvas('ex8');
  canvas.controlsAboveOverlay = true;
  var clipPath = new fabric.Rect({ width: 100, height: 100, fill: 'red' , top:100, left: 100});
 
  
   canvas.setOverlayImage("https://i.ibb.co/88QkSMC/crew-front.png", canvas.renderAll.bind(canvas), {

});
  var group = new fabric.Group([
    new fabric.Rect({ width: 100, height: 100, fill: 'red',globalCompositeOperation : 'xor' }),
    
  
    new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }),
    new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 })
  ]);
  
  canvas.clipPath = clipPath;
  canvas.add(group);
})()

Appreciate your help !!!

FIDDLE


Get this bounty!!!

#StackBounty: #javascript #canvas #html5-canvas #fabricjs How to Limit Object inside Transparent Rectangle Area – FabricJS?

Bounty: 50

I am implementing designer functionality using FabricJS in my side. My Idea is to set the background image using setBackgroundImage from fabric, also i add specific area of transparent rectangle with size and position fetch from JCrop. Now come to my question, i want to restrict the object placement within that specific area of transparent rectangle. Let say i want to add text/image/shapes that should be in that limited area, i able to implement the background image, position of transparent rectangle and even circle shape object but i unable to find details to limit object place it inside transparent rectangle and only in that region.

Here is my below code and working fiddle, If you see it in fiddle the image where you need to select the cropping portion and below that canvas background with transparent rectangle which is the one of same like crop selection. Now i want to limit the object placement anything to be in that transparent rectangle, right now i can place object in anywhere in the canvas.

HTML

   <img src="https://images.unsplash.com/photo-1595438337199-d50ba5072c7e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=330&q=80" id="target">

   <div class="canvas-container" style="position: relative; user-select: none;">
     <canvas id="c1" width="600" height="600" style="border:1px solid #ccc; position: absolute;  left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
   </div>

JS

function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
  var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
  return {
    width: srcWidth * ratio,
    height: srcHeight * ratio,
    aspectratio: ratio
  };
}

jQuery(function($) {
    //alert("Testing");
  var img = new Image();
  img.onload = function() {
    var data = calculateAspectRatioFit(this.width, this.height, '400', '600');
    console.log(data);
    jQuery('#target').attr('width', data.width);
    jQuery('#target').attr('height', data.height);
    jQuery('#pdr-drawing-area').html("Aspect Ratio: " + data.aspectratio);
    const stage = Jcrop.attach('target');
    stage.listen('crop.change', function(widget, e) {
      const pos = widget.pos;
      console.log(pos.x, pos.y, pos.w, pos.h);
      //fabric js
      var canvas = new fabric.Canvas('c1');
      var center = canvas.getCenter();
      var img = 'https://images.unsplash.com/photo-1595438337199-d50ba5072c7e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=330&q=80';
      canvas.setBackgroundImage(img, function() {
        canvas.backgroundImage && canvas.backgroundImage.scaleToWidth(data.width);
        canvas.backgroundImage && canvas.backgroundImage.scaleToHeight(data.height);
        //canvas.sendToBack(img);
        canvas.renderAll();
      });
      console.log(pos.x * data.aspectratio);
      var rect = new fabric.Rect({
        left: pos.x,
        top: pos.y,
        fill: 'transparent',
        width: (pos.w),
        height: (pos.h),
        strokeDashArray: [5, 5],
        stroke: "black",
        selectable: false,
        evented: false,
        //visible: false
      });



      canvas.add(new fabric.Circle({
        radius: 30,
        fill: '#f55',
        top: pos.y + 2,
        left: pos.x + 2
      }));

      canvas.add(rect);
      canvas.setHeight(data.height);
      canvas.setWidth(data.width);
      canvas.renderAll();
    });
  },
   img.src = 'https://images.unsplash.com/photo-1595438337199-d50ba5072c7e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=330&q=80';
});


Get this bounty!!!

#StackBounty: #javascript #canvas #optimization #webgl #mediarecorder Optimize performance of 1920×1080 canvas recording

Bounty: 50

I would like to make a basic canvas-recorder using the MediaRecorder API to record a webGL (or other) canvas.

I want the final video to be 1920×1080 size (even if the actual resolution is lower, AKA i’m fine with it being scaled up).

The problem is that when I try to load a 1920×1080 canvas and screen record it with WebGL, the results are very jittery and choppy, like, extremely (even though it does record it, choppyness and all).
All I want is the finished size of the video to be 1920×1080, even if it’s actually lower quality, and I need it to play in real time in a browser, without being choppy. So:

  1. Is there any way to make a webGL canvas a bigger size, while keeping the same performance it would have with a smaller size?

  2. Is there a way for the mediarecorder API to record a video that has a higher resolution than the stream (in this case, a canvas stream) that it is currently capturing?


Get this bounty!!!

#StackBounty: #javascript #game #html5 #canvas #collision Battle City (Tank) replica

Bounty: 50

I’m learning JS and Canvas, a friend of mine gave me the task to create a Battle City replica. I’ve already managed to make the map and the player to move. Nothing fancy just some squares with a color, I tried to replicate the first map:

enter image description here

My code currently creates the map using 26 x 26 little squares (I chosen that amount because there are 13 "cols" in the image above, but each brick col is destroyed partially by a certain amount per shot (if IRC that was about 2-4 shots in the original game), in my case that’s 2 bullets, so 13×2 = 26), my player uses 2 x 2 which is not ideal, because it requires to check 2 blocks for every direction, if I wanted to use a bigger grid, the size of the player grid might increase as well, making the code unmaintainable, how could I improve this code in order to have my player a single entity instead of a 4-block entity?

I think my intersection logic is kinda rudimentary, is there a way to improve this as well?

And I also struggled a little bit with the map drawing, as it’s drawn vertically so I had to change the i and j variables so that the map wouldn’t be rotated 90 degrees, I’m also interested in other options to do this and not having to paint the map and player every time I move the player in one direction.

const mapGrid = [
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1],
    [2, 2, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 2, 2],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  ];
  
const canvas = document.getElementById('map');
const ctx = canvas.getContext('2d');

const width = 24;
const height = 24;

const Directions = {
  up: 1,
  left: 2,
  right: 3,
  down: 4,
};

Object.freeze(Directions);

const playerCoords = [
  mapGrid.length - 2, 8,
];

const goalCoords = [6, 12];

const toRelativeCoord = (fromCoord) => fromCoord * width;

const drawMap = () => {
  ctx.beginPath();

  for (let i = 0; i < mapGrid.length; i += 1) {
    for (let j = 0; j < mapGrid[i].length; j += 1) {
      switch (mapGrid[i][j]) {
        case 1: //Bricks
          ctx.fillStyle = '#993333';
          break;
        case 2: //Iron-Bricks
          ctx.fillStyle = '#C0C0C0';
          break;
        case 3: //Base
          ctx.fillStyle = '#CCCC99';
          break;
        case 4: //Player
          ctx.fillStyle = '#FFFF00';
          break;
        default: //Road
          ctx.fillStyle = '#000000';
          break;
      }
      ctx.fillRect(j * width, i * height, width, height);
    }
  }
};

const drawPlayer = () => {
  ctx.beginPath();
  ctx.fillStyle = '#FFFF00';
  ctx.fillRect(toRelativeCoord(playerCoords[1]),
    toRelativeCoord(playerCoords[0]), width * 2, height * 2);
};

const repaint = () => {
  drawMap();
  drawPlayer();
  if (hasReachedGoal()) {
    alert('Game Over')
  }
};

const isMapEdge = (x, y, direction) => {
  switch (direction) {
    case Directions.up:
      return x - 1 < 0;
    case Directions.left:
      return y - 1 < 0;
    case Directions.right:
      return y + 2 === mapGrid[0].length;
    default: // back
      return x + 2 === mapGrid.length;
  }
};

const upIsClear = (x, y) => {
  if (isMapEdge(x, y, Directions.up)) {
    return false;
  }
  return mapGrid[x - 1][y] === 0 && mapGrid[x - 1][y + 1] === 0;
};

const leftIsClear = (x, y) => {
  if (isMapEdge(x, y, Directions.left)) {
    return false;
  }
  return mapGrid[x][y - 1] === 0 && mapGrid[x + 1][y - 1] === 0;
};

const rightIsClear = (x, y) => {
  if (isMapEdge(x, y, Directions.right)) {
    return false;
  }
  return mapGrid[x][y + 2] === 0 && mapGrid[x + 1][y + 2] === 0;
};

const downIsClear = (x, y) => {
  if (isMapEdge(x, y, Directions.down)) {
    return false;
  }
  return mapGrid[x + 2][y] === 0 && mapGrid[x + 2][y + 1] === 0;
};

const moveUp = () => {
  if (upIsClear(playerCoords[0], playerCoords[1])) {
    playerCoords[0] -= 1;
    repaint();
  }
};

const moveLeft = () => {
  if (leftIsClear(playerCoords[0], playerCoords[1])) {
    playerCoords[1] -= 1;
    repaint();
  }
};

const moveRight = () => {
  if (rightIsClear(playerCoords[0], playerCoords[1])) {
    playerCoords[1] += 1;
    repaint();
  }
};

const moveDown = () => {
  if (downIsClear(playerCoords[0], playerCoords[1])) {
    playerCoords[0] += 1;
    repaint();
  }
};

const listenToEvents = () => {
  document.addEventListener('keypress', (event) => {
    if (event.key === 'W' || event.key === 'w') {
      moveUp();
    } else if (event.key === 'A' || event.key === 'a') {
      moveLeft();
    } else if (event.key === 'S' || event.key === 's') {
      moveDown();
    } else if (event.key === 'D' || event.key === 'd') {
      moveRight();
    }
  });
};

const intersects = (coord1, coord2) => {
  return coord1 == coord2 || coord1 + 1 == coord2 || coord1 - 1 == coord2;
}

const hasReachedGoal = () => {
  if ((intersects(playerCoords[0], goalCoords[0])) && intersects(playerCoords[1], goalCoords[1]) ||
      (intersects(playerCoords[0], goalCoords[0])) && intersects(playerCoords[1] + 1, goalCoords[1]) ||
      (intersects(playerCoords[0] + 1, goalCoords[0])) && intersects(playerCoords[1], goalCoords[1]) ||
      (intersects(playerCoords[0] + 1, goalCoords[0])) && intersects(playerCoords[1] + 1, goalCoords[1])) {
        alert('Hey!')
  }
  return false;
}

/**
 * DEVELOPER NOTE
 * x = rows
 * y = columns
 *
 * 0, 0 = top left corner
*/

const initialize = () => {
  drawMap();
  drawPlayer();
  listenToEvents();
};

initialize();
<html>
    <head>
        <title>Tank</title>
        <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>
        <canvas id="map" width="624" height="624"></canvas>
    </body>

    http://main.js
</html>

Edit

I did some research on how to use classes, and I think the code is now more readable and more structured. I believe this is an improvement, but anyway I’d appreciate if someone could judge it with an expert and critic eye so that I can improve this code.

I’m still interested in how to paint the map horizontally instead of vertically, as currently I have to switch X and Y coords for some calculations.

index.html

<html>
    <head>
        <title>Tank</title>
    </head>
    <body>
        <canvas id="map" width="624" height="624"></canvas>
    </body>

    http://cell.js
    http://goal.js
    http://tank.js
    http://game.js
</html>

cell.js

const CellTypes = {
    road: 0,
    bricks: 1,
    ironBricks: 2,
    base: 3,
    player: 4,
    goal: 5
}

class Cell {
    static cellWidth = 24;
    static cellHeight = 24;

    constructor(x, y, color, type) {
        this.color = color;
        this.type = type;
        this.width = Cell.cellWidth;
        this.height = Cell.cellHeight;
        this.x = x * this.width;
        this.y = y * this.height;
    }
}

goal.js

class Goal extends Cell {
    constructor(x, y, color) {
        super(x, y, color, CellTypes.goal);
        this.width = this.width * 2;
        this.height = this.height * 2;
    }
}

tank.js

const Directions = {
    up: 1,
    left: 2,
    right: 3,
    down: 4,
};

class Tank extends Cell {
    constructor(x, y, color) {
        super(x, y, color, CellTypes.player)
        this.direction = Directions.up;
        this.speed = 12;
        this.width = this.width * 2;
        this.height = this.height * 2;
    }

    moveUp() {
        this.y -= this.speed;
    }

    moveDown() {
        this.y += this.speed;
    }

    moveLeft() {
        this.x -= this.speed;
    }

    moveRight() {
        this.x += this.speed;
    }
}

game.js

let maze = {
    map: [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1],
        [2, 2, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 2, 2],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ],
    goals: {
        goalColor: '#34EB9E',
        coords: [
            [12, 4], [0, 0]
        ]
    }
}

let cells = new Array(maze.map.length);
let goals = new Array(maze.goals.coords.length);
//Player coords
let player = {
    x: 8,
    y: 24
}

const canvas = document.getElementById('map');
const ctx = canvas.getContext('2d');
let tank = new Tank(player.x, player.y, '#FFFF00');;

const initialize = () => {
    configureMaze();
    repaint();
    listenToEvents();
}

//Sets the data as cells objects
const configureMaze = () => {
    for(let i = 0; i < maze.map.length; i++) {
        cells[i] = new Array(maze.map[i].length);
        for(let j = 0; j < maze.map[i].length; j++) {
            switch(maze.map[i][j]) {
                case 1:
                    cells[i][j] = new Cell(j, i, '#993333', CellTypes.bricks);
                    break;
                case 2:
                    cells[i][j] = new Cell(j, i, '#C0C0C0', CellTypes.ironBricks);
                    break;
                case 3:
                    cells[i][j] = new Cell(j, i, '#CCCC99', CellTypes.base);
                    break;
                default:
                    cells[i][j] = new Cell(j, i, '#000000', CellTypes.road);
                    break;
            }
        }
    }
}

//Draws the maze based on the configuration
const drawMaze = () => {
    ctx.beginPath();

    cells.forEach(cellsArr => {
        cellsArr.forEach(cell => {
            ctx.fillStyle = cell.color;
            ctx.fillRect(cell.x, cell.y, cell.width, cell.height)
        })
    })
}

//Goals are where some powerups will be
const drawGoals = () => {
    let i = 0;
    maze.goals.coords.forEach(coord => {
        goals[i] = new Goal(coord[0], coord[1], '#34EB9E');
        ctx.beginPath();
        ctx.fillStyle = '#34EB9E';
        ctx.fillRect(goals[i].x, goals[i].y, goals[i].width, goals[i].height);
        i++;
    })
}

//Draws the player's tank
const drawPlayerTank = () => {
    ctx.beginPath();
    ctx.fillStyle = tank.color;
    ctx.fillRect(tank.x, tank.y, tank.width, tank.height);
}

//Repaints the UI
const repaint = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawMaze();
    drawGoals();
    drawPlayerTank();
}

//Checks if the tank is on the canvas limit
const isMapLimit = (direction) => {
    switch (direction) {
        case Directions.up:
            return tank.y - 1 < 0;
        case Directions.down:
            return tank.y + 1 >= toCanvasCoord(maze.map.length - 2, Cell.cellWidth);
        case Directions.left:
            return tank.x - 1 < 0;
        case Directions.right:
            return tank.x + 1 >= toCanvasCoord(maze.map[0].length - 2, Cell.cellHeight);
    }
}

//Transforms map coords to canvas coords
const toCanvasCoord = (coord, toValue) => {
    return coord * toValue;
}

//Transforms canvas coords to map coords
const toMapCoord = (coord, toValue) => {
    return Math.floor(coord / toValue);
}

//Checks for intersection of coords
const intersects = (x1, y1, x2, y2, width, height) => {
    return x1 + width > x2 && y1 + height > y2 && x1 < x2 + width && y1 < y2 + height;
}

//Checks if we're standing in any of the goals zones
const isGoal = () => {
    for (let i = 0; i < goals.length; i++) {
        if (intersects(tank.x, tank.y, goals[i].x, goals[i].y, goals[i].width, goals[i].height)) {
            return true;
        }
    }
    return false;
}

//Checks if the cell that we're trying to move is a road cell
const isRoadCell = (direction) => {
    let xCoord1; //xCoord for the top left corner
    let yCoord1; //yCoord for the top left corner
    let xCoord2; //xCoord for the tank's width
    let yCoord2; //xCoord for the tank's height
    switch (direction) {
        case Directions.up:
            xCoord1 = toMapCoord(tank.x, Cell.cellWidth);
            yCoord1 = toMapCoord(tank.y - tank.speed, Cell.cellHeight);
            xCoord2 = toMapCoord(tank.x + tank.width - 1, Cell.cellWidth);
            yCoord2 = toMapCoord(tank.y - tank.speed, Cell.cellHeight);
            break;
        case Directions.down:
            xCoord1 = toMapCoord(tank.x, Cell.cellWidth);
            yCoord1 = toMapCoord(tank.y + tank.height, Cell.cellHeight);
            xCoord2 = toMapCoord(tank.x + tank.width - 1, Cell.cellWidth);
            yCoord2 = toMapCoord(tank.y + tank.height, Cell.cellHeight);
            break;
        case Directions.left:
            xCoord1 = toMapCoord(tank.x - tank.speed, Cell.cellWidth);
            yCoord1 = toMapCoord(tank.y, Cell.cellHeight);
            xCoord2 = toMapCoord(tank.x - tank.speed, Cell.cellWidth);
            yCoord2 = toMapCoord(tank.y + tank.height - 1, Cell.cellHeight);
            break;
        case Directions.right:
            xCoord1 = toMapCoord(tank.x + tank.width, Cell.cellWidth);
            yCoord1 = toMapCoord(tank.y, Cell.cellHeight);
            xCoord2 = toMapCoord(tank.x + tank.width, Cell.cellWidth);
            yCoord2 = toMapCoord(tank.y + tank.height - 1, Cell.cellHeight);
            break;
    }
    if (maze.map[yCoord1][xCoord1] === CellTypes.road && maze.map[yCoord2][xCoord2] === CellTypes.road) {
        return true;
    }
    return false;
}

//Listens to WASD key presses
const listenToEvents = () => {
    document.addEventListener('keypress', (event) => {
        if (event.key === 'W' || event.key === 'w') {
            tank.direction = Directions.up;
            if (!isMapLimit(tank.direction) && isRoadCell(tank.direction)) {
                tank.moveUp();
                repaint();
            }
        } else if (event.key === 'A' || event.key === 'a') {
            tank.direction = Directions.left;
            if (!isMapLimit(tank.direction) && isRoadCell(tank.direction)) {
                tank.moveLeft();
                repaint();
            }
        } else if (event.key === 'S' || event.key === 's') {
            tank.direction = Directions.down;
            if (!isMapLimit(tank.direction) && isRoadCell(tank.direction)) {
                tank.moveDown();
                repaint();
            }
        } else if (event.key === 'D' || event.key === 'd') {
            tank.direction = Directions.right;
            if (!isMapLimit(tank.direction) && isRoadCell(tank.direction)) {
                tank.moveRight();
                repaint();
            }
        }
        if (isGoal()) {
            alert('GOAL!')
        }
    });
}

initialize();


Get this bounty!!!

#StackBounty: #python #matplotlib #canvas #tkinter #figure FigureCanvasTkAgg resizes if its figure is refreshed

Bounty: 50

I am working on a matplotlib figure embedded in a tkinter gui in Python.

First a FigureCanvasTkAgg is created, which includes a previously created object, which contains a matplotlib figure and then it is drawn. This part works perfectly fine.
Afterwards I want to refresh the canvas / its content based on an user action. If the method to refresh the canvas is called, the figure object is refreshed and the canvas is redrawn.
This works as well, the figure is updated, but for some strange reason, the canvas resizes, so it shrinks to about one quarter of its original size. If I print the size of the canvas, I can see that its size changed.

I have tried to resize the canvas back to its original size after the refreshment, without success.

I would be thankful for any input. I have added a shortend version of my code.

    #canvas object which is displayed on the gui
    class Canvas:
        def __init__(self, parent):
            #the map_figure object is create
            self.map_figure = map_figure()
            #the matplotlib figure is extracted from the map_figure
            self.figure = self.map_figure.get_figure()
            #the canvas is created using the figure and the parent
            self.canvas = FigureCanvasTkAgg(self.figure, master=parent)
            #I tried both, to manually set the canvas size to the window dimensions (which are
            #described by w and h and using the .pack() parameters, both are working fine
            self.canvas.get_tk_widget().config(width=w,height=h)
            #self.canvas.get_tk_widget().pack(fill='both', expand=True)
            self.canvas.get_tk_widget().pack()
            #canvas is drawn
            self.canvas.draw()
            #its size is printed
            print(self.get_size())

       def refresh(self, parent, point):
            #the map_figure of the canvas is refresh
            self.map_figure.refresh(data)
            #the matplotlib figure is extracted  again
            self.canvas.figure = self.map_figure.get_figure()
            #canvas is redrawn
            self.canvas.draw()
            #the canvas size is now different for some reason even after calling 
            #self.canvas.get_tk_widget().config(width=w,height=h) before this again
            print(self.canvas.get_width_height())

    #the map_figure class if relevant
    class map_figure:
        def __init__(self, data):
            self.figure = self.create_figure(data)

        def get_figure(self):
            return self.figure

        def create_figure(self, data): 
            #creating a matplotlib figure, closing if there was one before
            plt.close(fig=None)
            fig = plt.figure()
            #creating a figure using the data here
            return fig

        #refreshing the figure using new data
        def refresh(self, data):
            self.figure = self.create_figure(data)

Here are also two picture to visualize my problem:

Before
State by initialization / before user action

After
State after user action


Get this bounty!!!

#StackBounty: #javascript #canvas #fabricjs Contain a rotated object inside another rotated object FabricJS

Bounty: 100

I have two objects a parent (red) and a child (blue). The parent object is fixed and can’t be moved, only the child object is movable and the child is always bigger than the parent. In whatever way the child object is moved it should always be contained inside the child, which means we should never see the red rectangle.

enter image description here

Demo: https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-7nt7q

I know there are solutions to contain an object within the canvas or other object boundaries (ex. Move object within canvas boundary limit) which mainly force the top/right/bottom/left values to not exceed the parent values, but here we have the case of two rotated objects by the same degree.

I have a real-life scenario when a user uploads a photo to a frame container. The photo is normally always bigger than the frame container. The user can move the photo inside the frame, but he should not be allowed to create any empty spaces, the photo should always be contained inside the photo frame.


Get this bounty!!!