i have a few more scripts i'm working on, but for now i thought people might like this one. to skip to the download, click here.
if you don't already have a user script manager, you'll need one to add my script!
if you haven't already, install Tampermonkey (or Greasemonkey) from the links above.
you can either download my script from this link, or you can copy the code and paste it into the manager yourself.
// ==UserScript==
// @name βͺβ§βΛ pixmix image adjustments+ v0.5
// @namespace http://tampermonkey.net/
// @version 0.5b
// @description adds 'saturation', 'contrast', and 'luminosity' sliders to stamps and fixes tint for text objects
// @author etoilee
// @match *://*.picmix.com/maker/app*
// @grant none
// ==/UserScript==
(function() {
'use strict';
if (typeof maker === 'undefined') {
console.error("cannot initialize, picmix maker not found...");
return;
}
//css
const style = document.createElement('style');
style.innerHTML = `
.mk-editable-label-wrapper {
display: flex;
align-items: center;
}
.mk-editable-label {
cursor: pointer;
font-size:12px;
text-decoration: underline;
text-decoration-style: dashed;
white-space: nowrap;
}
.mk-editable-input {
width: 60px;
text-align: center;
border-radius: 3px;
box-sizing: border-box;
display: none;
}
`;
document.head.appendChild(style);
// html
function addSlidersToDOM() {
const optionsPanel = document.getElementById('mk-options-panel');
if (optionsPanel) {
const hueSection = optionsPanel.querySelector('[data-section="hue"]');
const createSliderHTML = (id, labelText, min, max, step, defaultValue, unit) => `
<section data-section="${id.replace('mk-', '').replace('-input', '')}">
<div class="lines">
<div class="line">
<label>
<div class="mk-editable-label-wrapper">
<span class="mk-editable-label" data-target-input="${id}">${labelText}</span>
<input type="number" id="${id}-text" class="mk-editable-input"
min="${min}" max="${max}" step="${step}" value="${defaultValue}" data-unit="${unit}" />
</div>
</label>
</div>
<div class="line">
<input id="${id}" type="range" min="${min}" max="${max}" step="${step}"
value="${defaultValue}" data-default-value="${defaultValue}" data-unit="${unit}" class="mk-slider" />
</div>
</div>
</section>
`;
const saturationHTML = createSliderHTML("mk-saturation-input", "Saturation", -100, 200, 1, 100, "%");
const contrastHTML = createSliderHTML("mk-contrast-input", "Contrast", -100, 200, 1, 100, "%");
const luminosityHTML = createSliderHTML("mk-luminosity-input", "Luminosity", 0, 200, 1, 100, "%");
if (hueSection) {
hueSection.insertAdjacentHTML('afterend', luminosityHTML);
hueSection.insertAdjacentHTML('afterend', contrastHTML);
hueSection.insertAdjacentHTML('afterend', saturationHTML);
} else {
optionsPanel.insertAdjacentHTML('beforeend', saturationHTML);
optionsPanel.insertAdjacentHTML('beforeend', contrastHTML);
optionsPanel.insertAdjacentHTML('beforeend', luminosityHTML);
}
console.log("options panel update successful");
} else {
console.error("could not update options, not found");
}
}
if (typeof maker.getTextPatternObject === 'undefined') {
maker.getTextPatternObject = function(_textObject) {
if (_textObject && _textObject.isText) {
return _textObject.getObjects().find(o => o.isTextRect);
}
return _textObject;
};
}
// get object to apply filters to
function getFilterTargetObject(obj) {
if (maker.isTextObject(obj)) {
// apply filter to the pattern if text object
const textPatternObject = maker.getTextPatternObject(obj);
if (textPatternObject && (textPatternObject.isType("image") || textPatternObject.isType("sprite"))) {
return textPatternObject;
}
//otherwise apply filter to object directly
return obj;
}
return obj;
}
// get filter
function updateObjectFilter(object, filterType, propertyName, value) {
const targetObject = getFilterTargetObject(object);
// if text pattern is not image
if (maker.isTextObject(object) && !(targetObject.isType("image") || targetObject.isType("sprite"))) {
if (filterType === fabric.Image.filters.HueRotation) {
const hueDegrees = (value + 1) * 180;
const originalColor = new fabric.Color(object.fill);
const hsl = originalColor.toHSL();
hsl.h = hueDegrees;
object.set('fill', new fabric.Color(hsl).toRgb());
object.dirty = true;
maker.canvas.requestRenderAll();
maker.setDirtyTextObject(object);
return;
}
}
if (!targetObject || !targetObject.filters) {
return;
}
let filter = targetObject.filters.find(f => f instanceof filterType);
const defaultValue = filterType.DEFAULT_VALUE !== undefined ? filterType.DEFAULT_VALUE : (propertyName === 'brightness' ? 0 : 1);
if (value === defaultValue) {
if (filter) {
targetObject.filters = targetObject.filters.filter(f => f !== filter);
}
} else {
if (!filter) {
filter = new filterType();
targetObject.filters.push(filter);
}
filter[propertyName] = value;
}
targetObject.filters = targetObject.filters.filter(f => f);
targetObject.applyFilters();
if (maker.isTextObject(object)) {
maker.setDirtyTextObject(object);
}
}
// default definitions (maybe change later)
fabric.Image.filters.Saturation.DEFAULT_VALUE = 1;
fabric.Image.filters.Contrast.DEFAULT_VALUE = 1;
fabric.Image.filters.Brightness.DEFAULT_VALUE = 0;
fabric.Image.filters.HueRotation.DEFAULT_VALUE = 0;
maker.onSaturationInputChange = function () {
const activeObjects = maker.canvas.getActiveObjects();
const value = parseInt($(this).val()) / 100;
activeObjects.forEach(function (obj) {
if (maker.isImageObject(obj) || maker.isSpriteObject(obj) || maker.isTextObject(obj)) {
updateObjectFilter(obj, fabric.Image.filters.Saturation, 'saturation', value);
}
});
maker.canvas.requestRenderAll();
};
maker.onContrastInputChange = function () {
const activeObjects = maker.canvas.getActiveObjects();
const value = parseInt($(this).val()) / 100;
activeObjects.forEach(function (obj) {
if (maker.isImageObject(obj) || maker.isSpriteObject(obj) || maker.isTextObject(obj)) {
updateObjectFilter(obj, fabric.Image.filters.Contrast, 'contrast', value);
}
});
maker.canvas.requestRenderAll();
};
maker.onLuminosityInputChange = function () {
const activeObjects = maker.canvas.getActiveObjects();
const value = (parseInt($(this).val()) - 100) / 100;
activeObjects.forEach(function (obj) {
if (maker.isImageObject(obj) || maker.isSpriteObject(obj) || maker.isTextObject(obj)) {
updateObjectFilter(obj, fabric.Image.filters.Brightness, 'brightness', value);
}
});
maker.canvas.requestRenderAll();
};
const originalOnHueInputChange = maker.onHueInputChange;
maker.onHueInputChange = function (event) {
const activeObjects = maker.canvas.getActiveObjects();
let value = (parseInt($(this).val()) / 50) - 1;
activeObjects.forEach(function (obj) {
if (maker.isTextObject(obj)) {
const textPatternObject = maker.getTextPatternObject(obj);
if (textPatternObject && (textPatternObject.isType("image") || textPatternObject.isType("sprite"))) {
updateObjectFilter(obj, fabric.Image.filters.HueRotation, 'rotation', value);
} else {
const originalFill = new fabric.Color(obj.fill);
const hsl = originalFill.toHSL();
let currentHueDegrees = hsl.h;
let rotatedHueDegrees = (currentHueDegrees + (value * 180)) % 360;
if (rotatedHueDegrees < 0) {
rotatedHueDegrees += 360;
}
hsl.h = rotatedHueDegrees;
obj.set('fill', new fabric.Color(hsl).toRgb());
obj.dirty = true;
maker.setDirtyTextObject(obj);
}
} else if (maker.isImageObject(obj) || maker.isSpriteObject(obj)) {
updateObjectFilter(obj, fabric.Image.filters.HueRotation, 'rotation', value);
}
});
maker.canvas.requestRenderAll();
};
const originalUpdatePanel = maker.updatePanel;
maker.updatePanel = function() {
originalUpdatePanel.apply(this, arguments);
const activeObjects = maker.canvas.getActiveObjects();
let applicableObjects = [];
activeObjects.forEach(obj => {
if (maker.isImageObject(obj) || maker.isSpriteObject(obj) || maker.isTextObject(obj)) {
applicableObjects.push(obj);
}
});
const canApplyFilters = applicableObjects.length > 0;
const saturationInput = $("#mk-saturation-input");
const contrastInput = $("#mk-contrast-input");
const luminosityInput = $("#mk-luminosity-input");
const hueInput = $("#mk-hue-input");
const saturationTextInput = $("#mk-saturation-input-text");
const contrastTextInput = $("#mk-contrast-input-text");
const luminosityTextInput = $("#mk-luminosity-input-text");
const saturationLabel = $('span[data-target-input="mk-saturation-input"]');
const contrastLabel = $('span[data-target-input="mk-contrast-input"]');
const luminosityLabel = $('span[data-target-input="mk-luminosity-input"]');
const saturationSection = $("#mk-context-panel").find("[data-section=\"saturation\"]");
const contrastSection = $("#mk-context-panel").find("[data-section=\"contrast\"]");
const luminositySection = $("#mk-context-panel").find("[data-section=\"luminosity\"]");
const hueSection = $("#mk-context-panel").find("[data-section=\"hue\"]"); // Get the existing hue section
if (canApplyFilters) {
let totalSaturation = 0;
let totalContrast = 0;
let totalLuminosity = 0;
let totalHueRotation = 0;
applicableObjects.forEach(obj => {
const targetObject = getFilterTargetObject(obj);
if (targetObject && targetObject.filters) {
let objSaturation = fabric.Image.filters.Saturation.DEFAULT_VALUE;
let objContrast = fabric.Image.filters.Contrast.DEFAULT_VALUE;
let objLuminosity = fabric.Image.filters.Brightness.DEFAULT_VALUE;
let objHueRotation = fabric.Image.filters.HueRotation.DEFAULT_VALUE;
targetObject.filters.forEach(filter => {
if (filter instanceof fabric.Image.filters.Saturation) {
objSaturation = filter.saturation;
} else if (filter instanceof fabric.Image.filters.Contrast) {
objContrast = filter.contrast;
} else if (filter instanceof fabric.Image.filters.Brightness) {
objLuminosity = filter.brightness;
} else if (filter instanceof fabric.Image.filters.HueRotation) {
objHueRotation = filter.rotation;
}
});
totalSaturation += objSaturation;
totalContrast += objContrast;
totalLuminosity += objLuminosity;
totalHueRotation += objHueRotation;
} else if (maker.isTextObject(obj) && !targetObject.isType("image") && !targetObject.isType("sprite")) {
// for plain text with no fill (no use since plain text is always black?)
totalHueRotation += fabric.Image.filters.HueRotation.DEFAULT_VALUE;
}
});
const avgSaturation = totalSaturation / applicableObjects.length;
const avgContrast = totalContrast / applicableObjects.length;
const avgLuminosity = totalLuminosity / applicableObjects.length;
const avgHueRotation = totalHueRotation / applicableObjects.length;
saturationInput.val(Math.round(avgSaturation * 100));
contrastInput.val(Math.round(avgContrast * 100));
luminosityInput.val(Math.round((avgLuminosity + 1) * 100));
hueInput.val(Math.round((avgHueRotation + 1) * 50));
saturationInput.prop("disabled", false);
contrastInput.prop("disabled", false);
luminosityInput.prop("disabled", false);
hueInput.prop("disabled", false);
saturationTextInput.prop("disabled", false);
contrastTextInput.prop("disabled", false);
luminosityTextInput.prop("disabled", false);
saturationSection.removeClass('disabled');
contrastSection.removeClass('disabled');
luminositySection.removeClass('disabled');
hueSection.removeClass('disabled');
} else {
saturationInput.prop("disabled", true).val(saturationInput.data('default-value'));
contrastInput.prop("disabled", true).val(contrastInput.data('default-value'));
luminosityInput.prop("disabled", true).val(luminosityInput.data('default-value'));
hueInput.prop("disabled", true).val(0);
saturationTextInput.prop("disabled", true);
contrastTextInput.prop("disabled", true);
luminosityTextInput.prop("disabled", true);
saturationSection.addClass("disabled");
contrastSection.addClass("disabled");
luminositySection.addClass("disabled");
hueSection.addClass("disabled");
saturationLabel.text('Saturation');
contrastLabel.text('Contrast');
luminosityLabel.text('Luminosity');
}
};
// tippy tooltip
function setupSliderPreview() {
let sliderPreviewDiv = $('#' + maker.sliderPreviewValueDivId);
if (sliderPreviewDiv.length === 0) {
sliderPreviewDiv = $("<div />", {
'id': maker.sliderPreviewValueDivId,
'class': 'mk-slider-value-preview'
}).appendTo($("body"));
}
$('#mk-saturation-input, #mk-contrast-input, #mk-luminosity-input, #mk-hue-input').on("input mousedown", function (event) {
const _0x1d9c08 = $(this);
const _0x2377f0 = _0x1d9c08.attr("data-unit");
const _0x3402cd = _0x1d9c08.val() + (_0x2377f0 || '');
const _0x589990 = _0x1d9c08.offset().top;
const _0x113797 = maker.mousePosition ? maker.mousePosition.x : event.clientX;
sliderPreviewDiv.html(_0x3402cd).css({
'top': _0x589990 + 'px',
'left': _0x113797 + 'px'
}).show();
});
$('#mk-saturation-input, #mk-contrast-input, #mk-luminosity-input, #mk-hue-input').on("change mouseup", function () {
sliderPreviewDiv.hide();
});
}
// make labels editable
function setupEditableLabels() {
$('.mk-editable-label').each(function() {
const labelSpan = $(this);
const sliderId = labelSpan.data('target-input');
const sliderInput = $(`#${sliderId}`);
const textInput = $(`#${sliderId}-text`);
const originalLabelText = labelSpan.text();
labelSpan.text(originalLabelText);
labelSpan.on('click', function() {
if (sliderInput.prop('disabled')) {
return;
}
labelSpan.hide();
textInput.val(sliderInput.val() + (sliderInput.data('unit') || ''));
textInput.show();
textInput.focus();
textInput.select();
});
const applyValueFromTextInput = function() {
const rawValue = textInput.val();
let numericValue = parseFloat(rawValue);
const unit = sliderInput.data('unit');
if (unit && rawValue.endsWith(unit)) {
numericValue = parseFloat(rawValue.slice(0, -unit.length));
}
const min = parseFloat(sliderInput.attr('min'));
const max = parseFloat(sliderInput.attr('max'));
const defaultValue = parseFloat(sliderInput.data('default-value'));
if (isNaN(numericValue)) {
numericValue = defaultValue; // fallback
} else if (numericValue < min) {
numericValue = min;
} else if (numericValue > max) {
numericValue = max;
}
numericValue = Math.round(numericValue);
sliderInput.val(numericValue);
sliderInput.trigger('input'); // trigger input for live updates
sliderInput.trigger('change'); // trigger change for final update
// hide the input, display the label
labelSpan.text(originalLabelText);
textInput.hide();
labelSpan.show();
};
textInput.on('blur', applyValueFromTextInput);
textInput.on('keypress', function(e) {
if (e.which === 13) { // enter key is the default
e.preventDefault();
applyValueFromTextInput();
}
});
});
}
// init
setTimeout(() => {
addSlidersToDOM();
$("#mk-saturation-input").on("input change", maker.onSaturationInputChange);
$("#mk-contrast-input").on("input change", maker.onContrastInputChange);
$("#mk-luminosity-input").on("input change", maker.onLuminosityInputChange);
$("#mk-hue-input").off("input change").on("input change", maker.onHueInputChange);
setupSliderPreview();
setupEditableLabels();
console.log("picmix image adjustment userscript loaded");
maker.updatePanel();
}, 1000); //wait 1000 ms
})();
after downloading, your user script manager (e.g., Tampermonkey) will usually open a new tab asking you to confirm the installation. It will show you the code and ask if you want to install it. click the "Install" button.
Important!
make sure you see the code of the script! it should look something like the one above.
once installed, return to or reload the picmix editor page. the script will automatically activate!
in the Picmix Maker, add an image, a gif, or a text object to your canvas as normal.
in the top panel, under "Options" you will now see new sections for "Saturation", "Contrast", and "Luminosity".

