001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2013
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.image;
040
041import java.awt.color.ColorSpace;
042import java.awt.image.BufferedImage;
043import java.awt.image.ColorModel;
044import java.awt.image.DataBuffer;
045import java.awt.image.DataBufferByte;
046import java.awt.image.DataBufferInt;
047import java.awt.image.DataBufferUShort;
048import java.awt.image.DirectColorModel;
049import java.awt.image.Raster;
050import java.awt.image.SampleModel;
051import java.awt.image.WritableRaster;
052
053import org.dcm4che3.data.Tag;
054import org.dcm4che3.data.Attributes;
055
056/**
057 * @author Gunter Zeilinger <gunterze@gmail.com>
058 *
059 */
060public class PaletteColorModel extends ColorModel {
061
062    private static final int[] opaqueBits = {8, 8, 8};
063
064    private final LUT lut;
065
066    public PaletteColorModel(int bits, int dataType, ColorSpace cs,
067            Attributes ds) {
068        super(bits, opaqueBits, cs, false, false, OPAQUE, dataType);
069        int[] rDesc = lutDescriptor(ds,
070                Tag.RedPaletteColorLookupTableDescriptor);
071        int[] gDesc = lutDescriptor(ds,
072                Tag.GreenPaletteColorLookupTableDescriptor);
073        int[] bDesc = lutDescriptor(ds,
074                Tag.BluePaletteColorLookupTableDescriptor);
075        byte[] r = lutData(ds, rDesc,
076                Tag.RedPaletteColorLookupTableData,
077                Tag.SegmentedRedPaletteColorLookupTableData);
078        byte[] g = lutData(ds, gDesc,
079                Tag.GreenPaletteColorLookupTableData,
080                Tag.SegmentedGreenPaletteColorLookupTableData);
081        byte[] b = lutData(ds, bDesc, 
082                Tag.BluePaletteColorLookupTableData,
083                Tag.SegmentedBluePaletteColorLookupTableData);
084        lut = LUT.create(bits, r, g, b, rDesc[1], gDesc[1], bDesc[1]);
085    }
086
087    private int[] lutDescriptor(Attributes ds, int descTag) {
088        int[] desc = ds.getInts(descTag);
089        if (desc == null) {
090            throw new IllegalArgumentException("Missing LUT Descriptor!");
091        }
092        if (desc.length != 3) {
093            throw new IllegalArgumentException(
094                    "Illegal number of LUT Descriptor values: " + desc.length);
095        }
096        if (desc[0] < 0)
097            throw new IllegalArgumentException(
098                    "Illegal LUT Descriptor: len=" + desc[0]);
099        int bits = desc[2];
100        if (bits != 8 && bits != 16)
101            throw new IllegalArgumentException(
102                    "Illegal LUT Descriptor: bits=" + bits);
103        return desc;
104    }
105
106    private byte[] lutData(Attributes ds, int[] desc, int dataTag, int segmTag) {
107        int len = desc[0] == 0 ? 0x10000 : desc[0];
108        int bits = desc[2];
109        byte[] data = ds.getSafeBytes(dataTag);
110        if (data == null) {
111            int[] segm = ds.getInts(segmTag);
112            if (segm == null) {
113                throw new IllegalArgumentException("Missing LUT Data!");
114            }
115            if (bits == 8) {
116                throw new IllegalArgumentException(
117                        "Segmented LUT Data with LUT Descriptor: bits=8");
118            }
119            data = new byte[len];
120            inflateSegmentedLut(segm, data);
121        } else if (bits == 16 || data.length != len) {
122            if (data.length != len << 1)
123                lutLengthMismatch(data.length, len);
124            int hilo = ds.bigEndian() ? 0 : 1;
125            if (bits == 8)
126                hilo = 1 - hilo; // padded high bits -> use low bits
127            data = LookupTableFactory.halfLength(data, hilo);
128        }
129        return data;
130    }
131
132    private void inflateSegmentedLut(int[] in, byte[] out) {
133        int x = 0;
134        try {
135            for (int i = 0; i < in.length;) {
136                int op = in[i++];
137                int n = in[i++];
138                switch (op) {
139                case 0:
140                    while (n-- > 0)
141                        out[x++] = (byte) in[i++];
142                    break;
143                case 1:
144                     x = linearSegment(in[i++], out, x, n);
145                    break;
146                case 2: {
147                    int i2 = (in[i++] & 0xffff) | (in[i++] << 16);
148                    while (n-- > 0) {
149                        int op2 = in[i2++];
150                        int n2 = in[i2++] & 0xffff;
151                        switch (op2) {
152                        case 0:
153                            while (n2-- > 0)
154                                out[x++] = (byte) in[i2++];
155                            break;
156                        case 1:
157                            x = linearSegment(in[i2++], out, x, n);
158                            break;
159                        default:
160                            illegalOpcode(op, i2-2);
161                        }
162                    }
163                }
164                default:
165                    illegalOpcode(op, i-2);
166                }
167            }
168        } catch (IndexOutOfBoundsException e) {
169            if (x > out.length)
170                exceedsLutLength(out.length);
171            else
172                endOfSegmentedLut();
173        }
174        if (x < out.length)
175            lutLengthMismatch(x, out.length);
176    }
177
178    private static void endOfSegmentedLut() {
179        throw new IllegalArgumentException(
180                "Running out of data inflating segmented LUT");
181    }
182
183    private static int linearSegment(int y1, byte[] out, int x, int n) {
184        if (x == 0)
185            throw new IllegalArgumentException(
186                    "Linear segment cannot be the first segment");
187
188        try {
189            int y0 = out[x-1];
190            int dy = y1-y0;
191            for (int j = 1; j <= n; j++)
192                out[x++] = (byte)((y0 + dy*j/n)>>8);
193        } catch (IndexOutOfBoundsException e) {
194            exceedsLutLength(out.length);
195        }
196        return x;
197    }
198
199    private static void exceedsLutLength(int descLen) {
200        throw new IllegalArgumentException(
201                "Number of entries in inflated segmented LUT exceeds specified value: "
202                + descLen + " in LUT Descriptor");
203    }
204
205    private static void lutLengthMismatch(int lutLen, int descLen) {
206        throw new IllegalArgumentException("Number of actual LUT entries: "
207                + lutLen +  " mismatch specified value: " 
208                + descLen + " in LUT Descriptor");
209    }
210
211    private static void illegalOpcode(int op, int i) {
212        throw new IllegalArgumentException("illegal op code:" + op
213                + ", index:" + i);
214    }
215
216    @Override
217    public boolean isCompatibleRaster(Raster raster) {
218        return isCompatibleSampleModel(raster.getSampleModel());
219    }
220
221    @Override
222    public boolean isCompatibleSampleModel(SampleModel sm) {
223        return sm.getTransferType() == transferType
224                && sm.getNumBands() == 1; 
225    }
226
227    @Override
228    public int getRed(int pixel) {
229        return lut.getRed(pixel);
230    }
231
232    @Override
233    public int getGreen(int pixel) {
234        return lut.getGreen(pixel);
235    }
236
237    @Override
238    public int getBlue(int pixel) {
239        return lut.getBlue(pixel);
240    }
241
242    @Override
243    public int getAlpha(int pixel) {
244        return lut.getAlpha(pixel);
245    }
246
247    @Override
248    public int getRGB(int pixel) {
249        return lut.getRGB(pixel);
250    }
251
252    @Override
253    public WritableRaster createCompatibleWritableRaster(int w, int h) {
254        return Raster.createInterleavedRaster(
255                pixel_bits <= 8
256                    ? DataBuffer.TYPE_BYTE
257                    : DataBuffer.TYPE_USHORT,
258                    w, h, 1, null);
259    }
260
261    public BufferedImage convertToIntDiscrete(Raster raster) {
262        if (!isCompatibleRaster(raster))
263            throw new IllegalArgumentException(
264                    "This raster is not compatible with this PaletteColorModel.");
265
266        ColorModel cm = new DirectColorModel(getColorSpace(), 24,
267                0xff0000, 0x00ff00, 0x0000ff, 0, false, DataBuffer.TYPE_INT);
268
269        int w = raster.getWidth();
270        int h = raster.getHeight();
271        WritableRaster discreteRaster = cm.createCompatibleWritableRaster(w, h);
272        int[] discretData = ((DataBufferInt) discreteRaster.getDataBuffer()).getData();
273        DataBuffer data = raster.getDataBuffer();
274        if (data instanceof DataBufferByte) {
275            byte[] pixels = ((DataBufferByte) data).getData();
276            for (int i = 0; i < pixels.length; i++)
277                discretData[i] = getRGB(pixels[i]);
278        } else {
279            short[] pixels = ((DataBufferUShort) data).getData();
280            for (int i = 0; i < pixels.length; i++)
281                discretData[i] = getRGB(pixels[i]);
282        }
283        return new BufferedImage(cm, discreteRaster, false, null);
284    }
285
286    private static abstract class LUT {
287
288        final int mask;
289
290        LUT(int bits) {
291            mask = (1 << bits) - 1;
292        }
293
294        public static LUT create(int bits, byte[] r, byte[] g, byte[] b,
295                int rOffset, int gOffset, int bOffset) {
296            
297            return r.length == g.length && g.length == b.length
298                  && rOffset == gOffset && gOffset == bOffset
299                    ? new Packed(bits, r, g, b, rOffset)
300                    : new PerColor(bits, r, g, b, rOffset, gOffset, bOffset);
301        }
302
303        int index(int pixel, int offset, int length) {
304            return Math.min(Math.max(0, (pixel & mask) - offset), length-1);
305        }
306
307        abstract int getRed(int pixel);
308
309        abstract int getGreen(int pixel);
310
311        abstract int getBlue(int pixel);
312
313        abstract int getAlpha(int pixel);
314
315        abstract int getRGB(int pixel);
316
317        static class Packed extends LUT {
318
319            final int offset;
320            final int[] rgb;
321            
322            Packed(int bits, byte[] r, byte[] g, byte[] b, int offset) {
323                super(bits);
324                int length = r.length;
325                this.offset = offset;
326                rgb = new int[length];
327                for (int i = 0; i < r.length; i++)
328                    rgb[i] = 0xff000000
329                        | ((r[i] & 0xff) << 16)
330                        | ((g[i] & 0xff) << 8)
331                        | (b[i] & 0xff);
332            }
333
334            @Override
335            public int getAlpha(int pixel) {
336                return (rgb[index(pixel, offset, rgb.length)] >> 24) & 0xff;
337            }
338
339            @Override
340            public int getRed(int pixel) {
341                return (rgb[index(pixel, offset, rgb.length)] >> 16) & 0xff;
342            }
343
344            @Override
345            public int getGreen(int pixel) {
346                return (rgb[index(pixel, offset, rgb.length)] >> 8) & 0xff;
347            }
348
349            @Override
350            public int getBlue(int pixel) {
351                return rgb[index(pixel, offset, rgb.length)] & 0xff;
352            }
353
354            @Override
355            public int getRGB(int pixel) {
356                return rgb[index(pixel, offset, rgb.length)];
357            }
358        }
359
360        static class PerColor extends LUT {
361 
362            final byte[] r;
363            final byte[] g;
364            final byte[] b;
365            final int rOffset;
366            final int gOffset;
367            final int bOffset;
368
369            PerColor(int bits, byte[] r, byte[] g, byte[] b, int rOffset,
370                    int gbOffset, int bOffset) {
371                super(bits);
372                this.r = r;
373                this.g = g;
374                this.b = b;
375                this.rOffset = rOffset;
376                this.gOffset = gbOffset;
377                this.bOffset = bOffset;
378            }
379
380            @Override
381            public int getAlpha(int pixel) {
382                return 0xff;
383            }
384
385            @Override
386            public int getRed(int pixel) {
387                return value(pixel, rOffset, r);
388            }
389
390            @Override
391            public int getGreen(int pixel) {
392                return value(pixel, gOffset, g);
393            }
394
395            @Override
396            public int getBlue(int pixel) {
397                return value(pixel, bOffset, b);
398            }
399
400            @Override
401            public int getRGB(int pixel) {
402                return 0xff000000
403                            | (value(pixel, rOffset, r) << 16)
404                            | (value(pixel, gOffset, g) << 8)
405                            | (value(pixel, bOffset, b));
406            }
407
408            int value(int pixel, int offset, byte[] lut) {
409                return lut[index(pixel, offset, lut.length)] & 0xff;
410            }
411        }
412
413    }
414
415}