Add inches, crop-to-fill, and persistent units to Resize dialog
Resize dialog: - Units selector (pixels / inches) at the top; switching updates the Width/Height placeholders and clears any partially entered values - New "Crop to fill" checkbox: when both Width and Height are given, scales the image with cover-fit (fills target without letterboxing) then center-crops — subject looks the same at 5x7, 8x10, 11x14 - resize_layer/resize_gui now honour params.units instead of only reading the global default_units setting Global units persistence: - Switching units in either Resize or Canvas Size saves the choice to default_units so both dialogs open with the same unit next time https://claude.ai/code/session_017wupXfpjuoSdAqVyak4fJf
This commit is contained in:
@@ -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,6 +85,40 @@ 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
|
||||||
|
this.Tools_settings.save_setting('default_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)) {
|
||||||
@@ -85,6 +126,17 @@ class Image_resize_class {
|
|||||||
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 = [];
|
||||||
|
|
||||||
@@ -112,6 +164,100 @@ 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.
|
||||||
*
|
*
|
||||||
@@ -120,7 +266,7 @@ class Image_resize_class {
|
|||||||
* @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);
|
||||||
@@ -279,7 +425,7 @@ 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);
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ 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
|
||||||
|
this.Tools_settings.save_setting('default_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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user