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