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 ******************************************************************************/
009
010package imagingbook.common.image;
011
012import ij.ImageStack;
013import ij.process.ColorProcessor;
014import ij.process.FloatProcessor;
015import imagingbook.common.color.colorspace.sRGB65ColorSpace;
016
017import java.awt.color.ColorSpace;
018
019/**
020 * This class defines a "color stack" as a subtype of {@link PixelPack} with exactly 3 components (slices), representing
021 * a color image in a specific color space (default is {@link sRGB65ColorSpace}). It allows simple conversion to other
022 * color spaces (see {@link #convertFromSrgbTo(ColorSpace)}). All conversions are 'destructive', i.e., the affected
023 * color stack is modified. Pixel values are typically in [0,1], depending on the associated color space. A
024 * {@link ColorPack} may be created from an existing {@link ColorProcessor} whose pixels are assumed to be in sRGB color
025 * space (see {@link #ColorPack(ColorProcessor)}). To be converted back to a {@link ColorProcessor}, the
026 * {@link ColorPack} must be in sRGB color space (see {@link #convertToSrgb()}).
027 *
028 * @author WB
029 * @version 2022/09/10
030 */
031public class ColorPack extends PixelPack {
032
033        // the current color space of this color stack
034        private ColorSpace colorspace = null;
035
036        // ---------------------------------------------------------------------
037
038        /**
039         * Constructor.
040         * @param cp a color image assumed to be in sRGB
041         */
042        public ColorPack(ColorProcessor cp) {
043                super(cp, 1.0/255, null);
044                setColorSpace(sRGB65ColorSpace.getInstance());
045        }
046
047        // -----------------------------------------------------------------
048
049        /**
050         * Returns the 3 color components as an array of {@link FloatProcessor}.
051         * @return a {@link FloatProcessor} array
052         */
053        public FloatProcessor[] getProcessors() {
054                FloatProcessor[] processors = new FloatProcessor[3];
055                for (int k = 0; k < 3; k++) {
056                        processors[k] = new FloatProcessor(width, height, data[k]);
057                }
058                return processors;
059        }
060
061        /**
062         * Returns the current color space instance of this {@link ColorPack}.
063         * @return the current color space
064         */
065        public ColorSpace getColorspace() {
066                return this.colorspace;
067        }
068
069        private void setColorSpace(ColorSpace colorspace) {
070                this.colorspace = colorspace;
071        }
072
073        // -----------------------------------------------------------------
074
075        /**
076         * Converts the pixel values of this {@link ColorPack} to the specified color space. The color stack must be in sRGB
077         * space. An exceptions is thrown if the color stack is not in sRGB color space. Has no effect if the target color
078         * space already is sRGB.
079         *
080         * @param targetColorspace the new color space
081         */
082        public void convertFromSrgbTo(ColorSpace targetColorspace) {
083                if (!(colorspace instanceof sRGB65ColorSpace)) {
084                        throw new IllegalStateException("color stack must be in sRGB");
085                }
086
087                if (targetColorspace instanceof sRGB65ColorSpace) {
088                        return; // no effect
089                        // throw new IllegalArgumentException("cannot convert color stack from sRGB to sRGB");
090                }
091
092                final float[] srgb = new float[3];
093
094                for (int i = 0; i < length; i++) {
095                        getPix(i, srgb);
096                        clipTo01(srgb);
097                        float[] c = targetColorspace.fromRGB(srgb);
098                        setPix(i, c);
099                }
100
101                setColorSpace(targetColorspace);
102        }
103
104        // -----------------------------------------------------------------
105
106        /**
107         * Converts this {@link ColorPack} to sRGB space. Has no effect if the color stack is in sRGB color space already.
108         */
109        public void convertToSrgb() {
110                if (colorspace instanceof sRGB65ColorSpace) {
111                        return;
112                        // throw new IllegalStateException("color stack is in sRGB already");
113                }
114
115                final float[] c = new float[3];
116
117                for (int i = 0; i < length; i++) {
118                        getPix(i, c);
119                        float[] srgb = colorspace.toRGB(c);
120                        setPix(i, srgb);
121                }
122
123                setColorSpace(sRGB65ColorSpace.getInstance());
124        }
125
126        // ---------------------------------------------------------------
127
128        @Override
129        public ColorProcessor toColorProcessor() {
130                if (!(colorspace instanceof sRGB65ColorSpace)) {
131                        throw new IllegalStateException("color stack must be in sRGB to convert to ColorProcessor");
132                }
133                return super.toColorProcessor(255);
134        }
135
136        @Override       // arbitrary scaling is inhibited
137        public ColorProcessor toColorProcessor(double scale) {
138                throw new UnsupportedOperationException();
139        }
140
141        @Override
142        public ImageStack toImageStack() {
143                ImageStack stack = super.toImageStack();
144                // set slice labels to color component names:
145                for (int k = 0; k < depth; k++) {
146                        stack.setSliceLabel(colorspace.getName(k), k + 1);
147                }
148                return stack;
149        }
150
151        // ------------------------------------------------------------------
152
153        private float clipTo01(float val) {
154                if (val < 0) return 0f;
155                if (val > 1) return 1f;
156                return val;
157        }
158
159        private void clipTo01(float[] vals) {
160                for (int i = 0; i < vals.length; i++) {
161                        vals[i] = clipTo01(vals[i]);
162                }
163        }
164
165}