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 * Tiago Cardoso <tcardoso@nuxeo.com> 018 */ 019package org.nuxeo.ecm.platform.threed; 020 021import org.apache.commons.io.FilenameUtils; 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.codehaus.jackson.map.ObjectMapper; 025import org.nuxeo.ecm.core.api.Blob; 026import org.nuxeo.ecm.core.api.blobholder.BlobHolder; 027import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolderWithProperties; 028import org.nuxeo.ecm.platform.picture.api.ImageInfo; 029import org.nuxeo.ecm.platform.picture.api.ImagingService; 030import org.nuxeo.ecm.platform.picture.api.adapters.AbstractPictureAdapter; 031import org.nuxeo.ecm.platform.threed.service.RenderView; 032import org.nuxeo.ecm.platform.threed.service.ThreeDService; 033import org.nuxeo.runtime.api.Framework; 034 035import java.io.IOException; 036import java.io.Serializable; 037import java.util.*; 038import java.util.stream.Collectors; 039 040import static org.nuxeo.ecm.platform.threed.ThreeDInfo.*; 041 042/** 043 * Helper class to take out renders and list of {@link TransmissionThreeD} form batch conversion 044 * 045 * @since 8.4 046 */ 047public class BatchConverterHelper { 048 049 public static final String WIDTH = "width"; 050 051 public static final String HEIGHT = "height"; 052 053 public static final String DEPTH = "depth"; 054 055 public static final String FORMAT = "format"; 056 057 private static final Log log = LogFactory.getLog(BatchConverterHelper.class); 058 059 private BatchConverterHelper() { 060 } 061 062 protected static final BlobHolder convertTexture(BlobHolder resource, Integer percentage, String maxSize) { 063 ImagingService imagingService = Framework.getService(ImagingService.class); 064 float percScale = 1.0f; 065 if (percentage != null) { 066 percScale = (float) ((float) percentage / 100.0); 067 } 068 float maxScale = 1.0f; 069 Map<String, Serializable> infoTexture = resource.getProperties(); 070 if (maxSize != null) { 071 072 String[] size = maxSize.split("x"); 073 074 int width = Integer.parseInt(size[0]); 075 int height = Integer.parseInt(size[1]); 076 077 // calculate max size scale 078 maxScale = Math.min((float) width / (int) infoTexture.get(WIDTH), 079 (float) height / (int) infoTexture.get(HEIGHT)); 080 } 081 if (percScale >= 1.0 && maxScale >= 1.0) { 082 return resource; 083 } 084 float scale = Math.min(maxScale, percScale); 085 086 Map<String, Serializable> lodInfoTexture = new HashMap<>(); 087 lodInfoTexture.put(WIDTH, Math.round((int) infoTexture.get(WIDTH) * scale)); 088 lodInfoTexture.put(HEIGHT, Math.round((int) infoTexture.get(HEIGHT) * scale)); 089 lodInfoTexture.put(DEPTH, infoTexture.get(DEPTH)); 090 lodInfoTexture.put(FORMAT, infoTexture.get(FORMAT)); 091 Blob lodBlob = imagingService.resize(resource.getBlob(), (String) lodInfoTexture.get(FORMAT), 092 (int) lodInfoTexture.get(WIDTH), (int) lodInfoTexture.get(HEIGHT), (int) lodInfoTexture.get(DEPTH)); 093 lodBlob.setFilename(resource.getBlob().getFilename()); 094 return new SimpleBlobHolderWithProperties(lodBlob, lodInfoTexture); 095 } 096 097 public static final List<TransmissionThreeD> getTransmissions(BlobHolder batch, List<BlobHolder> resources) { 098 ThreeDService threeDService = Framework.getService(ThreeDService.class); 099 100 List<Blob> blobs = batch.getBlobs(); 101 102 Map<String, Integer> lodIdIndexes = (Map<String, Integer>) batch.getProperty("lodIdIndexes"); 103 104 // start with automatic LODs so we get the transmission 3Ds correctly ordered 105 return threeDService.getAutomaticLODs().stream().map(automaticLOD -> { 106 Integer index = lodIdIndexes.get(automaticLOD.getId()); 107 108 if (index != null) { 109 Blob dae = blobs.get(index); 110 // resize texture accordingly with LOD text params 111 List<BlobHolder> lodResources = resources.stream() 112 .map(resource -> convertTexture(resource, 113 automaticLOD.getPercTex(), automaticLOD.getMaxTex())) 114 .collect(Collectors.toList()); 115 List<Blob> lodResourceBlobs = lodResources.stream() 116 .map(BlobHolder::getBlob) 117 .collect(Collectors.toList()); 118 Integer idx = ((Map<String, Integer>) batch.getProperty("infoIndexes")).get(automaticLOD.getId()); 119 if (idx == null) { 120 return null; 121 } 122 Blob infoBlob = batch.getBlobs().get(idx); 123 ThreeDInfo info = null; 124 try { 125 info = getInfo(infoBlob, lodResources); 126 } catch (IOException e) { 127 log.warn(e); 128 info = null; 129 } 130 // create transmission 3D from blob and automatic LOD 131 return new TransmissionThreeD(dae, lodResourceBlobs, info, automaticLOD.getPercPoly(), 132 automaticLOD.getMaxPoly(), automaticLOD.getPercTex(), automaticLOD.getMaxTex(), 133 automaticLOD.getName()); 134 } 135 return null; 136 }).filter(Objects::nonNull).collect(Collectors.toList()); 137 } 138 139 public static final ThreeDInfo getMainInfo(BlobHolder batch, List<BlobHolder> resources) { 140 Integer idx = ((Map<String, Integer>) batch.getProperty("infoIndexes")).get("default"); 141 if (idx == null) { 142 return null; 143 } 144 Blob infoBlob = batch.getBlobs().get(idx); 145 ThreeDInfo info; 146 try { 147 info = getInfo(infoBlob, resources); 148 } catch (IOException e) { 149 log.warn(e); 150 info = null; 151 152 } 153 return info; 154 } 155 156 protected static final ThreeDInfo getInfo(Blob blob, List<BlobHolder> resources) throws IOException { 157 Map<String, Serializable> infoMap = convertToInfo(blob); 158 159 int maxWidth = resources.stream().mapToInt(resource -> (Integer) resource.getProperty(WIDTH)).max().orElse(0); 160 int maxHeight = resources.stream().mapToInt(resource -> (Integer) resource.getProperty(HEIGHT)).max().orElse(0); 161 long resourcesSize = resources.stream() 162 .mapToLong(resource -> resource.getBlob().getFile().length()) 163 .sum(); 164 165 infoMap.put(TEXTURE_LOD_SUCCESS, Boolean.TRUE); 166 infoMap.put(TEXTURES_MAX_DIMENSION, String.valueOf(maxWidth) + "x" + String.valueOf(maxHeight)); 167 infoMap.put(TEXTURES_SIZE, resourcesSize); 168 return new ThreeDInfo(infoMap); 169 } 170 171 protected static final Map<String, Serializable> convertToInfo(Blob blob) throws IOException { 172 Map<String, Serializable> info; 173 Map<String, Serializable> infoGlobal; 174 Map<String, Serializable> geomInfo = new HashMap<>(); 175 ObjectMapper mapper = new ObjectMapper(); 176 info = mapper.readValue(blob.getFile(), Map.class); 177 if (info.get("global") != null && info.get("global") instanceof Map) { 178 infoGlobal = (Map<String, Serializable>) info.get("global"); 179 geomInfo.put(GEOMETRY_LOD_SUCCESS, infoGlobal.get(GEOMETRY_LOD_SUCCESS)); 180 geomInfo.put(NON_MANIFOLD_POLYGONS, ((Integer) infoGlobal.get(NON_MANIFOLD_POLYGONS)).longValue()); 181 geomInfo.put(NON_MANIFOLD_EDGES, ((Integer) infoGlobal.get(NON_MANIFOLD_EDGES)).longValue()); 182 geomInfo.put(NON_MANIFOLD_VERTICES, ((Integer) infoGlobal.get(NON_MANIFOLD_VERTICES)).longValue()); 183 geomInfo.put(POLYGONS, ((Integer) infoGlobal.get(POLYGONS)).longValue()); 184 geomInfo.put(EDGES, ((Integer) infoGlobal.get(EDGES)).longValue()); 185 geomInfo.put(VERTICES, ((Integer) infoGlobal.get(VERTICES)).longValue()); 186 geomInfo.put(POSITION_X, infoGlobal.get(POSITION_X)); 187 geomInfo.put(POSITION_Y, infoGlobal.get(POSITION_Y)); 188 geomInfo.put(POSITION_Z, infoGlobal.get(POSITION_Z)); 189 geomInfo.put(DIMENSION_X, infoGlobal.get(DIMENSION_X)); 190 geomInfo.put(DIMENSION_Y, infoGlobal.get(DIMENSION_Y)); 191 geomInfo.put(DIMENSION_Z, infoGlobal.get(DIMENSION_Z)); 192 } 193 194 return geomInfo; 195 } 196 197 public static final List<BlobHolder> getResources(BlobHolder batch) { 198 List<Blob> blobs = batch.getBlobs(); 199 return ((List<Integer>) batch.getProperty("resourceIndexes")).stream().map(blobs::get).map(resource -> { 200 Map<String, Serializable> infoTexture = new HashMap<String, Serializable>(); 201 ImagingService imagingService = Framework.getService(ImagingService.class); 202 ImageInfo imageInfo = imagingService.getImageInfo(resource); 203 if (imageInfo == null) { 204 return null; 205 } 206 infoTexture.put(WIDTH, imageInfo.getWidth()); 207 infoTexture.put(HEIGHT, imageInfo.getHeight()); 208 infoTexture.put(FORMAT, imageInfo.getFormat()); 209 infoTexture.put(DEPTH, imageInfo.getDepth()); 210 211 return new SimpleBlobHolderWithProperties(resource, infoTexture); 212 }).filter(Objects::nonNull).collect(Collectors.toList()); 213 } 214 215 protected static final Blob createThumbnail(Blob render) { 216 ImagingService imagingService = Framework.getService(ImagingService.class); 217 ImageInfo imageInfo = imagingService.getImageInfo(render); 218 219 // calculate thumbnail size 220 float scale = Math.min((float) AbstractPictureAdapter.SMALL_SIZE / imageInfo.getWidth(), 221 (float) AbstractPictureAdapter.SMALL_SIZE / imageInfo.getHeight()); 222 223 return imagingService.resize(render, imageInfo.getFormat(), Math.round(imageInfo.getWidth() * scale), 224 Math.round(imageInfo.getHeight() * scale), imageInfo.getDepth()); 225 } 226 227 public static final List<ThreeDRenderView> getRenders(BlobHolder batch) { 228 List<Blob> allBlobs = batch.getBlobs(); 229 List<Blob> blobs = allBlobs.subList((int) batch.getProperty("renderStartIndex"), allBlobs.size()); 230 ThreeDService threeDService = Framework.getService(ThreeDService.class); 231 232 Collection<RenderView> orderedRV = new ArrayList<>(threeDService.getAutomaticRenderViews()); 233 Collection<RenderView> remainingRV = new ArrayList<>(threeDService.getAvailableRenderViews()); 234 remainingRV.removeAll(orderedRV); 235 orderedRV.addAll(remainingRV); 236 237 return orderedRV.stream().map(renderView -> { 238 Blob png = blobs.stream().filter(blob -> { 239 String[] fileNameArray = FilenameUtils.getBaseName(blob.getFilename()).split("-"); 240 return renderView.getId().equals(fileNameArray[1]); 241 }).findFirst().orElse(null); 242 243 if (png == null) { 244 return null; 245 } 246 247 return new ThreeDRenderView(renderView.getName(), png, createThumbnail(png), renderView.getAzimuth(), 248 renderView.getZenith()); 249 }).filter(Objects::nonNull).collect(Collectors.toList()); 250 } 251}