001/*
002 * **** BEGIN LICENSE BLOCK *****
003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
004 *
005 * The contents of this file are subject to the Mozilla Public License Version
006 * 1.1 (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 * http://www.mozilla.org/MPL/
009 *
010 * Software distributed under the License is distributed on an "AS IS" basis,
011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012 * for the specific language governing rights and limitations under the
013 * License.
014 *
015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
016 * Java(TM), hosted at https://github.com/dcm4che.
017 *
018 * The Initial Developer of the Original Code is Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011-2015
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 */
039
040package org.dcm4che3.imageio.codec;
041
042import java.awt.*;
043import java.awt.color.ColorSpace;
044import java.awt.image.*;
045import java.io.IOException;
046import java.io.OutputStream;
047
048/**
049 * @author Gunter Zeilinger <gunterze@gmail.com>
050 * @since Feb 2015.
051 */
052class BufferedImageUtils {
053
054    public static BufferedImage createBufferedImage(ImageParams imageParams, TransferSyntaxType tsType) {
055        int dataType = imageParams.getBitsAllocated() > 8
056                ? (imageParams.isSigned() && (tsType == null || tsType.canEncodeSigned())
057                    ? DataBuffer.TYPE_SHORT
058                    : DataBuffer.TYPE_USHORT)
059                : DataBuffer.TYPE_BYTE;
060        int samples = imageParams.getSamples();
061        int bitsStored = tsType == null
062                ? imageParams.getBitsStored()
063                : Math.min(imageParams.getBitsStored(), tsType.getMaxBitsStored());
064        ComponentColorModel cm = samples == 1
065                    ? new ComponentColorModel(
066                        ColorSpace.getInstance(ColorSpace.CS_GRAY),
067                        new int[] { bitsStored },
068                        false, // hasAlpha
069                        false, // isAlphaPremultiplied,
070                        Transparency.OPAQUE,
071                        dataType)
072                    :  new ComponentColorModel(
073                        ColorSpace.getInstance(ColorSpace.CS_sRGB),
074                        new int[] { bitsStored, bitsStored, bitsStored },
075                        false, // hasAlpha
076                        false, // isAlphaPremultiplied,
077                        Transparency.OPAQUE,
078                        dataType);
079
080        int rows = imageParams.getRows();
081        int columns = imageParams.getColumns();
082        SampleModel sm = imageParams.isBanded()
083                ? new BandedSampleModel(dataType, columns, rows, samples)
084                : new PixelInterleavedSampleModel(dataType, columns, rows,
085                        samples, columns * samples, bandOffsets(samples));
086        WritableRaster raster = Raster.createWritableRaster(sm, null);
087        return new BufferedImage(cm, raster, false, null);
088    }
089
090    private static int[] bandOffsets(int samples) {
091        int[] offsets = new int[samples];
092        for (int i = 0; i < samples; i++)
093            offsets[i] = i;
094        return offsets;
095    }
096
097    public static int sizeOf(BufferedImage bi) {
098        WritableRaster raster = bi.getRaster();
099        DataBuffer db = raster.getDataBuffer();
100        return db.getSize() * db.getNumBanks()
101                * (DataBuffer.getDataTypeSize(db.getDataType()) >>> 3);
102    }
103
104    public static void writeTo(BufferedImage bi, OutputStream out) throws IOException {
105        WritableRaster raster = bi.getRaster();
106        SampleModel sm = raster.getSampleModel();
107        DataBuffer db = raster.getDataBuffer();
108        switch (db.getDataType()) {
109            case DataBuffer.TYPE_BYTE:
110                writeTo(sm, ((DataBufferByte) db).getBankData(), out);
111                break;
112            case DataBuffer.TYPE_USHORT:
113                writeTo(sm, ((DataBufferUShort) db).getData(), out);
114                break;
115            case DataBuffer.TYPE_SHORT:
116                writeTo(sm, ((DataBufferShort) db).getData(), out);
117                break;
118            case DataBuffer.TYPE_INT:
119                writeTo(sm, ((DataBufferInt) db).getData(), out);
120                break;
121            default:
122                throw new UnsupportedOperationException(
123                        "Unsupported Datatype: " + db.getDataType());
124        }
125    }
126
127    private static void writeTo(SampleModel sm, byte[][] bankData, OutputStream out)
128            throws IOException {
129        int h = sm.getHeight();
130        int w = sm.getWidth();
131        ComponentSampleModel csm = (ComponentSampleModel) sm;
132        int len = w * csm.getPixelStride();
133        int stride = csm.getScanlineStride();
134        if (csm.getBandOffsets()[0] != 0)
135            bgr2rgb(bankData[0]);
136        for (byte[] b : bankData)
137            for (int y = 0, off = 0; y < h; ++y, off += stride)
138                out.write(b, off, len);
139    }
140
141    private static void bgr2rgb(byte[] bs) {
142        for (int i = 0, j = 2; j < bs.length; i += 3, j += 3) {
143            byte b = bs[i];
144            bs[i] = bs[j];
145            bs[j] = b;
146        }
147    }
148
149    private static void writeTo(SampleModel sm, short[] data, OutputStream out)
150            throws IOException {
151        int h = sm.getHeight();
152        int w = sm.getWidth();
153        int stride = ((ComponentSampleModel) sm).getScanlineStride();
154        byte[] b = new byte[w * 2];
155        for (int y = 0; y < h; ++y) {
156            for (int i = 0, j = y * stride; i < b.length;) {
157                short s = data[j++];
158                b[i++] = (byte) s;
159                b[i++] = (byte) (s >> 8);
160            }
161            out.write(b);
162        }
163    }
164
165    private static void writeTo(SampleModel sm, int[] data, OutputStream out)
166            throws IOException {
167        int h = sm.getHeight();
168        int w = sm.getWidth();
169        int stride = ((SinglePixelPackedSampleModel) sm).getScanlineStride();
170        byte[] b = new byte[w * 3];
171        for (int y = 0; y < h; ++y) {
172            for (int i = 0, j = y * stride; i < b.length;) {
173                int s = data[j++];
174                b[i++] = (byte) (s >> 16);
175                b[i++] = (byte) (s >> 8);
176                b[i++] = (byte) s;
177            }
178            out.write(b);
179        }
180    }
181
182    public static void nullifyUnusedBits(int bitsStored, DataBuffer db) {
183        if (bitsStored >= 16)
184            return;
185
186        short[] data;
187        switch (db.getDataType()) {
188        case DataBuffer.TYPE_USHORT:
189            data = ((DataBufferUShort) db).getData();
190            break;
191        case DataBuffer.TYPE_SHORT:
192            data = ((DataBufferShort) db).getData();
193            break;
194        default:
195            throw new IllegalArgumentException("Unsupported Datatype: " + db.getDataType());
196        }
197        int mask = (1 << bitsStored) - 1;
198        for (int i = 0; i < data.length; i++)
199            data[i] &= mask;
200    }
201
202    public static int maxDiff(WritableRaster raster, WritableRaster raster2) {
203        ComponentSampleModel csm =
204                (ComponentSampleModel) raster.getSampleModel();
205        ComponentSampleModel csm2 =
206                (ComponentSampleModel) raster2.getSampleModel();
207        DataBuffer db = raster.getDataBuffer();
208        DataBuffer db2 = raster2.getDataBuffer();
209        switch (db.getDataType()) {
210            case DataBuffer.TYPE_BYTE:
211                return maxDiff(csm, ((DataBufferByte) db).getBankData(),
212                        csm2, ((DataBufferByte) db2).getBankData());
213            case DataBuffer.TYPE_USHORT:
214            case DataBuffer.TYPE_SHORT:
215                return maxDiff(csm, getShortData(db),csm2, getShortData(db2));
216            default:
217                throw new UnsupportedOperationException(
218                        "Unsupported Datatype: " + db.getDataType());
219        }
220    }
221
222    private static short[] getShortData (DataBuffer db) {
223        if (db instanceof DataBufferShort)
224            return ((DataBufferShort)db).getData();
225        if (db instanceof DataBufferUShort)
226            return ((DataBufferUShort)db).getData();
227        throw new UnsupportedOperationException(
228                "Unsupported Datatype: " + db.getDataType());
229    }
230
231    public static int maxDiff(WritableRaster raster, WritableRaster raster2, int blockSize) {
232        if (blockSize <= 1)
233            return maxDiff(raster, raster2);
234
235        ComponentSampleModel csm =
236                (ComponentSampleModel) raster.getSampleModel();
237        ComponentSampleModel csm2 =
238                (ComponentSampleModel) raster2.getSampleModel();
239        DataBuffer db = raster.getDataBuffer();
240        DataBuffer db2 = raster2.getDataBuffer();
241        int w = csm.getWidth();
242        int h = csm.getHeight();
243        int maxY = (h / blockSize - 1) * blockSize;
244        int maxX = (w / blockSize - 1) * blockSize;
245        int[] samples = new int[blockSize * blockSize];
246        int diff, maxDiff = 0;
247        for (int b = 0; b < csm.getNumBands(); b++)
248            for (int y = 0; y < maxY; y += blockSize) {
249                for (int x = 0; x < maxX; x += blockSize) {
250                    if (maxDiff < (diff = Math.abs(
251                            sum(csm.getSamples(
252                                    x, y, blockSize, blockSize, b, samples, db))
253                                    - sum(csm2.getSamples(
254                                    x, y, blockSize, blockSize, b, samples, db2)))))
255                        maxDiff = diff;
256                }
257            }
258        return maxDiff / samples.length;
259    }
260
261    private static int sum(int[] samples) {
262        int sum = 0;
263        for (int sample : samples)
264            sum += sample;
265        return sum;
266    }
267
268    private static int maxDiff(ComponentSampleModel csm, short[] data,
269                        ComponentSampleModel csm2, short[] data2) {
270        int w = csm.getWidth() * csm.getPixelStride();
271        int h = csm.getHeight();
272        int stride = csm.getScanlineStride();
273        int stride2 = csm2.getScanlineStride();
274        int diff, maxDiff = 0;
275        for (int y = 0; y < h; y++) {
276            for (int j = w, i = y * stride, i2 = y * stride2; j-- > 0; i++, i2++) {
277                if (maxDiff < (diff = Math.abs(data[i] - data2[i2])))
278                    maxDiff = diff;
279            }
280        }
281        return maxDiff;
282    }
283
284    private static int maxDiff(ComponentSampleModel csm, byte[][] banks,
285                        ComponentSampleModel csm2, byte[][] banks2) {
286        int w = csm.getWidth();
287        int h = csm.getHeight();
288        int bands = csm.getNumBands();
289        int stride = csm.getScanlineStride();
290        int pixelStride = csm.getPixelStride();
291        int[] bankIndices = csm.getBankIndices();
292        int[] bandOffsets = csm.getBandOffsets();
293        int stride2 = csm2.getScanlineStride();
294        int pixelStride2 = csm2.getPixelStride();
295        int[] bankIndices2 = csm2.getBankIndices();
296        int[] bandOffsets2 = csm2.getBandOffsets();
297        int diff, maxDiff = 0;
298        for (int b = 0; b < bands; b++) {
299            byte[] bank = banks[bankIndices[b]];
300            byte[] bank2 = banks2[bankIndices2[b]];
301            int off = bandOffsets[b];
302            int off2 = bandOffsets2[b];
303            for (int y = 0; y < h; y++) {
304                for (int x = w, i = y * stride + off, i2 = y * stride2 + off2;
305                     x-- > 0; i += pixelStride, i2 += pixelStride2) {
306                    if (maxDiff < (diff = Math.abs(bank[i] - bank2[i2])))
307                        maxDiff = diff;
308                }
309            }
310        }
311        return maxDiff;
312    }
313}