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}