Reputation: 3677
Firstly I must apologise for the length of the code in this question. It is based on Django and javascript and I have stripped out as much as I can to give a working example
The challenge is to create a composite image using a random number of rooks - all black rooks on the top row and all red rooks on the bottom row
This works perfectly when the page is first loaded, but if I click on the New Board button, then randomly red rooks might appear in the top row, or black in the bottom
(The image was downloaded from here)
[Edit] If I add a trace in the javascript function displayPieceImage it reports the correct number of images being drawn[/Edit}
html
<!-- pages/home.html -->
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script>
</head>
<body>
{% block content %}
<div style="display:none;" id="pieceImages"></div>
<p>Red:<span id="red-count"></span> Black:<span id="black-count"></span></p>
<canvas id="top-image" style="background-color:#ffff00;"></canvas>
<p><button id="new-board">New Board</button>
{% endblock content %}
<script type="text/javascript" src="{% static 'js/test.js' %}"></script>
</body>
</html>
urls.py
#pages/urls.py
from django.urls import path
from .views import HomePageView, NewBoard
urlpatterns = [
path('', HomePageView.as_view()),
path('new-board', NewBoard.as_view(), name= 'new-board'),
]
views.py
# pages/views.py
import os
import io
import random
import base64
from PIL import Image as PilImage
from django.views.generic import View
from django.shortcuts import render
from django.http import JsonResponse
from django.conf import settings
PIECE_NAMES = ['red_rook', 'black_rook']
IMAGE_EXTENSION = 'png'
class HomePageView(View):
template = 'pages/home.html'
def get(self, request):
context = {}
return render(request, self.template, context)
class NewBoard(View):
template = 'pages/home.html'
def get(self, request):
pieces = []
red_count = random.randint(1, 5)
black_count = random.randint(1, 5)
for index in range(red_count):
pieces.append('red_rook')
for index in range(black_count):
pieces.append('black_rook')
context = {
'pieces': pieces,
'colour_length': {'red': red_count, 'black': black_count},
'max_colour_length': max(red_count, black_count),
'piece_images': piece_images,
}
return JsonResponse(context, safe=False)
def encode_image(image):
"""Return image encoded to base64 from a PIL.Image.Image."""
io_buffer = io.BytesIO()
image.save(io_buffer, format='PNG')
saved_image = io_buffer.getvalue()
encoded_image = ''.join(['data:image/jpg;base64,', base64.b64encode(saved_image).decode()])
return encoded_image
def _get_piece_images():
"""Return a dict of encoded piece images."""
images = {}
piece_directory = os.sep.join([settings.STATICFILES_DIRS[0], 'images'])
for piece in PIECE_NAMES:
image_path = os.path.join(piece_directory, f'{piece}.{IMAGE_EXTENSION}')
image = PilImage.open(image_path)
piece_image = encode_image(image)
images[piece] = piece_image
return images
# piece_images is a dict that contains all the valid pieces
piece_images = _get_piece_images()
test.js
// static/js/test.js
// 'Constants' to define image cropping
var RAW_WIDTH = 552
var RAW_HEIGHT = 640
var SCALE = 0.1
var PIECE_WIDTH = RAW_WIDTH * SCALE
var PIECE_HEIGHT = RAW_HEIGHT * SCALE
var CANVAS_HEIGHT = 3 * PIECE_HEIGHT;
// Initialise global variables
var last_colour = '';
var colour_row = 0;
var colour_column = 0;
$(document).ready(function () {
var new_board_link = document.getElementById('new-board');
new_board_link.onclick = function(){getNewBoard()};
getNewBoard();
});
function getNewBoard() {
$.ajax(
{
type: "GET",
url: 'new-board',
cache: false,
success: function (context) {
displayPieces(context);
}
}
);
}
function displayPieces(context) {
// Display all of the pieces for a given position
var red_count = document.getElementById('red-count');
var black_count = document.getElementById('black-count');
red_count.innerText = context.colour_length['red']
black_count.innerText = context.colour_length['black']
var max_colour_length = context.max_colour_length;
var position_id = 'top-image' // used to identify the position
var ctx = prepareCanvas(position_id, max_colour_length)
var piece_index = 0;
context.pieces.forEach(piece_name => {
createModelImage (position_id, piece_index)
displayPieceImage (context, ctx, position_id, piece_name, piece_index)
piece_index ++;
}
)
}
function prepareCanvas(position_id, max_colour_length) {
// Create a canvas and return the canvas context (ctx) for a given position
canvas = document.getElementById(position_id);
canvas.width = max_colour_length * PIECE_WIDTH;
canvas.height = CANVAS_HEIGHT;
var ctx = canvas.getContext('2d');
return ctx
}
function displayPieceImage (context, ctx, position_id, piece_name, piece_index) {
// Draw a piece and its object.
var image_id = 'source'+position_id+piece_index
var image = document.getElementById(image_id);
var position = piecePosition(piece_name)
var pos_x = position['pos_x']
var pos_y = position['pos_y']
image.src = context.piece_images[piece_name];
image.addEventListener('load', e => {
ctx.drawImage(image, 0, 0, RAW_WIDTH, RAW_HEIGHT, pos_x, pos_y, PIECE_WIDTH, PIECE_HEIGHT);
});
}
function piecePosition(piece_name) {
// Return the position of the piece relative to the canvas.
var piece_colour = piece_name.substring(0, 1);
if (last_colour != piece_colour) {
last_colour = piece_colour;
colour_column = -1;
}
colour_row = 'br'.indexOf(piece_colour.substr(0, 1));
colour_column ++;
position = {'pos_x': colour_column * PIECE_WIDTH, 'pos_y': colour_row * PIECE_HEIGHT}
return position
}
function createModelImage (position_id, index) {
// Add a piece image to the hidden element 'pieceImages'
var pieceImages = document.getElementById('pieceImages');
var img = document.createElement('img');
img.id = 'source'+position_id+index;
pieceImages.appendChild(img);
}
Upvotes: 1
Views: 33
Reputation: 21782
The problem is quite simple. In the function createModelImage
you add images to the div with the id pieceImages
. You never remove these images from the div when creating a new board. So there are old images with the ids which get used instead of the image tag that should be used. Instead you should remove these old image tags when making a new board:
function displayPieces(context) {
document.getElementById('pieceImages').innerHTML = '';
// Your original code here
}
Upvotes: 1