drag the sliders to change the corresponding image properties.

for precise adjustments, click on the underlined "Saturation", "Contrast", or "Luminosity" label above the slider.
this will reveal a text input box that looks something like this:
where you can type in exact numeric values. press enter or click outside the box to apply.
this userscript will make creating picmix a lot less painful. i implemented a layer window and favoriting stamps in the editor. you can download the userscript here.
if you don't already have a userscript manager, you'll need one to add my script
if you haven't already, install Tampermonkey from the links above.
you can either download my script from this link, or you can copy the code and paste it into the manager yourself.
// ==UserScript==
// @name ππββΛpicmix layer manager + favorites
// @namespace http://tampermonkey.net/
// @version 1.0
// @description adds a layer panel to picmix for easier stamp management, enables favoriting stamps
// @match *://*.picmix.com/maker/app*
// @grant none
// @author etoilee
// ==/UserScript==
(function() {
'use strict';
document.body.dataset.layerPluginActive = 'true';
function getStampKeyFromSrcThumb(srcThumbUrl) {
const match = srcThumbUrl.match(/_([a-f0-9]+)\.(png|gif|jpg)$/i);
if (match && match[1]) {
return match[1];
}
return null;
}
function getObjectName(object) {
if (object.isType('image') || object.isType('sprite')) {
const stampInfo = window.maker.stampInfos.find(info => info.element === object._originalElement);
if (stampInfo && stampInfo.name) {
return stampInfo.name;
}
return object.isType('sprite') ? 'Animated Stamp' : 'Stamp';
} else if (object.isType('i-text')) {
return object.text;
}
return object.type || 'Object';
}
function createFavoriteButton(stampId, stampKey, isFavorite) {
const favoriteButton = document.createElement('button');
const favoritedColor = '#FFD700';
const unfavoritedColor = 'black';
favoriteButton.classList.add('stamp-favorite-button');
favoriteButton.style.cssText = `
background: none;
border: none;
cursor: pointer;
padding: 0;
margin-left: auto;
font-size: 1em;
color: ${isFavorite ? favoritedColor : unfavoritedColor};
transition: color 0.2s;
text-shadow: 0px 0px 2px white;
line-height: 1;
z-index: 10;
position: relative;
`;
favoriteButton.innerHTML = isFavorite ? '&#9733;' : '&#9734;';
favoriteButton.title = isFavorite ? 'Unfavorite' : 'Favorite';
favoriteButton.onclick = function(event) {
event.stopPropagation();
const currentIsFavorite = favoriteButton.innerHTML === '&#9733;';
const originalAddRemoveStampFavorite = window.addRemoveStampFavorite;
const originalSuccessCallback = window.addRemoveStampFavorite.successCallback;
window.addRemoveStampFavorite.successCallback = function(json) {
if (originalSuccessCallback) {
originalSuccessCallback(json);
}
const targetStampInfo = window.maker.stampInfos.find(info => String(info.id) === String(stampId) && String(info.key) === String(stampKey));
if (targetStampInfo) {
targetStampInfo.isFavorite = !currentIsFavorite;
}
if (document.getElementById('picmix-layers-panel')) {
updateLayerPanel();
}
window.addRemoveStampFavorite.successCallback = originalSuccessCallback;
};
originalAddRemoveStampFavorite(
this,
stampId,
stampKey,
'&star;',
'&starf;',
'favorite-icon',
'remove-favorite-icon'
);
};
return favoriteButton;
}
const originalDisplayStamps = window.maker.displayStamps;
window.maker.displayStamps = function(_stamps) {
_stamps.forEach(stamp => {
if (!stamp.key && stamp.srcThumb) {
stamp.key = getStampKeyFromSrcThumb(stamp.srcThumb);
}
const existingStampInfo = window.maker.stampInfos.find(info => String(info.id) === String(stamp.id) && String(info.key) === String(stamp.key));
if (existingStampInfo) {
stamp.isFavorite = existingStampInfo.isFavorite;
} else if (typeof stamp.isFavorite === 'undefined') {
stamp.isFavorite = false;
}
});
originalDisplayStamps.apply(this, arguments);
const stampList = document.getElementById('mk-stamps-list');
if (stampList) {
stampList.querySelectorAll('.mk-stamp-preview').forEach((stampPreview, index) => {
const stampInfo = _stamps[index];
if (stampInfo && stampInfo.id && stampInfo.key) {
if (!stampPreview.querySelector('.stamp-favorite-button')) {
const favoriteButton = createFavoriteButton(stampInfo.id, stampInfo.key, stampInfo.isFavorite);
stampPreview.style.position = 'relative';
favoriteButton.style.position = 'absolute';
favoriteButton.style.top = '2px';
favoriteButton.style.right = '2px';
stampPreview.appendChild(favoriteButton);
} else {
const existingButton = stampPreview.querySelector('.stamp-favorite-button');
existingButton.innerHTML = stampInfo.isFavorite ? '&#9733;' : '&#9734;';
existingButton.style.color = stampInfo.isFavorite ? 'gold' : '#ccc';
existingButton.title = stampInfo.isFavorite ? 'Unfavorite' : 'Favorite';
}
}
});
}
};
function updateLayerPanel() {
const layersList = document.getElementById('picmix-layers-list');
if (!layersList) return;
layersList.innerHTML = '';
const objects = window.maker.canvas.getObjects();
const activeObject = window.maker.canvas.getActiveObject();
for (let i = objects.length - 1; i >= 0; i--) {
const obj = objects[i];
if (obj.isDrawingBackground) continue;
const listItem = document.createElement('li');
listItem.dataset.objectId = window.maker.canvas._objects.indexOf(obj);
listItem.style.cssText = `
display: flex; align-items: center; padding: 5px; border-radius: 10px;
background: linear-gradient(to bottom,rgba(255,255,255,1) 0,rgba(240,240,240,1) 100%); cursor: pointer; border: 2px solid transparent;
position: relative; font-size: 12px; color: #black; overflow: hidden;
`;
if (activeObject === obj) {
listItem.style.borderColor = '#5fb0ee';
listItem.style.background = 'linear-gradient(to bottom,rgba(227,243,255,1) 0,rgba(189,228,255,1) 100%)';
}
const thumbCanvas = document.createElement('canvas');
thumbCanvas.width = 30; thumbCanvas.height = 30;
const thumbCtx = thumbCanvas.getContext('2d');
const originalProps = { scaleX: obj.scaleX, scaleY: obj.scaleY, left: obj.left, top: obj.top, angle: obj.angle, originX: obj.originX, originY: obj.originY };
const scaleFactor = Math.min(thumbCanvas.width / (obj.width * (obj.scaleX || 1)), thumbCanvas.height / (obj.height * (obj.scaleY || 1)));
obj.set({ scaleX: scaleFactor, scaleY: scaleFactor, left: thumbCanvas.width / 2, top: thumbCanvas.height / 2, originX: 'center', originY: 'center', angle: 0 });
obj.render(thumbCtx);
obj.set(originalProps);
obj.setCoords();
const img = document.createElement('img');
img.src = thumbCanvas.toDataURL();
img.style.cssText = 'width: 30px; height: 30px; margin-right: 5px; background: #eee; border-radius:10px;';
listItem.appendChild(img);
const nameSpan = document.createElement('span');
nameSpan.textContent = getObjectName(obj);
nameSpan.style.cssText = 'flex-grow: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;';
nameSpan.title = getObjectName(obj);
listItem.appendChild(nameSpan);
const stampId = window.maker.getStampIdFromObject(obj);
if (stampId) {
const stampInfo = window.maker.stampInfos.find(info => String(info.id) === String(stampId));
if (stampInfo && stampInfo.key) {
const favoriteButton = createFavoriteButton(stampInfo.id, stampInfo.key, stampInfo.isFavorite);
listItem.appendChild(favoriteButton);
}
}
const controlsDiv = document.createElement('div');
controlsDiv.style.cssText = 'margin-left: 5px; display: flex; gap: 2px;';
const alphaClipButtonHTML = document.body.dataset.alphaClipPluginActive === 'true'
? `<button class="layer-control-btn alpha-clip-placeholder" data-object-index="${i}" title="Toggle Alpha Clip" style="background: ${obj.clipPath ? '#2e9942' : '#555'}; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#x2702;</button>`
: '';
controlsDiv.innerHTML = `
${alphaClipButtonHTML}
<button class="layer-control-btn" data-action="bringForward" title="Bring Forward" style="background: dodgerblue; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#9650;</button>
<button class="layer-control-btn" data-action="sendBackward" title="Send Backward" style="background: dodgerblue ; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#9660;</button>
<button class="layer-control-btn" data-action="remove" title="Delete Layer" style="background: indianred; border: none; color: white; cursor: pointer; padding: 2px 5px; border-radius: 5px; font-size: 10px;">&#x2715;</button>
`;
listItem.appendChild(controlsDiv);
listItem.addEventListener('click', (event) => {
if (event.target.closest('.layer-control-btn, .stamp-favorite-button')) return;
window.maker.canvas.setActiveObject(obj);
window.maker.canvas.requestRenderAll();
updateLayerPanel();
});
controlsDiv.querySelector('[data-action="bringForward"]').addEventListener('click', () => { obj.bringForward(); updateLayerPanel(); });
controlsDiv.querySelector('[data-action="sendBackward"]').addEventListener('click', () => { obj.sendBackwards(); updateLayerPanel(); });
controlsDiv.querySelector('[data-action="remove"]').addEventListener('click', () => { window.maker.canvas.remove(obj); updateLayerPanel(); });
layersList.appendChild(listItem);
}
document.dispatchEvent(new CustomEvent('layerPanelUpdated'));
}
function addLayerPanelHTML() {
const panelHtml = `
<style>
#picmix-layers-panel { position: absolute; right: 10px; top: 100px; width: 250px; background: linear-gradient(101deg,rgba(223, 30, 126, 0.52) 0%, rgba(247, 141, 52, 0.19) 99%); border-radius: 15px; color: black; font-family: sans-serif; z-index: 1; box-sizing: border-box; }
#picmix-layers-panel-header { display: flex; justify-content: space-between; border-radius:10px; margin:5px; align-items: center; padding: 5px 10px; background: linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 1) 38%, rgba(237, 237, 237, 1) 55%, rgb(231 229 229) 69%, rgba(230, 230, 230, 1) 98%); border-bottom: 1px solid #bfbfbf; }
#picmix-layers-panel h3 { margin: 0; font-size: 14px; text-align: center; flex-grow: 1; }
#picmix-layers-toggle-btn { background: none; border: none; color: black; font-size: 18px; cursor: pointer; padding: 0 5px; line-height: 1; }
#picmix-layers-list { list-style: none; padding: 0; margin: 0; max-height: 70vh; overflow-y: auto; display: none; padding: 10px; }
#picmix-layers-panel.expanded #picmix-layers-list { display: block; }
#picmix-layers-list::-webkit-scrollbar { width: 8px; }
#picmix-layers-list::-webkit-scrollbar-track { background: transparent; }
#picmix-layers-list::-webkit-scrollbar-thumb { background-color: white; border-radius: 4px; border: 2px solid #ddd; }
</style>
<div id="picmix-layers-panel">
<div id="picmix-layers-panel-header">
<h3>Layers</h3>
<button id="picmix-layers-toggle-btn">&#9660;</button>
</div>
<ul id="picmix-layers-list"></ul>
</div>
`;
document.body.insertAdjacentHTML('beforeend', panelHtml);
const toggleButton = document.getElementById('picmix-layers-toggle-btn');
const layersPanel = document.getElementById('picmix-layers-panel');
toggleButton.addEventListener('click', () => {
const isExpanded = layersPanel.classList.toggle('expanded');
toggleButton.innerHTML = isExpanded ? '&#9650;' : '&#9660;';
if (isExpanded) {
updateLayerPanel();
}
});
}
function setupCanvasEventHandlers() {
const canvasEvents = ['object:added', 'selection:created', 'selection:updated', 'selection:cleared', 'object:removed', 'object:modified'];
canvasEvents.forEach(eventName => {
window.maker.canvas.on(eventName, () => {
if (document.getElementById('picmix-layers-panel').classList.contains('expanded')) {
updateLayerPanel();
}
});
});
}
function initialize() {
if (window.maker && window.maker.canvas && !document.getElementById('picmix-layers-panel')) {
addLayerPanelHTML();
setupCanvasEventHandlers();
window.updateLayerManagerPanel = updateLayerPanel;
} else {
setTimeout(initialize, 500);
}
}
initialize();
})();
after downloading, your userscript manager (e.g., Tampermonkey) will usually open a new tab asking you to confirm the installation. it will show you the code and ask if you want to install it. click the "Install" button.
Important!
Make sure you see the code of the script! It should look something like the one above.
once installed, return to or reload the picMix editor page. the script will automatically activate!
after downloading the script, head to the PicMix maker. you'll see a new button that says "Layers", click it to open the layers menu.
after adding a stamp or text, your layers will start to fill up the list. you can now move a layer back and forward using the added buttons. clicking a layer will select it.
you can now delete a layer by clicking the red "x" button. you can also move a selected layer with your arrow keys if you click back into the maker while the move button is active.
if you want to view the full title of a stamp, you can hover over the title and a tooltip will appear.
after downloading the userscript, a star will appear in the top right corner of a stamp. click the star to favorite a stamp, and check out your favorites to see it there.
*please note: the stars will appear empty when first clicking this button, regardless of whether you've favorited it before. this is a limitation of the favoriting feature and is only cosmetic; favoriting and unfavoriting still work!*
if you look at the layer list, you will also be able to favorite and unfavorite from here.
it's OUT! check it out here for a full rundown and download.