001/*
002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 */
019package org.nuxeo.ecm.platform.picture.api.adapters;
020
021import static org.nuxeo.ecm.platform.picture.api.ImagingConvertConstants.OPERATION_CROP;
022import static org.nuxeo.ecm.platform.picture.api.ImagingConvertConstants.OPTION_CROP_X;
023import static org.nuxeo.ecm.platform.picture.api.ImagingConvertConstants.OPTION_CROP_Y;
024import static org.nuxeo.ecm.platform.picture.api.ImagingConvertConstants.OPTION_RESIZE_HEIGHT;
025import static org.nuxeo.ecm.platform.picture.api.ImagingConvertConstants.OPTION_RESIZE_WIDTH;
026import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_BY_LINE;
027import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_BY_LINE_TITLE;
028import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_CAPTION;
029import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_CATEGORY;
030import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_CITY;
031import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_COLORSPACE;
032import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_COMMENT;
033import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_COPYRIGHT;
034import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_COPYRIGHT_NOTICE;
035import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_COUNTRY_OR_PRIMARY_LOCATION;
036import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_CREDIT;
037import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_DATE_CREATED;
038import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_DESCRIPTION;
039import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_EQUIPMENT;
040import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_EXPOSURE;
041import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_FNUMBER;
042import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_FOCALLENGTH;
043import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_HEADLINE;
044import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_HEIGHT;
045import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_HRESOLUTION;
046import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_ICCPROFILE;
047import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_ISOSPEED;
048import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_KEYWORDS;
049import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_LANGUAGE;
050import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_OBJECT_NAME;
051import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_ORIENTATION;
052import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_ORIGINALDATE;
053import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_ORIGINAL_TRANSMISSION_REFERENCE;
054import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_ORIGINATING_PROGRAM;
055import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_PIXEL_XDIMENSION;
056import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_PIXEL_YDIMENSION;
057import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_PROVINCE_OR_STATE;
058import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_RECORD_VERSION;
059import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_RELEASE_DATE;
060import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_RELEASE_TIME;
061import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_SOURCE;
062import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_SPECIAL_INSTRUCTIONS;
063import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_SUPPLEMENTAL_CATEGORIES;
064import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_TIME_CREATED;
065import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_URGENCY;
066import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_VRESOLUTION;
067import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_WHITEBALANCE;
068import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_WIDTH;
069import static org.nuxeo.ecm.platform.picture.api.MetadataConstants.META_WRITER;
070
071import java.awt.color.ICC_Profile;
072import java.io.IOException;
073import java.io.Serializable;
074import java.util.ArrayList;
075import java.util.Calendar;
076import java.util.Date;
077import java.util.GregorianCalendar;
078import java.util.HashMap;
079import java.util.List;
080import java.util.Map;
081
082import org.nuxeo.ecm.core.api.Blob;
083import org.nuxeo.ecm.core.api.Blobs;
084import org.nuxeo.ecm.core.api.CoreSession;
085import org.nuxeo.ecm.core.api.DocumentModel;
086import org.nuxeo.ecm.core.api.NuxeoException;
087import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
088import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder;
089import org.nuxeo.ecm.core.convert.api.ConversionService;
090import org.nuxeo.ecm.platform.picture.api.ImageInfo;
091import org.nuxeo.ecm.platform.picture.api.ImagingService;
092import org.nuxeo.ecm.platform.picture.api.PictureConversion;
093import org.nuxeo.ecm.platform.picture.api.PictureView;
094import org.nuxeo.runtime.api.Framework;
095
096public abstract class AbstractPictureAdapter implements PictureResourceAdapter {
097
098    public static final String VIEWS_PROPERTY = "picture:views";
099
100    public static final String CONTENT_XPATH = "picture:views/view[%d]/content";
101
102    public static final String FIELD_HEADLINE = "headline";
103
104    public static final String FIELD_SUBHEADLINE = "subheadline";
105
106    public static final String FIELD_BYLINE = "byline";
107
108    public static final String FIELD_DATELINE = "dateline";
109
110    public static final String FIELD_SLUGLINE = "slugline";
111
112    public static final String FIELD_CREDIT = "credit";
113
114    public static final String FIELD_LANGUAGE = "language";
115
116    public static final String FIELD_SOURCE = "source";
117
118    public static final String FIELD_ORIGIN = "origin";
119
120    public static final String FIELD_GENRE = "genre";
121
122    public static final String FIELD_CAPTION = "caption";
123
124    public static final String FIELD_TYPAGE = "typage";
125
126    public static final String SCHEMA_NAME = "picture";
127
128    public static final int MEDIUM_SIZE = 1200;
129
130    public static final int SMALL_SIZE = 350;
131
132    public static final int THUMB_SIZE = 150;
133
134    protected DocumentModel doc;
135
136    protected Integer width;
137
138    protected Integer height;
139
140    protected Integer depth;
141
142    protected String description;
143
144    protected String type;
145
146    protected Blob fileContent;
147
148    /** @since 5.9.5 */
149    protected ImageInfo imageInfo;
150
151    private CoreSession session;
152
153    @Override
154    public void setDocumentModel(DocumentModel doc) {
155        this.doc = doc;
156    }
157
158    protected ImagingService getImagingService() {
159        return Framework.getService(ImagingService.class);
160    }
161
162    protected ConversionService getConversionService() {
163        return Framework.getService(ConversionService.class);
164    }
165
166    protected CoreSession getSession() {
167        if (session == null) {
168            if (doc == null) {
169                return null;
170            }
171            session = doc.getCoreSession();
172        }
173        return session;
174    }
175
176    protected void setMetadata() throws IOException {
177        boolean imageInfoUsed = false;
178        ImageInfo imageInfo = getImageInfo();
179        if (imageInfo != null) {
180            width = imageInfo.getWidth();
181            height = imageInfo.getHeight();
182            depth = imageInfo.getDepth();
183            imageInfoUsed = true;
184        }
185        Map<String, Object> metadata = getImagingService().getImageMetadata(fileContent);
186        description = (String) metadata.get(META_DESCRIPTION);
187        if (!imageInfoUsed) {
188            width = (Integer) metadata.get(META_WIDTH);
189            height = (Integer) metadata.get(META_HEIGHT);
190        }
191        doc.setPropertyValue("picture:" + FIELD_BYLINE, (String) metadata.get(META_BY_LINE));
192        doc.setPropertyValue("picture:" + FIELD_CAPTION, (String) metadata.get(META_CAPTION));
193        doc.setPropertyValue("picture:" + FIELD_CREDIT, (String) metadata.get(META_CREDIT));
194        if (metadata.containsKey(META_DATE_CREATED)) {
195            doc.setPropertyValue("picture:" + FIELD_DATELINE, metadata.get(META_DATE_CREATED).toString());
196        }
197        doc.setPropertyValue("picture:" + FIELD_HEADLINE, (String) metadata.get(META_HEADLINE));
198        doc.setPropertyValue("picture:" + FIELD_LANGUAGE, (String) metadata.get(META_LANGUAGE));
199        doc.setPropertyValue("picture:" + FIELD_ORIGIN, (String) metadata.get(META_OBJECT_NAME));
200        doc.setPropertyValue("picture:" + FIELD_SOURCE, (String) metadata.get(META_SOURCE));
201
202        // Set EXIF info
203        doc.setPropertyValue("imd:image_description", (String) metadata.get(META_DESCRIPTION));
204        doc.setPropertyValue("imd:user_comment", (String) metadata.get(META_COMMENT));
205        doc.setPropertyValue("imd:equipment", (String) metadata.get(META_EQUIPMENT));
206        Date dateTimeOriginal = (Date) metadata.get(META_ORIGINALDATE);
207        if (dateTimeOriginal != null) {
208            Calendar calendar = new GregorianCalendar();
209            calendar.setTime(dateTimeOriginal);
210            doc.setPropertyValue("imd:date_time_original", calendar);
211        }
212        doc.setPropertyValue("imd:xresolution", (Integer) metadata.get(META_HRESOLUTION));
213        doc.setPropertyValue("imd:yresolution", (Integer) metadata.get(META_VRESOLUTION));
214        doc.setPropertyValue("imd:pixel_xdimension", (Integer) metadata.get(META_PIXEL_XDIMENSION));
215        doc.setPropertyValue("imd:pixel_ydimension", (Integer) metadata.get(META_PIXEL_YDIMENSION));
216        doc.setPropertyValue("imd:copyright", (String) metadata.get(META_COPYRIGHT));
217        doc.setPropertyValue("imd:exposure_time", (String) metadata.get(META_EXPOSURE));
218        doc.setPropertyValue("imd:iso_speed_ratings", (String) metadata.get(META_ISOSPEED));
219        doc.setPropertyValue("imd:focal_length", (Double) metadata.get(META_FOCALLENGTH));
220        doc.setPropertyValue("imd:color_space", (String) metadata.get(META_COLORSPACE));
221        doc.setPropertyValue("imd:white_balance", (String) metadata.get(META_WHITEBALANCE));
222        ICC_Profile iccProfile = (ICC_Profile) metadata.get(META_ICCPROFILE);
223        if (iccProfile != null) {
224            doc.setPropertyValue("imd:icc_profile", iccProfile.toString());
225        }
226        doc.setPropertyValue("imd:orientation", (String) metadata.get(META_ORIENTATION));
227        doc.setPropertyValue("imd:fnumber", (Double) metadata.get(META_FNUMBER));
228
229        // Set IPTC info
230        doc.setPropertyValue("iptc:by_line", (String) metadata.get(META_BY_LINE));
231        doc.setPropertyValue("iptc:by_line_title", (String) metadata.get(META_BY_LINE_TITLE));
232        doc.setPropertyValue("iptc:caption", (String) metadata.get(META_CAPTION));
233        doc.setPropertyValue("iptc:category", (String) metadata.get(META_CATEGORY));
234        doc.setPropertyValue("iptc:city", (String) metadata.get(META_CITY));
235        doc.setPropertyValue("iptc:copyright_notice", (String) metadata.get(META_COPYRIGHT_NOTICE));
236        doc.setPropertyValue("iptc:country_or_primary_location",
237                (String) metadata.get(META_COUNTRY_OR_PRIMARY_LOCATION));
238        doc.setPropertyValue("iptc:credit", (String) metadata.get(META_CREDIT));
239        Date dateCreated = (Date) metadata.get(META_DATE_CREATED);
240        if (dateCreated != null) {
241            Calendar calendar = new GregorianCalendar();
242            calendar.setTime(dateCreated);
243            doc.setPropertyValue("iptc:date_created", calendar);
244        }
245        doc.setPropertyValue("iptc:headline", (String) metadata.get(META_HEADLINE));
246        doc.setPropertyValue("iptc:keywords", (String) metadata.get(META_KEYWORDS));
247        doc.setPropertyValue("iptc:language", (String) metadata.get(META_LANGUAGE));
248        doc.setPropertyValue("iptc:object_name", (String) metadata.get(META_OBJECT_NAME));
249        doc.setPropertyValue("iptc:original_transmission_ref",
250                (String) metadata.get(META_ORIGINAL_TRANSMISSION_REFERENCE));
251        doc.setPropertyValue("iptc:originating_program", (String) metadata.get(META_ORIGINATING_PROGRAM));
252        doc.setPropertyValue("iptc:province_or_state", (String) metadata.get(META_PROVINCE_OR_STATE));
253        doc.setPropertyValue("iptc:record_version", (String) metadata.get(META_RECORD_VERSION));
254        Date releaseDate = (Date) metadata.get(META_RELEASE_DATE);
255        if (releaseDate != null) {
256            Calendar calendar = new GregorianCalendar();
257            calendar.setTime(releaseDate);
258            doc.setPropertyValue("iptc:release_date", calendar);
259        }
260        doc.setPropertyValue("iptc:release_time", (String) metadata.get(META_RELEASE_TIME));
261        doc.setPropertyValue("iptc:source", (String) metadata.get(META_SOURCE));
262        doc.setPropertyValue("iptc:special_instructions", (String) metadata.get(META_SPECIAL_INSTRUCTIONS));
263        doc.setPropertyValue("iptc:supplemental_categories", (String) metadata.get(META_SUPPLEMENTAL_CATEGORIES));
264        doc.setPropertyValue("iptc:time_created", (String) metadata.get(META_TIME_CREATED));
265        doc.setPropertyValue("iptc:urgency", (String) metadata.get(META_URGENCY));
266        doc.setPropertyValue("iptc:writer", (String) metadata.get(META_WRITER));
267    }
268
269    protected void clearViews() {
270        List<Map<String, Object>> viewsList = new ArrayList<>();
271        doc.getProperty(VIEWS_PROPERTY).setValue(viewsList);
272    }
273
274    protected void addViews(List<Map<String, Object>> pictureConversions, String filename, String title)
275            throws IOException {
276        doc.setProperty("dublincore", "title", title);
277        if (pictureConversions != null) {
278            // Use PictureBook Properties
279            for (Map<String, Object> view : pictureConversions) {
280                Integer maxsize;
281                if (view.get("maxsize") == null) {
282                    maxsize = MEDIUM_SIZE;
283                } else {
284                    maxsize = ((Long) view.get("maxsize")).intValue();
285                }
286                createPictureimpl((String) view.get("description"), (String) view.get("tag"),
287                        (String) view.get("title"), maxsize, filename, width, height, depth, fileContent);
288            }
289        } else {
290            List<PictureView> pictureViews = getImagingService().computeViewsFor(doc, fileContent, getImageInfo(),
291                    true);
292            addPictureViews(pictureViews, true);
293        }
294    }
295
296    public void createPictureimpl(String description, String tag, String title, Integer maxsize, String filename,
297            Integer width, Integer height, Integer depth, Blob fileContent) throws IOException {
298        if (fileContent.getFilename() == null) {
299            fileContent.setFilename(filename);
300        }
301
302        if (maxsize == null) {
303            maxsize = 0;
304        }
305
306        PictureConversion pictureConversion = new PictureConversion(title, description, tag, maxsize);
307        pictureConversion.setChainId("Image.Blob.Resize");
308
309        PictureView view = getImagingService().computeViewFor(fileContent, pictureConversion, getImageInfo(), true);
310
311        addPictureView(view);
312    }
313
314    /**
315     * Attach new picture views with the document
316     *
317     * @since 7.1
318     */
319    protected void addPictureViews(List<PictureView> pictureViews, boolean clearPictureViews) {
320        if (clearPictureViews) {
321            clearViews();
322        }
323
324        List<Map<String, Serializable>> views = getPictureViews();
325
326        for (PictureView pictureView : pictureViews) {
327            views.add(pictureView.asMap());
328        }
329
330        doc.setPropertyValue(VIEWS_PROPERTY, (Serializable) views);
331    }
332
333    @Override
334    public boolean fillPictureViews(Blob blob, String filename, String title) throws IOException {
335        return fillPictureViews(blob, filename, title, null);
336    }
337
338    /**
339     * Returns the picture views attached to the document if present, an empty list otherwise.
340     *
341     * @since 7.1
342     */
343    @SuppressWarnings("unchecked")
344    protected List<Map<String, Serializable>> getPictureViews() {
345        List<Map<String, Serializable>> views = (List<Map<String, Serializable>>) doc.getPropertyValue(VIEWS_PROPERTY);
346        if (views == null) {
347            views = new ArrayList<>();
348        }
349        return views;
350    }
351
352    /**
353     * Add a pictureView to the existing picture views attached with the document
354     *
355     * @since 7.1
356     */
357    protected void addPictureView(PictureView view) {
358        List<Map<String, Serializable>> views = getPictureViews();
359        views.add(view.asMap());
360        doc.setPropertyValue(VIEWS_PROPERTY, (Serializable) views);
361    }
362
363    /**
364     * Returns the {@link ImageInfo} for the main Blob ({@code fileContent}).
365     *
366     * @since 5.9.5.
367     */
368    protected ImageInfo getImageInfo() {
369        if (imageInfo == null) {
370            imageInfo = getImagingService().getImageInfo(fileContent);
371        }
372        return imageInfo;
373    }
374
375    protected Blob getContentFromViews(Integer i) {
376        return (Blob) doc.getPropertyValue(String.format(CONTENT_XPATH, i));
377    }
378
379    protected Blob crop(Blob blob, Map<String, Serializable> coords) {
380        try {
381            BlobHolder bh = new SimpleBlobHolder(blob);
382            String type = blob.getMimeType();
383
384            Map<String, Serializable> options = new HashMap<>();
385            options.put(OPTION_CROP_X, coords.get("x"));
386            options.put(OPTION_CROP_Y, coords.get("y"));
387            options.put(OPTION_RESIZE_HEIGHT, coords.get("h"));
388            options.put(OPTION_RESIZE_WIDTH, coords.get("w"));
389
390            if (!"image/png".equals(type)) {
391                bh = getConversionService().convert(OPERATION_CROP, bh, options);
392                return Blobs.createBlob(bh.getBlob().getStream(), type);
393            }
394        } catch (IOException e) {
395            throw new NuxeoException("Crop failed", e);
396        }
397        return null;
398    }
399
400}