Merge pull request #52 from outis1one/claude/peaceful-gauss-9tfiw0

Claude/peaceful gauss 9tfiw0
This commit is contained in:
Outis
2026-06-11 22:59:50 -04:00
committed by GitHub
3 changed files with 200 additions and 32 deletions
+21 -6
View File
@@ -164,26 +164,41 @@ class Image_frame_fit_class {
resultCanvas.height = img.naturalHeight; resultCanvas.height = img.naturalHeight;
resultCanvas.getContext('2d').drawImage(img, 0, 0); resultCanvas.getContext('2d').drawImage(img, 0, 0);
var fitW = img.naturalWidth;
var fitH = img.naturalHeight;
if (params.new_layer) { if (params.new_layer) {
var dataURL = img.src; var dataURL = img.src;
app.State.do_action( app.State.do_action(
new app.Actions.Bundle_action('frame_fit_layer', 'Fit to Frame', [ new app.Actions.Bundle_action('frame_fit_layer', 'Fit to Frame', [
new app.Actions.Prepare_canvas_action('undo'),
new app.Actions.Update_config_action({
WIDTH: fitW,
HEIGHT: fitH,
}),
new app.Actions.Insert_layer_action({ new app.Actions.Insert_layer_action({
name: `${frameKey} fit`, name: `${frameKey} fit`,
type: 'image', type: 'image',
data: dataURL, data: dataURL,
x: 0, y: 0, x: 0, y: 0,
width: img.naturalWidth, width: fitW,
height: img.naturalHeight, height: fitH,
width_original: img.naturalWidth, width_original: fitW,
height_original: img.naturalHeight, height_original: fitH,
}) }),
new app.Actions.Prepare_canvas_action('do'),
]) ])
); );
} else { } else {
app.State.do_action( app.State.do_action(
new app.Actions.Bundle_action('frame_fit', 'Fit to Frame', [ new app.Actions.Bundle_action('frame_fit', 'Fit to Frame', [
new app.Actions.Update_layer_image_action(resultCanvas) new app.Actions.Prepare_canvas_action('undo'),
new app.Actions.Update_config_action({
WIDTH: fitW,
HEIGHT: fitH,
}),
new app.Actions.Update_layer_image_action(resultCanvas),
new app.Actions.Prepare_canvas_action('do'),
]) ])
); );
} }
+174 -26
View File
@@ -30,6 +30,7 @@ class Image_resize_class {
this.Tools_settings = new Tools_settings_class(); this.Tools_settings = new Tools_settings_class();
this.pica = Pica(); this.pica = Pica();
this.Helper = new Helper_class(); this.Helper = new Helper_class();
this._lastUnits = 'pixels';
this.set_events(); this.set_events();
} }
@@ -50,25 +51,31 @@ class Image_resize_class {
resize() { resize() {
var _this = this; var _this = this;
var units = this.Tools_settings.get_setting('default_units'); var savedUnits = this.Tools_settings.get_setting('default_units');
var resolution = this.Tools_settings.get_setting('resolution'); var resolution = this.Tools_settings.get_setting('resolution');
//convert units var displayUnits = (savedUnits === 'inches') ? 'inches' : 'pixels';
var width = this.Helper.get_user_unit(config.WIDTH, units, resolution); this._lastUnits = displayUnits;
var height = this.Helper.get_user_unit(config.HEIGHT, units, resolution);
var width = this.Helper.get_user_unit(config.WIDTH, displayUnits, resolution);
var height = this.Helper.get_user_unit(config.HEIGHT, displayUnits, resolution);
var settings = { var settings = {
title: 'Resize', title: 'Resize',
params: [ params: [
{name: "width", title: "Width:", value: '', placeholder: width, comment: units}, {name: "units", title: "Units:", value: displayUnits, values: ["pixels", "inches"]},
{name: "height", title: "Height:", value: '', placeholder: height, comment: units}, {name: "width", title: "Width:", value: '', placeholder: width, comment: displayUnits},
{name: "height", title: "Height:", value: '', placeholder: height, comment: displayUnits},
{name: "width_percent", title: "Width (%):", value: '', placeholder: 100, comment: "%"}, {name: "width_percent", title: "Width (%):", value: '', placeholder: 100, comment: "%"},
{name: "height_percent", title: "Height (%):", value: '', placeholder: 100, comment: "%"}, {name: "height_percent", title: "Height (%):", value: '', placeholder: 100, comment: "%"},
{name: "mode", title: "Mode:", values: ["Lanczos", "Hermite", "Basic"]}, {name: "mode", title: "Mode:", values: ["Lanczos", "Hermite", "Basic"]},
{name: "crop_to_fill", title: "Crop to fill:", value: false},
{name: "sharpen", title: "Sharpen:", value: false}, {name: "sharpen", title: "Sharpen:", value: false},
{name: "layers", title: "Layers:", values: ["All", "Active"], value: "All"}, {name: "layers", title: "Layers:", values: ["All", "Active"], value: "All"},
], ],
on_change: function(params) {
_this.units_change_handler(params);
},
on_finish: function (params) { on_finish: function (params) {
_this.do_resize(params); _this.do_resize(params);
}, },
@@ -78,16 +85,63 @@ class Image_resize_class {
document.getElementById("pop_data_width").select(); document.getElementById("pop_data_width").select();
} }
/**
* Called on any dialog field change; reacts only when the units radio switches.
* Updates width/height placeholders and labels, and persists the choice globally.
*/
units_change_handler(params) {
var units = params.units;
if (units === this._lastUnits) return;
this._lastUnits = units;
var resolution = this.Tools_settings.get_setting('resolution');
// Persist so Canvas Size and other dialogs open with the same units
const unitShort = {pixels: 'px', inches: '"', centimeters: 'cm', millimetres: 'mm'};
this.Tools_settings.save_setting('default_units', units);
this.Tools_settings.save_setting('default_units_short', unitShort[units] || units);
var newWidth = this.Helper.get_user_unit(config.WIDTH, units, resolution);
var newHeight = this.Helper.get_user_unit(config.HEIGHT, units, resolution);
var widthInput = document.getElementById('pop_data_width');
var heightInput = document.getElementById('pop_data_height');
if (widthInput) {
widthInput.placeholder = newWidth;
widthInput.value = '';
}
if (heightInput) {
heightInput.placeholder = newHeight;
heightInput.value = '';
}
var wComment = widthInput ? widthInput.nextElementSibling : null;
var hComment = heightInput ? heightInput.nextElementSibling : null;
if (wComment && wComment.classList.contains('field_comment')) wComment.textContent = units;
if (hComment && hComment.classList.contains('field_comment')) hComment.textContent = units;
}
async do_resize(params) { async do_resize(params) {
//validate //validate
if (isNaN(params.width) && isNaN(params.height) && isNaN(params.width_percent) && isNaN(params.height_percent)) { if (isNaN(params.width) && isNaN(params.height) && isNaN(params.width_percent) && isNaN(params.height_percent)) {
alertify.error('Missing at least 1 size parameter.'); alertify.error('Missing at least 1 size parameter.');
return false; return false;
} }
// Crop-to-fill: scale to cover then center-crop; requires both dimensions
if (params.crop_to_fill == true) {
if (isNaN(params.width) || isNaN(params.height)) {
alertify.error('Crop to fill requires both Width and Height.');
return false;
}
if (params.layers == 'All') {
return this.do_resize_crop_fill(params);
}
}
// Build a list of actions to execute for resize // Build a list of actions to execute for resize
let actions = []; let actions = [];
if (params.layers == 'All') { if (params.layers == 'All') {
//resize all layers //resize all layers
var skips = 0; var skips = 0;
@@ -112,15 +166,109 @@ class Image_resize_class {
); );
} }
/**
* Resize all image layers using cover-scale then center-crop so the subject
* looks the same regardless of target aspect ratio (no stretching).
*/
async do_resize_crop_fill(params) {
var units = params.units || this.Tools_settings.get_setting('default_units');
var resolution = this.Tools_settings.get_setting('resolution');
var targetWidth = this.Helper.get_internal_unit(parseFloat(params.width), units, resolution);
var targetHeight = this.Helper.get_internal_unit(parseFloat(params.height), units, resolution);
targetWidth = parseInt(targetWidth);
targetHeight = parseInt(targetHeight);
if (!targetWidth || !targetHeight || targetWidth < 1 || targetHeight < 1) {
alertify.error('Invalid dimensions for crop to fill.');
return;
}
var srcWidth = config.WIDTH;
var srcHeight = config.HEIGHT;
// Cover scale: image fills target, excess is cropped from center
var scale = Math.max(targetWidth / srcWidth, targetHeight / srcHeight);
var scaledW = Math.round(srcWidth * scale);
var scaledH = Math.round(srcHeight * scale);
var cropX = Math.round((scaledW - targetWidth) / 2);
var cropY = Math.round((scaledH - targetHeight) / 2);
var mode = params.mode;
var sharpen = params.sharpen;
let actions = [];
for (var i in config.layers) {
var layer = config.layers[i];
if (layer.type !== 'image') continue;
if (layer.width == null || layer.height == null) continue;
var canvas = this.Base_layers.convert_layer_to_canvas(layer.id, true, false);
var newLayerW = Math.round(layer.width * scale);
var newLayerH = Math.round(layer.height * scale);
var useMode = mode;
if (useMode == "Hermite" && (newLayerW > canvas.width || newLayerH > canvas.height)) {
useMode = "Lanczos";
}
var tmp = document.createElement('canvas');
tmp.width = newLayerW;
tmp.height = newLayerH;
if (useMode == "Lanczos") {
await this.pica.resize(canvas, tmp, {alpha: true});
} else if (useMode == "Hermite") {
tmp.getContext('2d').drawImage(canvas, 0, 0);
this.Hermite.resample_single(tmp, newLayerW, newLayerH, true);
} else {
tmp.getContext('2d').drawImage(canvas, 0, 0, newLayerW, newLayerH);
}
if (sharpen == true) {
var ctx = tmp.getContext('2d');
var imageData = ctx.getImageData(0, 0, tmp.width, tmp.height);
ctx.putImageData(this.ImageFilters.Sharpen(imageData, 1), 0, 0);
}
var newX = Math.round(layer.x * scale) - cropX;
var newY = Math.round(layer.y * scale) - cropY;
actions.push(new app.Actions.Update_layer_image_action(tmp, layer.id));
actions.push(new app.Actions.Update_layer_action(layer.id, {
x: newX,
y: newY,
width: newLayerW,
height: newLayerH,
width_original: newLayerW,
height_original: newLayerH,
}));
}
// Update canvas dimensions to exact target
actions = actions.concat([
new app.Actions.Prepare_canvas_action('undo'),
new app.Actions.Update_config_action({
WIDTH: targetWidth,
HEIGHT: targetHeight,
}),
new app.Actions.Prepare_canvas_action('do'),
]);
return app.State.do_action(
new app.Actions.Bundle_action('resize_layers', 'Resize Layers', actions)
);
}
/** /**
* Generates actions that will resize layer (image, text, vector), returns a promise that rejects on failure. * Generates actions that will resize layer (image, text, vector), returns a promise that rejects on failure.
* *
* @param {object} layer * @param {object} layer
* @param {object} params * @param {object} params
* @returns {Promise<object>} Returns array of actions to perform * @returns {Promise<object>} Returns array of actions to perform
*/ */
async resize_layer(layer, params) { async resize_layer(layer, params) {
var units = this.Tools_settings.get_setting('default_units'); var units = params.units || this.Tools_settings.get_setting('default_units');
var resolution = this.Tools_settings.get_setting('resolution'); var resolution = this.Tools_settings.get_setting('resolution');
var mode = params.mode; var mode = params.mode;
var width = parseFloat(params.width); var width = parseFloat(params.width);
@@ -168,7 +316,7 @@ class Image_resize_class {
let new_y = params.layers == 'All' ? Math.round(layer.y * height / config.HEIGHT) : layer.y; let new_y = params.layers == 'All' ? Math.round(layer.y * height / config.HEIGHT) : layer.y;
let xratio = width / config.WIDTH; let xratio = width / config.WIDTH;
let yratio = height / config.HEIGHT; let yratio = height / config.HEIGHT;
//is text //is text
if (layer.type == 'text') { if (layer.type == 'text') {
let data = JSON.parse(JSON.stringify(layer.data)); let data = JSON.parse(JSON.stringify(layer.data));
@@ -183,7 +331,7 @@ class Image_resize_class {
// Return actions // Return actions
return [ return [
new app.Actions.Update_layer_action(layer.id, { new app.Actions.Update_layer_action(layer.id, {
x: new_x, x: new_x,
y: new_y, y: new_y,
data, data,
width: layer.width * xratio, width: layer.width * xratio,
@@ -191,27 +339,27 @@ class Image_resize_class {
}) })
]; ];
} }
//is vector //is vector
else if (layer.is_vector == true && layer.width != null && layer.height != null) { else if (layer.is_vector == true && layer.width != null && layer.height != null) {
// Return actions // Return actions
return [ return [
new app.Actions.Update_layer_action(layer.id, { new app.Actions.Update_layer_action(layer.id, {
x: new_x, x: new_x,
y: new_y, y: new_y,
width: layer.width * xratio, width: layer.width * xratio,
height: layer.height * yratio height: layer.height * yratio
}) })
]; ];
} }
//only images supported at this point //only images supported at this point
else if (layer.type != 'image') { else if (layer.type != 'image') {
//error - no support //error - no support
alertify.error('Layer must be vector or image (convert it to raster).'); alertify.error('Layer must be vector or image (convert it to raster).');
throw new Error('Layer is not compatible with resize'); throw new Error('Layer is not compatible with resize');
} }
//get canvas from layer //get canvas from layer
var canvas = this.Base_layers.convert_layer_to_canvas(layer.id, true, false); var canvas = this.Base_layers.convert_layer_to_canvas(layer.id, true, false);
var ctx = canvas.getContext("2d"); var ctx = canvas.getContext("2d");
@@ -221,15 +369,15 @@ class Image_resize_class {
alertify.warning('Scaling up is not supported in Hermite, using Lanczos.'); alertify.warning('Scaling up is not supported in Hermite, using Lanczos.');
mode = "Lanczos"; mode = "Lanczos";
} }
//resize //resize
if (mode == "Lanczos") { if (mode == "Lanczos") {
//Pica resize with max quality //Pica resize with max quality
var tmp_data = document.createElement("canvas"); var tmp_data = document.createElement("canvas");
tmp_data.width = width; tmp_data.width = width;
tmp_data.height = height; tmp_data.height = height;
await this.pica.resize(canvas, tmp_data, { await this.pica.resize(canvas, tmp_data, {
alpha: true, alpha: true,
}) })
@@ -250,11 +398,11 @@ class Image_resize_class {
tmp_data.width = canvas.width; tmp_data.width = canvas.width;
tmp_data.height = canvas.height; tmp_data.height = canvas.height;
tmp_data.getContext("2d").drawImage(canvas, 0, 0); tmp_data.getContext("2d").drawImage(canvas, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = width; canvas.width = width;
canvas.height = height; canvas.height = height;
ctx.drawImage(tmp_data, 0, 0, width, height); ctx.drawImage(tmp_data, 0, 0, width, height);
} }
@@ -268,7 +416,7 @@ class Image_resize_class {
return [ return [
new app.Actions.Update_layer_image_action(canvas, layer.id), new app.Actions.Update_layer_image_action(canvas, layer.id),
new app.Actions.Update_layer_action(layer.id, { new app.Actions.Update_layer_action(layer.id, {
x: new_x, x: new_x,
y: new_y, y: new_y,
width: canvas.width, width: canvas.width,
height: canvas.height, height: canvas.height,
@@ -277,9 +425,9 @@ class Image_resize_class {
}) })
]; ];
} }
resize_gui(params) { resize_gui(params) {
var units = this.Tools_settings.get_setting('default_units'); var units = params.units || this.Tools_settings.get_setting('default_units');
var resolution = this.Tools_settings.get_setting('resolution'); var resolution = this.Tools_settings.get_setting('resolution');
var width = parseFloat(params.width); var width = parseFloat(params.width);
@@ -326,4 +474,4 @@ class Image_resize_class {
} }
export default Image_resize_class; export default Image_resize_class;
+5
View File
@@ -82,6 +82,11 @@ class Image_size_class {
this._lastUnits = units; this._lastUnits = units;
var resolution = this.Tools_settings.get_setting('resolution'); var resolution = this.Tools_settings.get_setting('resolution');
// Persist so Resize and other dialogs open with the same units
const unitShort = {pixels: 'px', inches: '"', centimeters: 'cm', millimetres: 'mm'};
this.Tools_settings.save_setting('default_units', units);
this.Tools_settings.save_setting('default_units_short', unitShort[units] || units);
var newWidth = this.Helper.get_user_unit(config.WIDTH, units, resolution); var newWidth = this.Helper.get_user_unit(config.WIDTH, units, resolution);
var newHeight = this.Helper.get_user_unit(config.HEIGHT, units, resolution); var newHeight = this.Helper.get_user_unit(config.HEIGHT, units, resolution);