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.imageio.codec;
040
041import org.dcm4che3.conf.core.api.ConfigurableClass;
042import org.dcm4che3.conf.core.api.ConfigurableProperty;
043import org.dcm4che3.conf.core.api.LDAP;
044import org.dcm4che3.data.UID;
045import org.dcm4che3.imageio.codec.jpeg.PatchJPEGLS;
046import org.dcm4che3.util.Property;
047import org.dcm4che3.util.ResourceLocator;
048import org.dcm4che3.util.SafeClose;
049import org.dcm4che3.util.StringUtils;
050import org.slf4j.Logger;
051import org.slf4j.LoggerFactory;
052
053import javax.imageio.ImageIO;
054import javax.imageio.ImageWriter;
055import java.io.File;
056import java.io.IOException;
057import java.io.InputStream;
058import java.io.Serializable;
059import java.net.MalformedURLException;
060import java.net.URL;
061import java.util.Iterator;
062import java.util.Map;
063import java.util.Map.Entry;
064import java.util.Properties;
065import java.util.TreeMap;
066
067/**
068 * Provides Image Writers for different DICOM transfer syntaxes and MIME types.
069 * 
070 * @author Gunter Zeilinger <gunterze@gmail.com>
071 * @author Hermann Czedik-Eysenberg <hermann-agfa@czedik.net>
072 */
073@LDAP(objectClasses = "dcmImageWriterFactory")
074@ConfigurableClass
075public class ImageWriterFactory implements Serializable {
076    private static final Logger LOG = LoggerFactory.getLogger(ImageWriterFactory.class);
077
078    private static final long serialVersionUID = 6328126996969794374L;
079
080    @LDAP(objectClasses = "dcmImageWriter")
081    @ConfigurableClass
082    public static class ImageWriterParam implements Serializable {
083
084        private static final long serialVersionUID = 3521737269113651910L;
085
086        @ConfigurableProperty(name="dcmIIOFormatName")
087        public String formatName;
088
089        @ConfigurableProperty(name="dcmJavaClassName")
090        public String className;
091
092        @ConfigurableProperty(name="dcmPatchJPEGLS")
093        public PatchJPEGLS patchJPEGLS;
094
095        @ConfigurableProperty(name = "dcmImageWriteParam")
096        public Property[] imageWriteParams;
097
098        @ConfigurableProperty(name = "dcmWriteIIOMetadata")
099        public Property[] iioMetadata;
100
101        public ImageWriterParam() {
102        }
103
104        public ImageWriterParam(String formatName, String className,
105                PatchJPEGLS patchJPEGLS, Property[] imageWriteParams, Property[] iioMetadata) {
106            this.formatName = formatName;
107            this.className = nullify(className);
108            this.patchJPEGLS = patchJPEGLS;
109            this.imageWriteParams = imageWriteParams;
110            this.iioMetadata = iioMetadata;
111        }
112
113        public ImageWriterParam(String formatName, String className,
114                String patchJPEGLS, String[] imageWriteParams, String[] iioMetadata) {
115            this(formatName, className, patchJPEGLS != null
116                    && !patchJPEGLS.isEmpty() ? PatchJPEGLS
117                    .valueOf(patchJPEGLS) : null, Property
118                    .valueOf(imageWriteParams), Property.valueOf(iioMetadata));
119        }
120
121        public ImageWriterParam(String formatName, String className,
122                String patchJPEGLS, String[] imageWriteParams) {
123            this(formatName, className, patchJPEGLS != null
124                    && !patchJPEGLS.isEmpty() ? PatchJPEGLS
125                    .valueOf(patchJPEGLS) : null, Property
126                    .valueOf(imageWriteParams), null);
127        }
128
129
130        public Property[] getImageWriteParams() {
131            return imageWriteParams;
132        }
133
134        public Property[] getIIOMetadata() {
135            return iioMetadata;
136        }
137
138        public String getFormatName() {
139            return formatName;
140        }
141
142        public void setFormatName(String formatName) {
143            this.formatName = formatName;
144        }
145
146        public String getClassName() {
147            return className;
148        }
149
150        public void setClassName(String className) {
151            this.className = className;
152        }
153
154        public PatchJPEGLS getPatchJPEGLS() {
155            return patchJPEGLS;
156        }
157
158        public void setPatchJPEGLS(PatchJPEGLS patchJPEGLS) {
159            this.patchJPEGLS = patchJPEGLS;
160        }
161
162        public void setImageWriteParams(Property[] imageWriteParams) {
163            this.imageWriteParams = imageWriteParams;
164        }
165
166        public Property[] getIioMetadata() {
167            return iioMetadata;
168        }
169
170        public void setIioMetadata(Property[] iioMetadata) {
171            this.iioMetadata = iioMetadata;
172        }
173    }
174
175    private static ImageWriterFactory defaultFactory;
176
177    @LDAP(distinguishingField = "dicomTransferSyntax", noContainerNode = true)
178    @ConfigurableProperty(
179        name="dicomImageWriterMap",
180        label = "Image Writers by transfer syntax",
181        description = "Image writers by transfer syntax"
182    )
183    private Map<String, ImageWriterParam> mapTransferSyntaxUIDs = new TreeMap<String, ImageWriterParam>();
184    
185    @ConfigurableProperty(
186            name="dicomImageWriterMapMime",
187            label = "Image Writers by MIME type",
188            description = "Image writers by MIME type"
189    )
190    private Map<String, ImageWriterParam> mapMimeTypes = new TreeMap<String, ImageWriterParam>();
191
192    public Map<String, ImageWriterParam> getMapTransferSyntaxUIDs() {
193        return mapTransferSyntaxUIDs;
194    }
195
196    public void setMapTransferSyntaxUIDs(Map<String, ImageWriterParam> mapTransferSyntaxUIDs) {
197        this.mapTransferSyntaxUIDs = mapTransferSyntaxUIDs;
198    }
199
200    public Map<String, ImageWriterParam> getMapMimeTypes() {
201        return mapMimeTypes;
202    }
203
204    public void setMapMimeTypes(Map<String, ImageWriterParam> mapMimeTypes) {
205        this.mapMimeTypes = mapMimeTypes;
206    }
207
208    private static String nullify(String s) {
209        return s == null || s.isEmpty() || s.equals("*") ? null : s;
210    }
211
212    public static ImageWriterFactory getDefault() {
213        if (defaultFactory == null)
214            defaultFactory = initDefault();
215
216        return defaultFactory;
217    }
218
219    public static void resetDefault() {
220        defaultFactory = null;
221    }
222
223    public static void setDefault(ImageWriterFactory factory) {
224        if (factory == null)
225            throw new NullPointerException();
226
227        defaultFactory = factory;
228    }
229
230    private static ImageWriterFactory initDefault() {
231        ImageWriterFactory factory = new ImageWriterFactory();
232        String name = System.getProperty(ImageWriterFactory.class.getName(),
233                "org/dcm4che3/imageio/codec/ImageWriterFactory.properties");
234        try {
235            factory.load(name);
236        } catch (Exception e) {
237            throw new RuntimeException(
238                    "Failed to load Image Writer Factory configuration from: "
239                            + name, e);
240        }
241
242        factory.init();
243
244        return factory;
245    }
246
247    public void init() {
248        if (LOG.isDebugEnabled()) {
249            StringBuilder sb = new StringBuilder();
250            sb.append("Image Writers:\n");
251            for (Entry<String, ImageWriterParam> entry : mapTransferSyntaxUIDs.entrySet()) {
252                String tsUid = entry.getKey();
253                sb.append(' ').append(tsUid);
254                sb.append(" (").append(UID.nameOf(tsUid)).append("): ");
255                sb.append(getImageWriterName(entry.getValue())).append('\n');
256            }
257            for (Entry<String, ImageWriterParam> entry : mapMimeTypes.entrySet()) {
258                sb.append(' ').append(entry.getKey()).append(": ");
259                sb.append(getImageWriterName(entry.getValue())).append('\n');
260            }
261            LOG.debug(sb.toString());
262        }
263    }
264
265    private String getImageWriterName(ImageWriterParam imageWriterParam) {
266        ImageWriter imageWriter = null;
267        try {
268            imageWriter = getImageWriter(imageWriterParam);
269        } catch (RuntimeException e) {
270            // none found
271        }
272        return imageWriter != null ? imageWriter.getClass().getName() : "null";
273    }
274
275    public void load(String name) throws IOException {
276        URL url;
277        try {
278            url = new URL(name);
279        } catch (MalformedURLException e) {
280            url = ResourceLocator.getResourceURL(name, this.getClass());
281            if (url == null) {
282                File f = new File(name);
283                if(f.exists() && f.isFile()) {
284                    url = f.toURI().toURL();
285                } else {
286                    throw new IOException("No such resource: " + name);
287                }
288            }
289        }
290        InputStream in = url.openStream();
291        try {
292            load(in);
293        } finally {
294            SafeClose.close(in);
295        }
296    }
297
298    public void load(InputStream in) throws IOException {
299        Properties props = new Properties();
300        props.load(in);
301        for (Map.Entry<Object, Object> entry : props.entrySet()) {
302            String key = (String) entry.getKey();
303
304            String[] ss = StringUtils.split((String) entry.getValue(), ':');
305            String formatName = ss[0];
306            String className = ss[1];
307            String patchJPEGLS = ss[2];
308            String[] imageWriteParams = StringUtils.split(ss[3], ';');
309
310            if (key.contains("/")) { // mime type
311                mapMimeTypes.put(key, new ImageWriterParam(formatName, className, patchJPEGLS, imageWriteParams));
312            } else { // transfer syntax uid
313                mapTransferSyntaxUIDs.put(key, new ImageWriterParam(formatName, className, patchJPEGLS, imageWriteParams));
314            }
315
316        }
317    }
318
319    public ImageWriterParam getForTransferSyntaxUID(String tsuid) {
320        return mapTransferSyntaxUIDs.get(tsuid);
321    }
322
323    public ImageWriterParam getForMimeType(String mimeType) {
324        return mapMimeTypes.get(mimeType);
325    }
326
327    public static ImageWriterParam getImageWriterParam(String tsuid) {
328        return getDefault().getForTransferSyntaxUID(tsuid);
329    }
330
331    public static ImageWriter getImageWriter(ImageWriterParam param) {
332
333        Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(param.formatName);
334
335        while (writers.hasNext()) {
336            ImageWriter writer = writers.next();
337
338            if (param.className == null || param.className.equals(writer.getClass().getName())) {
339                LOG.debug("Using Image Writer {}", writer.getClass());
340                return writer;
341            }
342        }
343
344        throw new RuntimeException("No matching Image Writer for format: " + param.formatName + " (Class: " + ((param.className == null) ? "*" : param.className) + ") registered");
345    }
346
347    public static ImageWriter getImageWriterForMimeType(String mimeType) {
348        ImageWriterParam imageWriterParam = getDefault().getForMimeType(mimeType);
349
350        if (imageWriterParam != null) {
351            // configured mime type
352            return getImageWriter(imageWriterParam);
353        } else {
354            // not configured mime type, fallback to first ImageIO writer for this mime type
355            ImageWriter writer = ImageIO.getImageWritersByMIMEType(mimeType).next();
356            LOG.debug("Using Image Writer {}", writer.getClass());
357            return writer;
358        }
359    }
360
361}