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.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<>();
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}