[English below]
Cucú!
Normalmente, cuando estamos ajustando los parámetros de un proceso, trabajamos con un preview. Esto mola porque podemos tocar y probar los parámetros rápidamente sin tener que esperar a que el proceso trabaje sobre la imagen entera. Sin embargo, este modo de trabajo tiene un pequeño inconveniente: al concentrarnos en un preview y basar los parámetros en los resultados que vamos obteniendo con él, es más que posible que otros previews definidos pasen a mostrar un comportamiento subóptimo sin que nos demos cuenta. Y cuando finalmente lo hacemos, nos vemos en la tesitura de tener que buscar unos parámetros que funcionen decentemente bien para todos los previews, y probar el proceso alternativamente en todos ellos.
He probado a jugar con un ImageContainer para aliviar este problema y, si bien se puede aplicar el proceso de una sola vez a todos los previews que queramos, me encontré con dos pegas:
- No podemos ver todos los previews al mismo tiempo, y tenemos que alternar entre ellos para ver el resultado.
- El botón "Undo preview / Redo preview" se queda inactivo, no se pueden ver los cambios que el proceso ha hecho sobre los previews.
Esto provoca que las ventajas de usar ImageContainer sean muy pocas, y probablemente el inconveniente de no poder usar "Undo/Redo" las supera.
He hecho, por tanto, un scriptcillo que crea una imagen nueva conteniendo todos los previews que queramos. Así, los tenemos todos en la pantalla al mismo tiempo y podemos trabajar con ellos de una sola vez. El script también genera un preview conteniendo toda esta imagen nueva, ya que de todas formas sería lo primero que el usuario haría con ella.
Espero que os guste y saquéis buen provecho de él!
--
Hiya!
Usually, when we'are adjusting the parameters of some process, we work on a preview. This is nice because we can quickly touch and test the parameters without having to wait for the process to work on the entire image. However, this methodology has a little drawback: when we concentrate on a preview and base the parameters on the results we obtain with it, it's more than probable that the result in other defined previews becomes suboptimal without us noticing. And when we finally do, we're forced to look for a set of parameters that work fine for all previews, and alternately test the process on them all.
I tried to play with an ImageContainer to alleviate this problem and, while you can apply the process to all the previews you want at once, I've found two snags:
- We can't see all the previews at the same time, and we're forced to alternate among all of them to see the result.
- The button "Undo preview / Redo preview" becomes inactive, you can't see the changes that the process has done to the previews.
This causes that the advantage of using ImageContainer is very small, and probably surpassed by the drawback of not being able to use "Undo/Redo".
Therefore, I've done a little script that creates a new image containing all the previews we want. Thus, we have them all on the screen at the same time and we can work with them at once. The script also generates a preview containing this whole new image, since it would be the first thing the user would do on it anyway.
I hope you like it and get a lot of it!
--
Aquí va la versión 0.2.1. Here goes version 0.2.1.
/*
PreviewAggregator.js v0.2.1 - Joins previews into a new image.
Copyright (C) 2009 David Serrano.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Changelog:
0.2.1: Bugfix: working without a mask was broken.
0.2: If all selected previews belong to same imagen, take into account
the mask that the image may have, as per Oriol suggestion.
0.1: Initial version.
*/
#include <pjsr/Sizer.jsh>
#include <pjsr/FrameStyle.jsh>
#include <pjsr/TextAlign.jsh>
#include <pjsr/StdButton.jsh>
#include <pjsr/StdIcon.jsh>
#include <pjsr/ColorSpace.jsh>
#include <pjsr/SampleType.jsh>
#include <pjsr/UndoFlag.jsh>
#feature-id PreviewAggregator
#feature-info When using previews to fine-tune the parameters of a process, \
it's often desired to pay attention two more than a preview at the same \
time, so the adjustments done to the parameters in order to improve the \
result in one area of the image don't degrade other areas. This script \
creates an image containing other images' previews, so processes can be \
applied to it and the user can see the results in all original previews \
at once.
// #feature-icon KSIntegration.xpm
#define VERSION "0.2.1"
function pa_engine() {
this.imgIDs = new Array;
this.views = new Array;
this.mask_views = new Array;
this.anchors = new Array;
this.new_image_width = 0;
this.new_image_height = 0;
// Populates the views array.
this.load = function() {
var N = this.imgIDs.length;
this.views.length = 0;
for (var i = 0; i < N; i++) {
this.views.push (new View (this.imgIDs[i]));
}
};
// calculates new image dimensions
this.imagesize = function () {
var x = Math.ceil (Math.sqrt (this.views.length));
var y = Math.ceil (this.views.length / x);
var max_w = 0;
var max_h = 0;
for (var i = 0; i < this.views.length; i++) {
var w = this.views[i].image.width;
var h = this.views[i].image.height;
if (w > max_w) { max_w = w; }
if (h > max_h) { max_h = h; }
}
this.new_image_width = x * max_w;
this.new_image_height = y * max_h;
//console.writeln ("x (",x,") y (",y,") max_w (",max_w,") max_h (",max_h,") width (",this.new_image_width,") height (",this.new_image_height,")");
// now build array of anchors using x, y, max_w and max_h
var anchor_x = -1;
var anchor_y = 0;
for (var i = 0; i < this.views.length; i++) {
anchor_x++;
if (anchor_x >= x) { // technically '==' works, but let's cover us from any unexpected event
anchor_x = 0;
anchor_y++;
}
//console.writeln ("about to push point at x (",anchor_x*max_w,") y (",anchor_y*max_h,") for view (",i,")");
this.anchors.push (new Point (anchor_x * max_w, anchor_y * max_h));
}
}
this.parent_images = function() {
var parent_images = {};
for (var i = 0; i < this.imgIDs.length; i++) {
var end_idx = this.imgIDs[i].indexOf ("->");
var img_id = this.imgIDs[i].substr (0, end_idx);
parent_images[img_id] = 1;
}
// this can probably be done by some sort of parent_images.length,
// parent_images.keys or something like that, but didn't put much
// effort in figuring out exactly how. Sorry for this, but...
var n = 0;
for (k in parent_images) { n++; }
return n;
}
this.create_image = function() {
var do_mask = 0;
var orig_mask_win;
if (this.views[0].window.mask.mainView.fullId) { console.writeln ("mask true (",this.views[0].window.mask.mainView.fullId,")"); }
if (
1 == this.parent_images() &&
this.views[0].window.mask.mainView.fullId
) {
do_mask = 1;
orig_mask_win = this.views[0].window.mask;
}
this.imagesize();
var window = new ImageWindow (
this.new_image_width,
this.new_image_height,
this.views[0].image.numberOfChannels,
this.views[0].image.bitsPerSample,
this.views[0].image.sampleType != SampleType_Integer,
this.views[0].image.colorSpace != ColorSpace_Gray,
"Aggregated"
);
var mask_window;
var mask_view;
if (do_mask) {
//console.writeln ("should work with a mask");
// replicate previews in the mask
for (var i = 0; i < this.views.length; i++) {
var r = this.views[i].window.previewRect (this.views[i]);
//console.writeln ("going to replicate preview (",i,") x (",r.x0,") y (",r.y0,") w (",r.width,") h (",r.height,")");
this.mask_views[i] = orig_mask_win.createPreview (r, this.views[i].id);
}
// create another identical image for the mask
new_mask_win = new ImageWindow (
this.new_image_width,
this.new_image_height,
this.views[0].image.numberOfChannels,
this.views[0].image.bitsPerSample,
this.views[0].image.sampleType != SampleType_Integer,
this.views[0].image.colorSpace != ColorSpace_Gray,
"Aggregated_mask"
);
mask_view = new_mask_win.mainView;
mask_view.beginProcess (UndoFlag_NoSwapFile);
}
var view = window.mainView;
view.beginProcess (UndoFlag_NoSwapFile);
view.image.fill (0);
if (do_mask) { mask_view.image.fill (0); }
// paste previews in their proper place
for (var i = 0; i < this.views.length; i++) {
//console.writeln ("applying image (",i,")");
view.image.selectedPoint = this.anchors[i];
view.image.apply (this.views[i].image);
if (do_mask) {
mask_view.image.selectedPoint = this.anchors[i];
mask_view.image.apply (this.mask_views[i].image);
}
}
view.endProcess();
if (do_mask) {
mask_view.endProcess();
// delete created previews
for (var i = 0; i < this.mask_views.length; i++) {
orig_mask_win.deletePreview (this.mask_views[i]);
}
// assign mask
window.mask = new_mask_win;
}
// usually users will create a preview on the new image. Anticipate it
window.createPreview (0, 0, this.new_image_width, this.new_image_height);
// TODO: select the new preview
window.show();
if (do_mask) { new_mask_win.show(); }
}
}
var engine = new pa_engine();
function pa_dialog() {
this.__base__ = Dialog;
this.__base__();
// ----- HELP LABEL
this.helpLabel = new Label (this);
this.helpLabel.frameStyle = FrameStyle_Box;
this.helpLabel.margin = 4;
this.helpLabel.wordWrapping = true;
this.helpLabel.useRichText = true;
this.helpLabel.text = "<b>PreviewAggregator v"+VERSION+"</b> - A script to create " +
"a new image based on the contents of other images' previews. " +
"Select the previews to be aggregated and click 'Ok'.";
// ----- LIST OF TARGETS
this.target_List = new TreeBox (this);
this.target_List.setMinSize (400, 200);
this.target_List.font = new Font ("monospace", 10); // best to show tabulated data
this.target_List.numberOfColumns = 2;
this.target_List.headerVisible = true;
this.target_List.headerSorting = true;
this.target_List.setHeaderText (0, "Views");
this.target_List.setHeaderText (1, "Dimensions");
this.target_List.setHeaderAlignment (0, Align_Left);
this.target_List.setHeaderAlignment (1, Align_Left);
var active_id = ImageWindow.activeWindow.mainView.fullId;
// Node creation helper
this.addViewNode = function (parent, view) {
var node = new TreeBoxNode (parent);
node.checkable = true;
var id = view.fullId;
// automatically select all previews in the active window
var arrow = id.indexOf ("->");
var parent_id = id.substring (0, arrow);
if (active_id == parent_id) {
node.checked = true;
} else {
node.checked = false;
}
node.setText (0, id);
var image = view.image;
var metadata = format ("%5d x %5d x %d", image.width, image.height, image.numberOfChannels);
node.setText (1, metadata);
return node;
}
// build the view tree structure
var windows = ImageWindow.windows;
for (var i = 0; i < windows.length; ++i) {
var previews = windows[i].previews;
for (var j = 0; j < previews.length; ++j) {
this.addViewNode (this.target_List, previews[j]);
}
}
this.target_List.sort();
// Ensure that all columns are initially visible
this.target_List.adjustColumnWidthToContents (0);
this.target_List.adjustColumnWidthToContents (1);
this.target_GroupBox = new GroupBox (this);
this.target_GroupBox.title = "Previews to aggregate";
this.target_GroupBox.sizer = new VerticalSizer;
this.target_GroupBox.sizer.margin = 4;
this.target_GroupBox.sizer.spacing = 4;
this.target_GroupBox.sizer.add (this.target_List, 100);
// ----- BUTTONS
this.ok_Button = new PushButton (this);
this.ok_Button.text = " OK ";
// transfer the names of the selected images to the engine
this.ok_Button.onClick = function() {
for (n = 0; n < this.dialog.target_List.numberOfChildren; n++)
if (this.dialog.target_List.child (n).checked)
engine.imgIDs.push (this.dialog.target_List.child (n).text (0));
this.dialog.ok();
};
this.cancel_Button = new PushButton (this);
this.cancel_Button.text = " Cancel ";
this.cancel_Button.onClick = function() {
this.dialog.cancel();
};
this.buttons_Sizer = new HorizontalSizer;
this.buttons_Sizer.spacing = 4;
this.buttons_Sizer.addStretch();
this.buttons_Sizer.add (this.ok_Button);
this.buttons_Sizer.add (this.cancel_Button);
// ----- PACK EVERYTHING
this.sizer = new VerticalSizer;
this.sizer.margin = 6;
this.sizer.spacing = 6;
this.sizer.add (this.helpLabel);
this.sizer.addSpacing (4);
this.sizer.add (this.target_GroupBox, 100);
this.sizer.add (this.buttons_Sizer);
this.windowTitle = "PreviewAggregator v" + VERSION;
this.adjustToContents();
}
pa_dialog.prototype = new Dialog;
function main() {
var dialog = new pa_dialog();
for (;;) {
if (!dialog.execute()) {
break;
}
engine.load();
engine.create_image();
// Quit after successful execution.
break;
}
}
main();