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