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