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    private ConversionService converionService;
154
155    @Override
156    public void setDocumentModel(DocumentModel doc) {
157        this.doc = doc;
158    }
159
160    protected ImagingService getImagingService() {
161        return Framework.getService(ImagingService.class);
162    }
163
164    protected ConversionService getConversionService() {
165        if (converionService == null) {
166            converionService = Framework.getService(ConversionService.class);
167        }
168        return converionService;
169    }
170
171    protected CoreSession getSession() {
172        if (session == null) {
173            if (doc == null) {
174                return null;
175            }
176            session = doc.getCoreSession();
177        }
178        return session;
179    }
180
181    protected void setMetadata() throws IOException {
182        boolean imageInfoUsed = false;
183        ImageInfo imageInfo = getImageInfo();
184        if (imageInfo != null) {
185            width = imageInfo.getWidth();
186            height = imageInfo.getHeight();
187            depth = imageInfo.getDepth();
188            imageInfoUsed = true;
189        }
190        Map<String, Object> metadata = getImagingService().getImageMetadata(fileContent);
191        description = (String) metadata.get(META_DESCRIPTION);
192        if (!imageInfoUsed) {
193            width = (Integer) metadata.get(META_WIDTH);
194            height = (Integer) metadata.get(META_HEIGHT);
195        }
196        doc.setPropertyValue("picture:" + FIELD_BYLINE, (String) metadata.get(META_BY_LINE));
197        doc.setPropertyValue("picture:" + FIELD_CAPTION, (String) metadata.get(META_CAPTION));
198        doc.setPropertyValue("picture:" + FIELD_CREDIT, (String) metadata.get(META_CREDIT));
199        if (metadata.containsKey(META_DATE_CREATED)) {
200            doc.setPropertyValue("picture:" + FIELD_DATELINE, metadata.get(META_DATE_CREATED).toString());
201        }
202        doc.setPropertyValue("picture:" + FIELD_HEADLINE, (String) metadata.get(META_HEADLINE));
203        doc.setPropertyValue("picture:" + FIELD_LANGUAGE, (String) metadata.get(META_LANGUAGE));
204        doc.setPropertyValue("picture:" + FIELD_ORIGIN, (String) metadata.get(META_OBJECT_NAME));
205        doc.setPropertyValue("picture:" + FIELD_SOURCE, (String) metadata.get(META_SOURCE));
206
207        // Set EXIF info
208        doc.setPropertyValue("imd:image_description", (String) metadata.get(META_DESCRIPTION));
209        doc.setPropertyValue("imd:user_comment", (String) metadata.get(META_COMMENT));
210        doc.setPropertyValue("imd:equipment", (String) metadata.get(META_EQUIPMENT));
211        Date dateTimeOriginal = (Date) metadata.get(META_ORIGINALDATE);
212        if (dateTimeOriginal != null) {
213            Calendar calendar = new GregorianCalendar();
214            calendar.setTime(dateTimeOriginal);
215            doc.setPropertyValue("imd:date_time_original", calendar);
216        }
217        doc.setPropertyValue("imd:xresolution", (Integer) metadata.get(META_HRESOLUTION));
218        doc.setPropertyValue("imd:yresolution", (Integer) metadata.get(META_VRESOLUTION));
219        doc.setPropertyValue("imd:pixel_xdimension", (Integer) metadata.get(META_PIXEL_XDIMENSION));
220        doc.setPropertyValue("imd:pixel_ydimension", (Integer) metadata.get(META_PIXEL_YDIMENSION));
221        doc.setPropertyValue("imd:copyright", (String) metadata.get(META_COPYRIGHT));
222        doc.setPropertyValue("imd:exposure_time", (String) metadata.get(META_EXPOSURE));
223        doc.setPropertyValue("imd:iso_speed_ratings", (String) metadata.get(META_ISOSPEED));
224        doc.setPropertyValue("imd:focal_length", (Double) metadata.get(META_FOCALLENGTH));
225        doc.setPropertyValue("imd:color_space", (String) metadata.get(META_COLORSPACE));
226        doc.setPropertyValue("imd:white_balance", (String) metadata.get(META_WHITEBALANCE));
227        ICC_Profile iccProfile = (ICC_Profile) metadata.get(META_ICCPROFILE);
228        if (iccProfile != null) {
229            doc.setPropertyValue("imd:icc_profile", iccProfile.toString());
230        }
231        doc.setPropertyValue("imd:orientation", (String) metadata.get(META_ORIENTATION));
232        doc.setPropertyValue("imd:fnumber", (Double) metadata.get(META_FNUMBER));
233
234        // Set IPTC info
235        doc.setPropertyValue("iptc:by_line", (String) metadata.get(META_BY_LINE));
236        doc.setPropertyValue("iptc:by_line_title", (String) metadata.get(META_BY_LINE_TITLE));
237        doc.setPropertyValue("iptc:caption", (String) metadata.get(META_CAPTION));
238        doc.setPropertyValue("iptc:category", (String) metadata.get(META_CATEGORY));
239        doc.setPropertyValue("iptc:city", (String) metadata.get(META_CITY));
240        doc.setPropertyValue("iptc:copyright_notice", (String) metadata.get(META_COPYRIGHT_NOTICE));
241        doc.setPropertyValue("iptc:country_or_primary_location",
242                (String) metadata.get(META_COUNTRY_OR_PRIMARY_LOCATION));
243        doc.setPropertyValue("iptc:credit", (String) metadata.get(META_CREDIT));
244        Date dateCreated = (Date) metadata.get(META_DATE_CREATED);
245        if (dateCreated != null) {
246            Calendar calendar = new GregorianCalendar();
247            calendar.setTime(dateCreated);
248            doc.setPropertyValue("iptc:date_created", calendar);
249        }
250        doc.setPropertyValue("iptc:headline", (String) metadata.get(META_HEADLINE));
251        doc.setPropertyValue("iptc:keywords", (String) metadata.get(META_KEYWORDS));
252        doc.setPropertyValue("iptc:language", (String) metadata.get(META_LANGUAGE));
253        doc.setPropertyValue("iptc:object_name", (String) metadata.get(META_OBJECT_NAME));
254        doc.setPropertyValue("iptc:original_transmission_ref",
255                (String) metadata.get(META_ORIGINAL_TRANSMISSION_REFERENCE));
256        doc.setPropertyValue("iptc:originating_program", (String) metadata.get(META_ORIGINATING_PROGRAM));
257        doc.setPropertyValue("iptc:province_or_state", (String) metadata.get(META_PROVINCE_OR_STATE));
258        doc.setPropertyValue("iptc:record_version", (String) metadata.get(META_RECORD_VERSION));
259        Date releaseDate = (Date) metadata.get(META_RELEASE_DATE);
260        if (releaseDate != null) {
261            Calendar calendar = new GregorianCalendar();
262            calendar.setTime(releaseDate);
263            doc.setPropertyValue("iptc:release_date", calendar);
264        }
265        doc.setPropertyValue("iptc:release_time", (String) metadata.get(META_RELEASE_TIME));
266        doc.setPropertyValue("iptc:source", (String) metadata.get(META_SOURCE));
267        doc.setPropertyValue("iptc:special_instructions", (String) metadata.get(META_SPECIAL_INSTRUCTIONS));
268        doc.setPropertyValue("iptc:supplemental_categories", (String) metadata.get(META_SUPPLEMENTAL_CATEGORIES));
269        doc.setPropertyValue("iptc:time_created", (String) metadata.get(META_TIME_CREATED));
270        doc.setPropertyValue("iptc:urgency", (String) metadata.get(META_URGENCY));
271        doc.setPropertyValue("iptc:writer", (String) metadata.get(META_WRITER));
272    }
273
274    protected void clearViews() {
275        List<Map<String, Object>> viewsList = new ArrayList<>();
276        doc.getProperty(VIEWS_PROPERTY).setValue(viewsList);
277    }
278
279    protected void addViews(List<Map<String, Object>> pictureConversions, String filename, String title)
280            throws IOException {
281        doc.setProperty("dublincore", "title", title);
282        if (pictureConversions != null) {
283            // Use PictureBook Properties
284            for (Map<String, Object> view : pictureConversions) {
285                Integer maxsize;
286                if (view.get("maxsize") == null) {
287                    maxsize = MEDIUM_SIZE;
288                } else {
289                    maxsize = ((Long) view.get("maxsize")).intValue();
290                }
291                createPictureimpl((String) view.get("description"), (String) view.get("tag"),
292                        (String) view.get("title"), maxsize, filename, width, height, depth, fileContent);
293            }
294        } else {
295            List<PictureView> pictureViews = getImagingService().computeViewsFor(doc, fileContent, getImageInfo(),
296                    true);
297            addPictureViews(pictureViews, true);
298        }
299    }
300
301    public void createPictureimpl(String description, String tag, String title, Integer maxsize, String filename,
302            Integer width, Integer height, Integer depth, Blob fileContent) throws IOException {
303        if (fileContent.getFilename() == null) {
304            fileContent.setFilename(filename);
305        }
306
307        if (maxsize == null) {
308            maxsize = 0;
309        }
310
311        PictureConversion pictureConversion = new PictureConversion(title, description, tag, maxsize);
312        pictureConversion.setChainId("Image.Blob.Resize");
313
314        PictureView view = getImagingService().computeViewFor(fileContent, pictureConversion, getImageInfo(), true);
315
316        addPictureView(view);
317    }
318
319    /**
320     * Attach new picture views with the document
321     *
322     * @since 7.1
323     */
324    protected void addPictureViews(List<PictureView> pictureViews, boolean clearPictureViews) {
325        if (clearPictureViews) {
326            clearViews();
327        }
328
329        List<Map<String, Serializable>> views = getPictureViews();
330
331        for (PictureView pictureView : pictureViews) {
332            views.add(pictureView.asMap());
333        }
334
335        doc.setPropertyValue(VIEWS_PROPERTY, (Serializable) views);
336    }
337
338    @Override
339    public boolean fillPictureViews(Blob blob, String filename, String title) throws IOException {
340        return fillPictureViews(blob, filename, title, null);
341    }
342
343    /**
344     * Returns the picture views attached to the document if present, an empty list otherwise.
345     *
346     * @since 7.1
347     */
348    @SuppressWarnings("unchecked")
349    protected List<Map<String, Serializable>> getPictureViews() {
350        List<Map<String, Serializable>> views = (List<Map<String, Serializable>>) doc.getPropertyValue(VIEWS_PROPERTY);
351        if (views == null) {
352            views = new ArrayList<>();
353        }
354        return views;
355    }
356
357    /**
358     * Add a pictureView to the existing picture views attached with the document
359     *
360     * @since 7.1
361     */
362    protected void addPictureView(PictureView view) {
363        List<Map<String, Serializable>> views = getPictureViews();
364        views.add(view.asMap());
365        doc.setPropertyValue(VIEWS_PROPERTY, (Serializable) views);
366    }
367
368    /**
369     * Returns the {@link ImageInfo} for the main Blob ({@code fileContent}).
370     *
371     * @since 5.9.5.
372     */
373    protected ImageInfo getImageInfo() {
374        if (imageInfo == null) {
375            imageInfo = getImagingService().getImageInfo(fileContent);
376        }
377        return imageInfo;
378    }
379
380    protected Blob getContentFromViews(Integer i) {
381        return (Blob) doc.getPropertyValue(String.format(CONTENT_XPATH, i));
382    }
383
384    protected Blob crop(Blob blob, Map<String, Serializable> coords) {
385        try {
386            BlobHolder bh = new SimpleBlobHolder(blob);
387            String type = blob.getMimeType();
388
389            Map<String, Serializable> options = new HashMap<>();
390            options.put(OPTION_CROP_X, coords.get("x"));
391            options.put(OPTION_CROP_Y, coords.get("y"));
392            options.put(OPTION_RESIZE_HEIGHT, coords.get("h"));
393            options.put(OPTION_RESIZE_WIDTH, coords.get("w"));
394
395            if (!"image/png".equals(type)) {
396                bh = getConversionService().convert(OPERATION_CROP, bh, options);
397                return Blobs.createBlob(bh.getBlob().getStream(), type);
398            }
399        } catch (IOException e) {
400            throw new NuxeoException("Crop failed", e);
401        }
402        return null;
403    }
404
405}