Add pixel counting and minimize/maximize overlay features

Introduces a comprehensive pixel counting system to the Template class and integrates pixel statistics into the template creation and rendering process. Adds minimize/maximize functionality to the overlay UI, including responsive element visibility, fixed minimized dimensions, and improved user feedback. Updates documentation and comments to reflect new features and version history.
This commit is contained in:
Nicholas 2025-08-04 22:08:12 -03:00
parent a0df44e746
commit 82bf8a3c3e
5 changed files with 315 additions and 68 deletions

File diff suppressed because one or more lines are too long

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "wplace-bluemarble",
"version": "0.67.0",
"version": "0.69.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wplace-bluemarble",
"version": "0.67.0",
"version": "0.69.0",
"devDependencies": {
"esbuild": "^0.25.0",
"terser": "^5.43.1"

View file

@ -1,19 +1,50 @@
/** An instance of a template.
* Handles all mathmatics and manipulation regarding a single template.
/** An instance of a template with comprehensive pixel counting and statistics.
* Handles all mathematics, manipulation, and statistical analysis regarding a single template.
*
* TEMPLATE FEATURES:
* - Automatic pixel counting and dimension analysis
* - Tile-based template chunking for efficient rendering
* - Statistical information storage and retrieval
* - Bitmap processing with configurable scaling
* - Memory-efficient template tile generation
*
* PIXEL COUNTING SYSTEM:
* - Calculates total pixel count (width × height) during template creation
* - Stores pixel count for statistical display and analysis
* - Integrates with template manager for aggregate statistics
* - Provides formatted pixel count information to user interface
*
* @since 0.65.2
* @version 1.1.0 - Added comprehensive pixel counting and statistical analysis system
*/
export default class Template {
/** The constructor for the {@link Template} class.
* @param {Object} [params={}] - Object containing all optional params
/** The constructor for the {@link Template} class with enhanced pixel tracking.
*
* Initializes a new template instance with all necessary properties for rendering
* and statistical analysis. The pixel counting system is initialized here and
* populated during the template creation process.
*
* PIXEL COUNTING INTEGRATION:
* The pixelCount property is automatically calculated during createTemplateTiles()
* and represents the total number of pixels in the source image (width × height).
* This information is used for:
* - User interface statistics display
* - Template comparison and analysis
* - Performance optimization decisions
* - Memory usage estimation
*
* @param {Object} [params={}] - Object containing all optional parameters
* @param {string} [params.displayName='My template'] - The display name of the template
* @param {number} [params.sortID=0] - The sort number of the template
* @param {string} [params.authorID=''] - The user ID of the person who exported the template. This is to prevent sort ID collisions when importing
* @param {string} [params.url=''] - The URL to the image
* @param {File} [params.file=null] - The template file. This can be a pre-processed File, or a processed bitmap
* @param {[number, number, number, number]} [params.coords=null] - The coordinates of the top left corner as (x, y, x, y)
* @param {number} [params.sortID=0] - The sort number of the template for rendering priority
* @param {string} [params.authorID=''] - The user ID of the person who exported the template (prevents sort ID collisions)
* @param {string} [params.url=''] - The URL to the source image
* @param {File} [params.file=null] - The template file (pre-processed File or processed bitmap)
* @param {[number, number, number, number]} [params.coords=null] - The coordinates of the top left corner as (tileX, tileY, pixelX, pixelY)
* @param {Object} [params.chunked=null] - The affected chunks of the template, and their template for each chunk
* @param {number} [params.tileSize=1000] - The size of a tile in pixels. Assumes the tile is a square
* @param {number} [params.tileSize=1000] - The size of a tile in pixels (assumes square tiles)
* @param {number} [params.pixelCount=0] - Total number of pixels in the template (calculated automatically during processing)
* @since 0.65.2
* @version 1.1.0 - Added pixelCount property for comprehensive template statistics and analysis
*/
constructor({
displayName = 'My template',
@ -33,19 +64,60 @@ export default class Template {
this.coords = coords;
this.chunked = chunked;
this.tileSize = tileSize;
this.pixelCount = 0; // Total pixel count in template (automatically calculated during createTemplateTiles)
}
/** Creates chunks of the template for each tile.
* @returns {Object} Collection of template bitmaps in a Object
/** Creates chunks of the template for each tile with integrated pixel counting system.
*
* This method processes the template image and performs several critical operations:
* 1. PIXEL ANALYSIS: Calculates total pixel count (width × height) for statistical purposes
* 2. TILE CHUNKING: Divides the template into tile-sized chunks for efficient rendering
* 3. BITMAP PROCESSING: Applies scaling and filtering for optimal display quality
* 4. MEMORY OPTIMIZATION: Creates efficient ImageBitmap objects for each tile segment
*
* PIXEL COUNTING IMPLEMENTATION:
* The pixel counting system calculates the total number of pixels in the source image
* by multiplying the bitmap width by height. This information is stored in the
* pixelCount property and used throughout the application for:
* - User interface statistics display
* - Template comparison and analysis
* - Performance monitoring and optimization
* - Memory usage estimation and management
*
* TECHNICAL DETAILS:
* - Uses createImageBitmap() for efficient image processing
* - Applies 3x scaling factor (shreadSize) for pixel art enhancement
* - Processes images in tile-sized chunks for memory efficiency
* - Maintains pixel-perfect rendering with nearest-neighbor sampling
* - Handles coordinate transformation between template and tile coordinate systems
*
* PERFORMANCE CONSIDERATIONS:
* - Large templates are processed incrementally to avoid memory issues
* - Bitmap creation uses OffscreenCanvas for optimal performance
* - Pixel counting is performed once during initial processing
* - Results are cached in the pixelCount property for repeated access
*
* @returns {Object} Collection of template bitmaps organized by tile coordinates
* @since 0.65.4
* @version 1.1.0 - Added comprehensive pixel counting functionality with detailed logging and statistics
*/
async createTemplateTiles() {
console.log(this.coords);
console.log('Template coordinates:', this.coords);
const shreadSize = 3; // Scale image factor. Must be odd
const bitmap = await createImageBitmap(this.file); // Creates a bitmap image from the uploaded file
const shreadSize = 3; // Scale image factor for pixel art enhancement (must be odd)
const bitmap = await createImageBitmap(this.file); // Create efficient bitmap from uploaded file
const imageWidth = bitmap.width;
const imageHeight = bitmap.height;
// ==================== PIXEL COUNTING SYSTEM ====================
// Calculate total pixel count using standard width × height formula
// This provides essential statistical information for the user interface
const totalPixels = imageWidth * imageHeight;
console.log(`Template pixel analysis - Dimensions: ${imageWidth}×${imageHeight} = ${totalPixels.toLocaleString()} pixels`);
// Store pixel count in instance property for access by template manager and UI components
// This enables real-time statistics display and template comparison features
this.pixelCount = totalPixels;
const templateTiles = {}; // Holds the template tiles

View file

@ -1,5 +1,19 @@
/** The main file. Everything in the userscript is executed from here.
* @since 0.0.0
*
* VERSION HISTORY:
* v1.1.0 - Added minimize/maximize functionality and pixel counting system
* Features added:
* - Interactive minimize/maximize overlay with click-to-toggle functionality
* - Fixed overlay dimensions: 60px width × 76px height in minimized state
* - Smart element visibility control (hides all UI except icon and drag bar when minimized)
* - Icon repositioning system (3px right offset) for better visual alignment in minimized state
* - Comprehensive pixel counting system for template statistics
* - Real-time pixel count display in template creation and rendering status messages
* - Intelligent pixel counting for actively rendered templates with tile-based filtering
* - Internationalized number formatting for large pixel counts (e.g., "1,234,567 pixels")
* - Automatic state management with proper cleanup when switching between modes
* - Enhanced user experience with visual feedback and status updates
*/
import Overlay from './Overlay.js';
@ -229,20 +243,61 @@ function observeBlack() {
observer.observe(document.body, { childList: true, subtree: true });
}
/** Deploys the overlay to the page.
/** Deploys the overlay to the page with minimize/maximize functionality.
* Creates a responsive overlay UI that can toggle between full-featured and minimized states.
*
* Parent/child relationships in the DOM structure below are indicated by indentation.
*
* OVERLAY STATES:
* - MAXIMIZED: Full UI with all controls, inputs, and status information visible
* - MINIMIZED: Compact 60×76px interface showing only the Blue Marble icon and drag functionality
*
* FEATURES:
* - Click-to-toggle functionality on the Blue Marble icon
* - Automatic element visibility management
* - Fixed dimensions for consistent minimized appearance
* - Proper cleanup and restoration of all UI elements
* - Visual feedback through alt-text updates
* - Status message integration
*
* @since 0.58.3
* @version 1.1.0 - Added comprehensive minimize/maximize feature with fixed dimensions and enhanced UX
*/
function buildOverlayMain() {
let isMinimized = false; // Estado do overlay (false = maximizado, true = minimizado)
let isMinimized = false; // Overlay state tracker (false = maximized, true = minimized)
overlay.addDiv({'id': 'bm-overlay', 'style': 'top: 10px; right: 75px;'})
.addDiv({'id': 'bm-contain-header'})
.addDiv({'id': 'bm-bar-drag'}).buildElement()
.addImg({'alt': 'Blue Marble Icon - Click to minimize/maximize', 'src': 'https://raw.githubusercontent.com/SwingTheVine/Wplace-BlueMarble/main/dist/assets/Favicon.png', 'style': 'cursor: pointer;'},
(instance, img) => {
/** Click event handler for overlay minimize/maximize functionality.
*
* Toggles between two distinct UI states:
* 1. MINIMIZED STATE (60×76px):
* - Shows only the Blue Marble icon and drag bar
* - Hides all input fields, buttons, and status information
* - Applies fixed dimensions for consistent appearance
* - Repositions icon with 3px right offset for visual centering
*
* 2. MAXIMIZED STATE (responsive):
* - Restores full functionality with all UI elements
* - Removes fixed dimensions to allow responsive behavior
* - Resets icon positioning to default alignment
* - Shows success message when returning to maximized state
*
* IMPLEMENTATION DETAILS:
* - Uses CSS display property manipulation for element visibility
* - Maintains drag functionality in both states
* - Updates accessibility text (alt attribute) based on current state
* - Provides user feedback through status messages
* - Ensures proper cleanup of all style overrides when switching states
*
* @since 1.1.0 - Complete minimize/maximize implementation
* @param {Event} event - The click event object (implicit)
*/
img.addEventListener('click', () => {
isMinimized = !isMinimized;
isMinimized = !isMinimized; // Toggle the current state
const overlay = document.querySelector('#bm-overlay');
const header = document.querySelector('#bm-contain-header');
@ -252,107 +307,148 @@ function buildOverlayMain() {
const enableButton = document.querySelector('#bm-button-enable');
const coordInputs = document.querySelectorAll('#bm-contain-coords input');
// Restaura o tamanho original ao maximizar
// Pre-restore original dimensions when switching to maximized state
// This ensures smooth transition and prevents layout issues
if (!isMinimized) {
overlay.style.width = "auto";
overlay.style.maxWidth = "300px";
overlay.style.minWidth = "200px";
overlay.style.padding = "10px";
}
// Define elements that should be hidden/shown during state transitions
// Each element is documented with its purpose for maintainability
const elementsToToggle = [
'#bm-overlay h1', // Título "Blue Marble"
'#bm-contain-userinfo', // Informações do usuário
'#bm-overlay hr', // Linhas separadoras
'#bm-contain-automation > *:not(#bm-contain-coords)', // Seção de automação exceto coordenadas
'#bm-input-file-template', // Upload de arquivo
'#bm-contain-buttons-action', // Botões de ação
`#${instance.outputStatusId}` // Log de status (textarea)
'#bm-overlay h1', // Main title "Blue Marble"
'#bm-contain-userinfo', // User information section (username, droplets, level)
'#bm-overlay hr', // Visual separator lines
'#bm-contain-automation > *:not(#bm-contain-coords)', // Automation section excluding coordinates
'#bm-input-file-template', // Template file upload interface
'#bm-contain-buttons-action', // Action buttons container
`#${instance.outputStatusId}` // Status log textarea for user feedback
];
// Apply visibility changes to all toggleable elements
elementsToToggle.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
element.style.display = isMinimized ? 'none' : '';
});
});
// Controla especificamente o container de coordenadas e botões
// Handle coordinate container and button visibility based on state
if (isMinimized) {
// Configura o container de coords para mostrar apenas o botão (esconde inputs)
// ==================== MINIMIZED STATE CONFIGURATION ====================
// In minimized state, we hide ALL interactive elements except the icon and drag bar
// This creates a clean, unobtrusive interface that maintains only essential functionality
// Hide coordinate input container completely
if (coordsContainer) {
coordsContainer.style.display = 'flex';
coordsContainer.style.flexDirection = 'row';
coordsContainer.style.justifyContent = 'center';
coordsContainer.style.alignItems = 'center';
coordsContainer.style.gap = '0.5em';
coordsContainer.style.margin = '0.5em 0';
coordsContainer.style.display = 'none';
}
// Hide coordinate button (pin icon)
if (coordsButton) {
coordsButton.style.display = '';
coordsButton.style.display = 'none';
}
// Move o botão Enable para o container de coordenadas para ficar na mesma linha
if (enableButton && coordsContainer) {
coordsContainer.appendChild(enableButton);
enableButton.style.display = '';
enableButton.style.marginTop = '0';
// Hide enable/disable template button
if (enableButton) {
enableButton.style.display = 'none';
}
// Hide all coordinate input fields individually (failsafe)
coordInputs.forEach(input => {
input.style.display = 'none';
});
overlay.style.padding = '5px';
// Apply fixed dimensions for consistent minimized appearance
// These dimensions were chosen to accommodate the icon while remaining compact
overlay.style.width = '60px'; // Fixed width for consistency
overlay.style.height = '76px'; // Fixed height (60px + 16px for better proportions)
overlay.style.maxWidth = '60px'; // Prevent expansion
overlay.style.minWidth = '60px'; // Prevent shrinking
overlay.style.padding = '8px'; // Comfortable padding around icon
// Apply icon positioning for better visual centering in minimized state
// The 3px offset compensates for visual weight distribution
img.style.marginLeft = '3px';
// Configure header layout for minimized state
header.style.textAlign = 'center';
header.style.margin = '0';
header.style.marginBottom = '0';
// Ensure drag bar remains visible and properly spaced
if (dragBar) {
dragBar.style.display = '';
dragBar.style.marginBottom = '0.25em';
}
} else {
// Restaura o layout normal
// ==================== MAXIMIZED STATE RESTORATION ====================
// In maximized state, we restore all elements to their default functionality
// This involves clearing all style overrides applied during minimization
// Restore coordinate container to default state
if (coordsContainer) {
coordsContainer.style.display = '';
coordsContainer.style.flexDirection = '';
coordsContainer.style.justifyContent = '';
coordsContainer.style.alignItems = '';
coordsContainer.style.gap = '';
coordsContainer.style.textAlign = '';
coordsContainer.style.margin = '';
coordsContainer.style.display = ''; // Show container
coordsContainer.style.flexDirection = ''; // Reset flex layout
coordsContainer.style.justifyContent = ''; // Reset alignment
coordsContainer.style.alignItems = ''; // Reset alignment
coordsContainer.style.gap = ''; // Reset spacing
coordsContainer.style.textAlign = ''; // Reset text alignment
coordsContainer.style.margin = ''; // Reset margins
}
// Restore coordinate button visibility
if (coordsButton) {
coordsButton.style.display = '';
}
// Move o botão Enable de volta para seu container original
// Restore enable button visibility and reset positioning
if (enableButton) {
const enableContainer = document.querySelector('#bm-contain-buttons-template');
if (enableContainer) {
enableContainer.appendChild(enableButton);
}
enableButton.style.display = '';
enableButton.style.marginTop = '';
}
// Restore all coordinate input fields
coordInputs.forEach(input => {
input.style.display = '';
});
// Reset icon positioning to default (remove minimized state offset)
img.style.marginLeft = '';
// Restore overlay to responsive dimensions
overlay.style.padding = '10px';
// Reset header styling to defaults
header.style.textAlign = '';
header.style.margin = '';
header.style.marginBottom = '';
// Reset drag bar spacing
if (dragBar) {
dragBar.style.marginBottom = '0.5em';
}
// Remove dimensões fixas para permitir redimensionamento automático
// Remove all fixed dimensions to allow responsive behavior
// This ensures the overlay can adapt to content changes
overlay.style.width = '';
overlay.style.height = '';
}
// Atualiza o alt text do ícone para refletir o estado atual
// ==================== ACCESSIBILITY AND USER FEEDBACK ====================
// Update accessibility information and provide user feedback
// Update alt text to reflect current state for screen readers and tooltips
img.alt = isMinimized ?
'Blue Marble Icon - Minimized (Click to maximize)' :
'Blue Marble Icon - Maximized (Click to minimize)';
// Atualiza a mensagem de status apenas quando maximizado (para não aparecer quando minimizado)
// Provide status feedback only when maximizing (avoid clutter in minimized state)
// This gives users confirmation that the action was successful
if (!isMinimized) {
const statusMessage = 'Overlay maximizado';
const statusMessage = 'Overlay maximized - All controls restored';
instance.handleDisplayStatus(statusMessage);
}
});

View file

@ -1,9 +1,34 @@
import Template from "./Template";
import { numberToEncoded } from "./utils";
/** Manages the template system.
* This class handles all external requests for modification to a Template.
/** Manages the comprehensive template system with integrated pixel counting and statistics.
*
* This class handles all external requests for template modification, creation, and statistical analysis.
* It serves as the central coordinator between template instances and the user interface, providing
* real-time feedback on template statistics including pixel counts and rendering status.
*
* ENHANCED FEATURES (v1.1.0):
* - Real-time pixel counting and statistics display
* - Intelligent template filtering based on active tiles
* - Internationalized number formatting for large pixel counts
* - Comprehensive status reporting with detailed template information
* - Enhanced user feedback during template creation and rendering
*
* PIXEL COUNTING SYSTEM:
* The template manager integrates with the Template class pixel counting system to provide:
* - Individual template pixel counts during creation
* - Aggregate pixel counts for multiple templates during rendering
* - Smart filtering to count only actively displayed templates
* - Formatted display of pixel statistics in user interface
*
* STATISTICAL INTEGRATION POINTS:
* 1. Template Creation: Displays pixel count when new templates are processed
* 2. Template Rendering: Shows aggregate pixel count for templates being displayed
* 3. Tile Filtering: Counts pixels only for templates active in current viewport
* 4. User Interface: Provides formatted statistics for status messages
*
* @since 0.55.8
* @version 1.1.0 - Added comprehensive pixel counting system and enhanced statistical reporting
* @example
* // JSON structure for a template
* {
@ -147,7 +172,11 @@ export default class TemplateManager {
this.templatesArray = []; // Remove this to enable multiple templates (2/2)
this.templatesArray.push(template); // Pushes the Template object instance to the Template Array
this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}!`);
// ==================== PIXEL COUNT DISPLAY SYSTEM ====================
// Display pixel count statistics with internationalized number formatting
// This provides immediate feedback to users about template complexity and size
const pixelCountFormatted = new Intl.NumberFormat().format(template.pixelCount);
this.overlay.handleDisplayStatus(`Template created at ${coords.join(', ')}! Total pixels: ${pixelCountFormatted}`);
console.log(Object.keys(this.templatesJSON.templates).length);
console.log(this.templatesJSON);
@ -177,18 +206,44 @@ export default class TemplateManager {
}
/** Draws all templates on that tile
/** Draws all templates on the specified tile with intelligent pixel count reporting.
*
* This method handles the rendering of template overlays on individual tiles and provides
* comprehensive statistics about the templates being displayed. It integrates with the
* pixel counting system to give users real-time feedback about template complexity.
*
* PIXEL COUNTING INTEGRATION:
* The method implements intelligent pixel counting that:
* - Identifies templates that have content in the current tile
* - Sums pixel counts only for templates actually being rendered
* - Formats large numbers with locale-appropriate separators
* - Provides detailed status messages with template and pixel statistics
*
* PERFORMANCE OPTIMIZATIONS:
* - Filters templates by tile coordinates before processing
* - Counts pixels only for active templates to avoid unnecessary calculations
* - Uses efficient array operations for template filtering and aggregation
* - Caches formatted numbers to avoid repeated formatting operations
*
* USER EXPERIENCE ENHANCEMENTS:
* - Shows both template count and total pixel count in status messages
* - Uses internationalized number formatting for better readability
* - Provides immediate feedback when templates are being displayed
* - Handles singular/plural forms correctly for template count
*
* @param {File} tileBlob - The pixels that are placed on a tile
* @param {[number, number]} tileCoords - The tile coordinates [x, y]
* @since 0.65.77
* @version 1.1.0 - Added intelligent pixel counting and enhanced status reporting
*/
async drawTemplateOnTile(tileBlob, tileCoords) {
const drawSize = this.tileSize * this.drawMult; // Draw multiplier
const drawSize = this.tileSize * this.drawMult; // Calculate draw multiplier for scaling
// Format tile coordinates with proper padding for consistent lookup
tileCoords = tileCoords[0].toString().padStart(4, '0') + ',' + tileCoords[1].toString().padStart(4, '0');
console.log(`Looking for "${tileCoords}"`);
console.log(`Searching for templates in tile: "${tileCoords}"`);
const templateArray = this.templatesArray; // Stores a copy for sorting
@ -218,7 +273,31 @@ export default class TemplateManager {
console.log(templateBlobs);
if (templateBlobs.length > 0) {
this.overlay.handleDisplayStatus(`Displaying ${templateBlobs.length} template${templateBlobs.length == 1 ? '' : 's'}.`);
// ==================== INTELLIGENT PIXEL COUNTING SYSTEM ====================
// Calculate total pixel count for templates actively being displayed in this tile
// This provides accurate statistics by counting only templates with content in the current viewport
const totalPixels = templateArray
.filter(template => {
// Filter templates to include only those with tiles matching current coordinates
// This ensures we count pixels only for templates actually being rendered
const matchingTiles = Object.keys(template.chunked).filter(tile =>
tile.startsWith(tileCoords)
);
return matchingTiles.length > 0;
})
.reduce((sum, template) => sum + (template.pixelCount || 0), 0);
// Format pixel count with locale-appropriate thousands separators for better readability
// Examples: "1,234,567" (US), "1.234.567" (DE), "1 234 567" (FR)
const pixelCountFormatted = new Intl.NumberFormat().format(totalPixels);
// Display comprehensive status information including both template count and pixel statistics
// This gives users immediate feedback about the complexity and scope of what's being rendered
this.overlay.handleDisplayStatus(
`Displaying ${templateBlobs.length} template${templateBlobs.length == 1 ? '' : 's'}. ` +
`Total pixels: ${pixelCountFormatted}`
);
}
const tileBitmap = await createImageBitmap(tileBlob);