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