Add inches unit, print size presets, and resize-crop to Canvas Size dialog

- Units selector (pixels/inches) directly in the dialog; switching live-converts
  the width/height fields so you can type e.g. 5 / 7 without mental math
- Six print-size presets at 300 DPI added to the Resolution dropdown:
  5x7, 8x10, and 11x14 in both Portrait and Landscape orientations
- New "Resize & crop image" checkbox: when enabled, image layers are scaled
  with cover-fit (fills the target canvas, no letterboxing) and center-cropped
  so the subject stays proportionally the same across all three print sizes

https://claude.ai/code/session_017wupXfpjuoSdAqVyak4fJf
This commit is contained in:
Claude
2026-06-12 01:13:13 +00:00
parent 045bd0806f
commit 35932743fc
+142 -48
View File
@@ -1,47 +1,73 @@
import app from './../../app.js'; import app from './../../app.js';
import config from './../../config.js'; import config from './../../config.js';
import Base_gui_class from './../../core/base-gui.js'; import Base_gui_class from './../../core/base-gui.js';
import Base_layers_class from './../../core/base-layers.js';
import Dialog_class from './../../libs/popup.js'; import Dialog_class from './../../libs/popup.js';
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js'; import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
import Tools_settings_class from './../tools/settings.js'; import Tools_settings_class from './../tools/settings.js';
import Helper_class from './../../libs/helpers.js'; import Helper_class from './../../libs/helpers.js';
import Pica from './../../../../node_modules/pica/dist/pica.js';
// Common print sizes at 300 DPI: [width_px, height_px, display_label]
const PRINT_SIZES = [
[1500, 2100, '5x7" Portrait'],
[2100, 1500, '5x7" Landscape'],
[2400, 3000, '8x10" Portrait'],
[3000, 2400, '8x10" Landscape'],
[3300, 4200, '11x14" Portrait'],
[4200, 3300, '11x14" Landscape'],
];
class Image_size_class { class Image_size_class {
constructor() { constructor() {
this.Base_gui = new Base_gui_class(); this.Base_gui = new Base_gui_class();
this.Base_layers = new Base_layers_class();
this.POP = new Dialog_class(); this.POP = new Dialog_class();
this.Tools_settings = new Tools_settings_class(); this.Tools_settings = new Tools_settings_class();
this.Helper = new Helper_class(); this.Helper = new Helper_class();
this.pica = Pica();
this._lastUnits = 'pixels';
} }
size() { size() {
var _this = this; var _this = this;
var common_dimensions = this.Base_gui.common_dimensions; var common_dimensions = this.Base_gui.common_dimensions;
var units = this.Tools_settings.get_setting('default_units'); var global_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 enable_autoresize = this.Tools_settings.get_setting('enable_autoresize'); var enable_autoresize = this.Tools_settings.get_setting('enable_autoresize');
var displayUnits = (global_units === 'inches') ? 'inches' : 'pixels';
this._lastUnits = displayUnits;
var resolutions = ['Custom']; var resolutions = ['Custom'];
for (var i in common_dimensions) { for (var i in common_dimensions) {
var value = common_dimensions[i]; var value = common_dimensions[i];
resolutions.push(value[0] + 'x' + value[1] + ' - ' + value[2]); resolutions.push(value[0] + 'x' + value[1] + ' - ' + value[2]);
} }
// Print size presets — WxH format is parsed by existing resolution logic
for (var ps of PRINT_SIZES) {
resolutions.push(ps[0] + 'x' + ps[1] + ' - ' + ps[2] + ' Print');
}
//convert units var width = this.Helper.get_user_unit(config.WIDTH, displayUnits, resolution);
var width = this.Helper.get_user_unit(config.WIDTH, units, resolution); var height = this.Helper.get_user_unit(config.HEIGHT, displayUnits, resolution);
var height = this.Helper.get_user_unit(config.HEIGHT, units, resolution);
var settings = { var settings = {
title: 'Canvas Size', title: 'Canvas Size',
params: [ params: [
{name: "w", title: "Width:", value: width, placeholder: width, comment: units}, {name: "units", title: "Units:", value: displayUnits, values: ["pixels", "inches"]},
{name: "h", title: "Height:", value: height, placeholder: height, comment: units}, {name: "w", title: "Width:", value: width, placeholder: width, comment: displayUnits},
{name: "h", title: "Height:", value: height, placeholder: height, comment: displayUnits},
{name: "resolution", title: "Resolution:", values: resolutions}, {name: "resolution", title: "Resolution:", values: resolutions},
{name: "layout", title: "Layout:", value: "Custom", values: ["Custom", "Landscape", "Portrait"]}, {name: "layout", title: "Layout:", value: "Custom", values: ["Custom", "Landscape", "Portrait"]},
{name: "enable_autoresize", title: "Enable autoresize:", value: enable_autoresize}, {name: "enable_autoresize", title: "Enable autoresize:", value: enable_autoresize},
{name: "in_proportion", title: "In proportion:", value: false}, {name: "in_proportion", title: "In proportion:", value: false},
{name: "resize_image", title: "Resize & crop image:", value: false},
], ],
on_change: function(params) {
_this.units_change_handler(params);
},
on_finish: function (params) { on_finish: function (params) {
_this.size_handler(params); _this.size_handler(params);
}, },
@@ -49,92 +75,160 @@ class Image_size_class {
this.POP.show(settings); this.POP.show(settings);
} }
size_handler(data) { units_change_handler(params) {
var width = parseFloat(data.w); var units = params.units;
var height = parseFloat(data.h); if (units === this._lastUnits) return;
var ratio = config.WIDTH / config.HEIGHT;
var units = this.Tools_settings.get_setting('default_units'); this._lastUnits = units;
var resolution = this.Tools_settings.get_setting('resolution'); var resolution = this.Tools_settings.get_setting('resolution');
if (width < 0){ var newWidth = this.Helper.get_user_unit(config.WIDTH, units, resolution);
width = 1; var newHeight = this.Helper.get_user_unit(config.HEIGHT, units, resolution);
}
if (height < 0){ var wInput = document.getElementById('pop_data_w');
height = 1; var hInput = document.getElementById('pop_data_h');
} if (wInput) wInput.value = newWidth;
if (hInput) hInput.value = newHeight;
// Update the unit label shown next to each field
var wComment = wInput ? wInput.nextElementSibling : null;
var hComment = hInput ? hInput.nextElementSibling : null;
if (wComment && wComment.classList.contains('field_comment')) wComment.textContent = units;
if (hComment && hComment.classList.contains('field_comment')) hComment.textContent = units;
}
async size_handler(data) {
var width = parseFloat(data.w);
var height = parseFloat(data.h);
var canvasRatio = config.WIDTH / config.HEIGHT;
var units = data.units || this.Tools_settings.get_setting('default_units');
var resolution = this.Tools_settings.get_setting('resolution');
if (width < 0) width = 1;
if (height < 0) height = 1;
this.Tools_settings.save_setting('enable_autoresize', data.enable_autoresize); this.Tools_settings.save_setting('enable_autoresize', data.enable_autoresize);
//aspect ratio if (isNaN(width) && isNaN(height)) {
if (isNaN(width) && isNaN(height)){
alertify.error('Wrong dimensions'); alertify.error('Wrong dimensions');
return; return;
} }
if (isNaN(width)){ if (isNaN(width)) width = height * canvasRatio;
width = height * ratio; if (isNaN(height)) height = width / canvasRatio;
}
if (isNaN(height)){
height = width / ratio;
}
if (data.resolution != 'Custom') { if (data.resolution != 'Custom') {
var dim = data.resolution.split(" "); var dim = data.resolution.split(" ");
dim = dim[0].split("x"); dim = dim[0].split("x");
width = parseInt(dim[0]); width = parseInt(dim[0]);
height = parseInt(dim[1]); height = parseInt(dim[1]);
if(data.layout == 'Portrait'){ // Don't apply layout swap for print presets (orientation is already encoded)
if (data.layout == 'Portrait' && !data.resolution.includes('Print')) {
var tmp = width; var tmp = width;
width = height; width = height;
height = tmp; height = tmp;
} }
} } else {
else{
//convert units
width = this.Helper.get_internal_unit(width, units, resolution); width = this.Helper.get_internal_unit(width, units, resolution);
height = this.Helper.get_internal_unit(height, units, resolution); height = this.Helper.get_internal_unit(height, units, resolution);
} }
width = parseInt(width);
height = parseInt(height);
var actions = [ var actions = [
new app.Actions.Prepare_canvas_action('undo'), new app.Actions.Prepare_canvas_action('undo'),
new app.Actions.Update_config_action({ new app.Actions.Update_config_action({
WIDTH: parseInt(width), WIDTH: width,
HEIGHT: parseInt(height) HEIGHT: height
}), }),
]; ];
if(data.in_proportion == true) { // Proportional layer repositioning (only when not doing full resize+crop)
//resize object and change coordinates if (data.in_proportion == true && data.resize_image != true) {
var width_ratio = config.WIDTH / width; var width_ratio = config.WIDTH / width;
var height_ratio = config.HEIGHT / height; var height_ratio = config.HEIGHT / height;
var ratio = Math.max(width_ratio, height_ratio); var maxRatio = Math.max(width_ratio, height_ratio);
for (var i in config.layers) { for (var i in config.layers) {
var layer = config.layers[i]; var layer = config.layers[i];
if(layer.x != null && layer.y != null) { if (layer.x != null && layer.y != null) {
var data_new = { actions.push(new app.Actions.Update_layer_action(layer.id, {
x: Math.round(layer.x / width_ratio), x: Math.round(layer.x / width_ratio),
y: Math.round(layer.y / height_ratio), y: Math.round(layer.y / height_ratio),
}; }));
actions.push(new app.Actions.Update_layer_action(layer.id, data_new));
} }
if(layer.width != null && layer.height != null) { if (layer.width != null && layer.height != null) {
var data_new = { actions.push(new app.Actions.Update_layer_action(layer.id, {
width: Math.round(layer.width / ratio), width: Math.round(layer.width / maxRatio),
height: Math.round(layer.height / ratio), height: Math.round(layer.height / maxRatio),
}; }));
actions.push(new app.Actions.Update_layer_action(layer.id, data_new));
} }
} }
} }
// Resize & center-crop image layers to fill the new canvas
if (data.resize_image == true) {
try {
var cropActions = await this.get_resize_crop_actions(width, height);
actions = actions.concat(cropActions);
} catch (error) {
alertify.error('Could not resize image: ' + error.message);
}
}
actions.push(new app.Actions.Prepare_canvas_action('do')); actions.push(new app.Actions.Prepare_canvas_action('do'));
//execute
app.State.do_action( app.State.do_action(
new app.Actions.Bundle_action('set_image_size', 'Set Image Size', actions) new app.Actions.Bundle_action('set_image_size', 'Set Image Size', actions)
); );
} }
/**
* Generates actions to scale-and-center-crop all image layers to fill targetWidth × targetHeight.
* Uses "cover" scaling: the image is scaled so it fills the target, then cropped from the center.
*/
async get_resize_crop_actions(targetWidth, targetHeight) {
var actions = [];
var srcWidth = config.WIDTH;
var srcHeight = config.HEIGHT;
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);
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 tmp = document.createElement('canvas');
tmp.width = newLayerW;
tmp.height = newLayerH;
await this.pica.resize(canvas, tmp, {alpha: true});
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,
}));
}
return actions;
}
} }
export default Image_size_class; export default Image_size_class;