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 imagingbook.common.image;
010
011import ij.process.ImageProcessor;
012import imagingbook.common.geometry.basic.Pnt2d;
013import imagingbook.common.geometry.basic.Pnt2d.PntInt;
014import imagingbook.common.geometry.mappings.Mapping2D;
015import imagingbook.common.image.access.ImageAccessor;
016import imagingbook.common.image.interpolation.InterpolationMethod;
017
018/**
019 * This class defines methods to perform arbitrary geometric transformations on images. The geometric transformation
020 * (mapping) must be specified at construction. The specified geometric mapping is supposed to be INVERTED, i.e.
021 * transforming
022 * <strong>target to source</strong> coordinates!
023 * For reading pixel values (from the source image) the out-of-bounds strategy and pixel interpolation method can be
024 * specified. All methods work for both scalar-valued and color images.
025 *
026 * @author WB
027 * @version 2022/09/16 revised
028 * @see ImageAccessor
029 * @see Mapping2D
030 */
031public class ImageMapper {
032        
033        /** Default out-of-bounds strategy (see {@link OutOfBoundsStrategy}). */
034        public static OutOfBoundsStrategy DefaultOutOfBoundsStrategy = OutOfBoundsStrategy.NearestBorder;       
035        
036        /** Default pixel interpolation method (see {@link InterpolationMethod}). */
037        public static InterpolationMethod DefaultInterpolationMethod = InterpolationMethod.Bicubic;     
038        
039        private final OutOfBoundsStrategy obs;
040        private final InterpolationMethod ipm;
041        private final Mapping2D mapping;
042
043        /**
044         * Constructor - creates a new {@link ImageMapper} with the specified geometric mapping. The default pixel
045         * interpolation method (see {@link #DefaultInterpolationMethod}) and out-of-bounds strategy (see
046         * {@link #DefaultOutOfBoundsStrategy}) are used.
047         *
048         * @param targetToSourceMapping the geometric (target to source) transformation
049         */
050        public ImageMapper(Mapping2D targetToSourceMapping) {
051                this(targetToSourceMapping, DefaultOutOfBoundsStrategy, DefaultInterpolationMethod);
052        }
053
054        /**
055         * Constructor - creates a new {@link ImageMapper} with the specified geometric mapping, out-of-bounds strategy and
056         * pixel interpolation method.
057         *
058         * @param targetToSourceMapping the geometric (target to source) transformation
059         * @param obs the out-of-bounds strategy (affects source image only)
060         * @param ipm the pixel interpolation method
061         */
062        public ImageMapper(Mapping2D targetToSourceMapping, OutOfBoundsStrategy obs, InterpolationMethod ipm) {
063                this.mapping = targetToSourceMapping;
064                this.obs = obs;
065                this.ipm = ipm;
066        }
067        
068        // ---------------------------------------------------------------------
069
070        /**
071         * Destructively transforms the passed image using this geometric mapping and the specified pixel interpolation
072         * method.
073         *
074         * @param ip target image to which this mapping is applied
075         */
076        public void map(ImageProcessor ip) {
077                // make a temporary copy of the image:
078                ImageProcessor source = ip.duplicate();
079                ImageProcessor target = ip;
080                map(source, target);
081                source = null;
082        }
083        
084        // ---------------------------------------------------------------------
085
086        /**
087         * Transforms the source image to the target image using this geometric mapping and the specified pixel
088         * interpolation method. Note that source and target must be different images! The geometric mapping is supposed to
089         * be INVERTED, i.e. transforming target to source image coordinates!
090         *
091         * @param source input image (not modified)
092         * @param target output image (modified)
093         */
094        public void map(ImageProcessor source, ImageProcessor target) {
095                if (target == source) {
096                        throw new IllegalArgumentException("source and target image must not be the same!");
097                }
098                ImageAccessor sourceAcc = ImageAccessor.create(source, obs, ipm);
099                ImageAccessor targetAcc = ImageAccessor.create(target);
100                map(sourceAcc, targetAcc);
101        }
102
103        // ---------------------------------------------------------------------
104
105        /**
106         * Transforms the source image to the target image using this geometric mapping and the specified pixel
107         * interpolation method. The two images are passed as instances of {@link ImageAccessor}. Note that source and
108         * target must be different images! The geometric mapping is supposed to be INVERTED, i.e. transforming target to
109         * source image coordinates. Access to source pixels is controlled by the out-of-bounds strategy and pixel
110         * interpolation method settings of the source {@link ImageAccessor} (the corresponding settings of this
111         * {@link ImageAccessor} are ignored).
112         *
113         * @param sourceAcc {@link ImageAccessor} for the source image
114         * @param targetAcc {@link ImageAccessor} for the target image
115         */
116        public void map(ImageAccessor sourceAcc, ImageAccessor targetAcc) {
117                if (targetAcc.getProcessor() == sourceAcc.getProcessor()) {
118                        throw new IllegalArgumentException("Source and target image must not be the same!");
119                }
120                ImageProcessor target = targetAcc.getProcessor();
121                final int w = target.getWidth();
122                final int h = target.getHeight();
123                for (int v = 0; v < h; v++) {
124                        for (int u = 0; u < w; u++) {
125                                Pnt2d sourcePt = this.mapping.applyTo(PntInt.from(u, v));
126                                float[] val = sourceAcc.getPix(sourcePt.getX(), sourcePt.getY());
127                                targetAcc.setPix(u, v, val);
128                        }
129                }
130        }
131
132}