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}