001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package Ch03_Point_Operations;
010
011import ij.IJ;
012import ij.ImagePlus;
013import ij.ImageStack;
014import ij.gui.GenericDialog;
015import ij.plugin.filter.PlugInFilter;
016import ij.process.Blitter;
017import ij.process.ImageProcessor;
018import imagingbook.common.ij.DialogUtils;
019import imagingbook.core.jdoc.JavaDocHelp;
020import imagingbook.sampleimages.GeneralSampleImage;
021
022import java.util.Locale;
023
024import static imagingbook.common.ij.IjUtils.noCurrentImage;
025
026/**
027 * <p>
028 * This plugin demonstrates linear (alpha) blending between two images, which are supplied as a {@link ImageStack} with
029 * exactly 2 frames. The first stack frame is taken as the <em>foreground</em> image, the second as the
030 * <em>background</em> image. Running the plugin inserts N &gt; 0 (N is specified by the user) additional frames
031 * obtained by linearly blending the two input images with varying &alpha; values. See Sec. 3.8.5 (Prog. 3.5) of [1] for
032 * additional details. The input stack is modified.
033 * </p>
034 * <p>
035 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
036 * (2022).
037 * </p>
038 *
039 * @author WB
040 * @version 2022/12/07
041 */
042public class Linear_Blending_Stack implements PlugInFilter, JavaDocHelp {
043
044        private static int StepCount = 5;       // number of intermediate images
045        private ImagePlus im;
046
047        /** Constructor, asks to open a predefined sample image if no other image is currently open. */
048        public Linear_Blending_Stack() {
049                if (noCurrentImage()) {
050                        DialogUtils.askForSampleImage(GeneralSampleImage.ShipBeachSmallStack);
051                }
052        }
053
054        @Override
055        public int setup(String arg, ImagePlus im) {
056                this.im = im;
057                return DOES_8G + STACK_REQUIRED;
058        }       
059        
060        @Override
061        public void run(ImageProcessor ip) {
062                ImageStack stack = im.getStack();
063                if (stack.getSize() != 2) {
064                        IJ.error("Plugin requires stack with exactly 2 slices!");
065                        return;
066                }
067
068                if(!runDialog() || StepCount < 1) {
069                        return;
070                }
071
072                ImageProcessor ip1 = stack.getProcessor(1);
073                ImageProcessor ip2 = stack.getProcessor(2);
074
075                stack.setSliceLabel(makeSliceLabel(0.0), 1);
076                stack.setSliceLabel(makeSliceLabel(1.0), 2);
077
078                for (int i = 1; i <= StepCount; i++) {
079                        double alpha = (double) i / (StepCount + 1);
080                        ImageProcessor ipA = ip1.duplicate();
081                        ImageProcessor ipB = ip2.duplicate();
082                        ipA.multiply(1 - alpha);
083                        ipB.multiply(alpha);
084                        ipA.copyBits(ipB, 0, 0, Blitter.ADD);
085                        stack.addSlice(makeSliceLabel(alpha), ipA, i);
086                }
087
088                im.setStack(stack);
089        }
090
091        private String makeSliceLabel(double alpha) {
092                return String.format(Locale.US, "\u03B1 = %.2f", alpha);
093        }
094
095        private boolean runDialog() {
096                GenericDialog gd = new GenericDialog(this.getClass().getSimpleName());
097                gd.addHelp(getJavaDocUrl());
098                gd.addNumericField("Step count (N > 0)", StepCount, 0);
099
100                gd.showDialog();
101                if (gd.wasCanceled()) {
102                        return false;
103                }
104
105                StepCount = (int) gd.getNextNumber();
106                return true;
107        }
108}