// ==UserScript==
// @name image color viewer.js
// @namespace github.com/annaprojects
// @version 1.3
// @description Adds context menu options to view an image in a new tab with dynamic background controls, and to change the background directly behind the image on the current page (white, black, transparent, reset).
// @author annaroblox
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// ==/UserScript==
(function() {
'use strict';
// Variable to store the URL of the image that was right-clicked.
let imageUrl = null;
// Variable to store the actual DOM image element that was right-clicked.
let currentImageElement = null;
// Define a class name for our custom wrapper elements to easily identify them.
const WRAPPER_CLASS = 'gm-image-bg-wrapper';
/**
* Event listener for the 'contextmenu' event.
* When a user right-clicks, this function checks if the target is an image.
* If it is, both the image's source URL and the image DOM element are stored.
* Otherwise, both are reset to null.
*/
document.addEventListener('contextmenu', function(e) {
if (e.target.tagName === 'IMG') {
imageUrl = e.target.src;
currentImageElement = e.target; // Store the actual image element
} else {
imageUrl = null;
currentImageElement = null; // Clear both if a non-image element is right-clicked
}
}, true); // Use capture phase to ensure this runs before other context menu handlers
/**
* Registers a context menu command for "View Image in New Tab".
* When this command is selected, if an image URL is available, it opens the image
* viewer in a new tab with an initial white background. The new tab will have its
* own controls to change the background.
*/
GM_registerMenuCommand("View Image in New Tab", function() {
if (imageUrl) {
// Open the image viewer in a new tab, defaulting to a white background.
// The new tab will contain controls to change its background dynamically.
openImageViewer(imageUrl, 'white');
}
});
/**
* Registers a context menu command for "Set Image BG (White)".
* When selected, this applies a white background directly behind the clicked image
* on the current page.
*/
GM_registerMenuCommand("Set Image BG (White)", function() {
if (currentImageElement) {
setImageBackground(currentImageElement, 'white');
}
});
/**
* Registers a context menu command for "Set Image BG (Black)".
* When selected, this applies a black background directly behind the clicked image
* on the current page.
*/
GM_registerMenuCommand("Set Image BG (Black)", function() {
if (currentImageElement) {
setImageBackground(currentImageElement, 'black');
}
});
/**
* Registers a context menu command for "Set Image BG (Transparent)".
* When selected, this applies a checkerboard background directly behind the clicked image
* on the current page, simulating transparency.
*/
GM_registerMenuCommand("Set Image BG (Transparent)", function() {
if (currentImageElement) {
setImageBackground(currentImageElement, 'transparent');
}
});
/**
* Registers a context menu command for "Reset Image BG".
* When selected, this removes any custom background wrapper applied to the clicked image,
* returning it to its original state on the page.
*/
GM_registerMenuCommand("Reset Image BG", function() {
if (currentImageElement) {
resetImageBackground(currentImageElement);
}
});
/**
* Applies a background color or pattern behind the given image element on the current page.
* It either creates a new wrapper div or updates an existing one.
*
* @param {HTMLElement} imageElement - The <img> element to apply the background to.
* @param {string} bgColor - The desired background color ('white', 'black', or 'transparent').
*/
function setImageBackground(imageElement, bgColor) {
const parent = imageElement.parentNode;
let wrapper = null;
// Check if the image is already inside one of our wrappers
if (parent && parent.classList && parent.classList.contains(WRAPPER_CLASS)) {
wrapper = parent;
} else {
// Create a new wrapper div
wrapper = document.createElement('div');
wrapper.classList.add(WRAPPER_CLASS); // Mark it with our custom class
wrapper.style.display = 'inline-flex'; // Use inline-flex to wrap content and allow centering
wrapper.style.alignItems = 'center';
wrapper.style.justifyContent = 'center';
wrapper.style.padding = '10px'; // Padding to show the background
wrapper.style.borderRadius = '8px'; // Rounded corners for the background
wrapper.style.boxShadow = '0 4px 10px rgba(0,0,0,0.2)'; // Subtle shadow
wrapper.style.transition = 'background-color 0.3s ease, background-image 0.3s ease'; // Smooth transitions
wrapper.style.lineHeight = '0'; // Prevent extra space below image due to line-height
// Insert the wrapper before the image, then append the image to the wrapper
parent.insertBefore(wrapper, imageElement);
wrapper.appendChild(imageElement);
}
// Apply the chosen background style to the wrapper
if (bgColor === 'transparent') {
wrapper.style.backgroundImage = 'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)';
wrapper.style.backgroundSize = '20px 20px';
wrapper.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px';
wrapper.style.backgroundColor = ''; // Clear solid color if any
} else {
wrapper.style.backgroundImage = 'none';
wrapper.style.backgroundSize = 'auto';
wrapper.style.backgroundPosition = 'auto';
wrapper.style.backgroundColor = bgColor;
}
}
/**
* Resets the background of an image by removing the custom wrapper.
*
* @param {HTMLElement} imageElement - The <img> element whose background wrapper should be removed.
*/
function resetImageBackground(imageElement) {
const parent = imageElement.parentNode;
// Check if the parent is our custom wrapper
if (parent && parent.classList && parent.classList.contains(WRAPPER_CLASS)) {
const grandParent = parent.parentNode;
if (grandParent) {
// Move the image back to its original parent's position
grandParent.insertBefore(imageElement, parent);
// Remove the wrapper div
grandParent.removeChild(parent);
}
}
}
/**
* Opens a new tab with the image viewer HTML content.
* The HTML includes the image and controls to change the background color dynamically.
*
* @param {string} url - The URL of the image to display.
* @param {string} initialBgColor - The initial background color ('white', 'black', or 'transparent').
*/
function openImageViewer(url, initialBgColor) {
// Construct the HTML content for the new image viewer tab.
// It includes basic styling, the image, and JavaScript functions for changing the background.
const imageViewerHTML = `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Viewer</title>
<!-- Link to Google Fonts for Inter font -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<style>
/* Basic body styling for centering content and smooth background transitions */
body {
background-color: ${initialBgColor}; /* Set the initial background color */
margin: 0;
display: flex;
flex-direction: column; /* Stack controls and image vertically */
justify-content: center;
align-items: center;
min-height: 100vh; /* Full viewport height */
transition: background-color 0.3s ease; /* Smooth transition for background changes */
font-family: 'Inter', sans-serif; /* Apply Inter font */
color: #333; /* Default text color */
overflow: hidden; /* Prevent scrollbars if image is slightly larger */
}
/* Styling for the control buttons container */
.controls {
position: fixed; /* Keep controls visible even when scrolling (though this page won't scroll much) */
top: 20px; /* Position from the top */
left: 50%; /* Center horizontally */
transform: translateX(-50%); /* Adjust for true centering */
background-color: rgba(255, 255, 255, 0.9); /* Semi-transparent white background for controls */
padding: 10px 20px;
border-radius: 10px; /* Rounded corners for the control panel */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Subtle shadow for depth */
display: flex; /* Use flexbox for button layout */
gap: 10px; /* Space between buttons */
z-index: 1000; /* Ensure controls are on top of other content */
}
/* Styling for the individual background change buttons */
.controls button {
padding: 8px 15px;
border: none; /* No default border */
border-radius: 5px; /* Rounded corners for buttons */
cursor: pointer; /* Indicate interactivity */
font-size: 14px;
font-weight: 500;
transition: background-color 0.2s ease, transform 0.1s ease; /* Smooth hover and click effects */
background-color: #007bff; /* Primary blue color for buttons */
color: white; /* White text on buttons */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Small shadow for buttons */
}
/* Hover effect for buttons */
.controls button:hover {
background-color: #0056b3; /* Darker blue on hover */
transform: translateY(-1px); /* Slight lift effect */
}
/* Active (clicked) effect for buttons */
.controls button:active {
transform: translateY(1px); /* Slight press effect */
}
/* Styling for the image itself */
img {
max-width: 95%; /* Image takes up to 95% of the viewport width */
max-height: 95vh; /* Image takes up to 95% of the viewport height */
object-fit: contain; /* Ensures the entire image is visible within its bounds */
border-radius: 8px; /* Rounded corners for the image */
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25); /* More prominent shadow for the image */
margin-top: 80px; /* Push image down to clear controls */
}
/* Responsive adjustments for smaller screens */
@media (max-width: 600px) {
.controls {
flex-direction: column; /* Stack buttons vertically on small screens */
width: 80%; /* Wider control panel */
top: 10px;
}
.controls button {
width: 100%; /* Full width buttons */
}
img {
margin-top: 150px; /* Adjust margin for stacked controls */
}
}
</style>
</head>
<body>
<!-- Control panel for changing background colors -->
<div class="controls">
<button onclick="changeBg('white')">White Background</button>
<button onclick="changeBg('black')">Black Background</button>
<button onclick="changeBg('transparent')">Transparent Background</button>
</div>
<!-- The image display area -->
<img src="${url}" alt="Image Viewer">
<script>
/**
* JavaScript function to dynamically change the body's background.
* This function is called by the buttons in the control panel within the new tab.
* @param {string} color - The desired background color ('white', 'black', or 'transparent').
*/
function changeBg(color) {
const body = document.body;
if (color === 'transparent') {
// For a 'transparent' background, apply a checkerboard pattern
// This simulates transparency on a webpage that doesn't have a true transparent layer beneath it.
body.style.backgroundImage = 'linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, transparent 75%, #ccc 75%)';
body.style.backgroundSize = '20px 20px'; // Size of each checkerboard square
body.style.backgroundPosition = '0 0, 0 10px, 10px -10px, -10px 0px'; // Offset for checkerboard pattern
body.style.backgroundColor = ''; // Clear any solid background color
body.style.color = '#333'; // Default text color for checkerboard
} else {
// For solid colors, clear the checkerboard and set the solid color
body.style.backgroundImage = 'none'; // Remove checkerboard pattern
body.style.backgroundSize = 'auto';
body.style.backgroundPosition = 'auto';
body.style.backgroundColor = color; // Set the solid background color
// Adjust text color for readability
body.style.color = (color === 'black') ? '#eee' : '#333';
}
}
</script>
</body>
</html>
`;
// Create a Blob from the HTML string. This allows us to create a URL for the HTML content.
const blob = new Blob([imageViewerHTML], { type: 'text/html' });
// Create a URL for the Blob. This URL can be opened in a new tab.
const blobURL = URL.createObjectURL(blob);
// Open the new tab using Tampermonkey's GM_openInTab function.
GM_openInTab(blobURL, { active: true, insert: false });
// Optional: Revoke the Blob URL after a short delay.
// This frees up memory, as the browser will have already loaded the content from the URL.
setTimeout(() => {
URL.revokeObjectURL(blobURL);
}, 1000);
}
})();