1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.texture.plugins;
34
35import com.jme3.asset.AssetInfo;
36import com.jme3.asset.AssetLoader;
37import com.jme3.asset.TextureKey;
38import com.jme3.texture.Image;
39import com.jme3.texture.Image.Format;
40import com.jme3.util.BufferUtils;
41import java.io.IOException;
42import java.io.InputStream;
43import java.nio.ByteBuffer;
44import java.nio.ByteOrder;
45import java.util.logging.Logger;
46
47public class PFMLoader implements AssetLoader {
48
49    private static final Logger logger = Logger.getLogger(PFMLoader.class.getName());
50
51    private String readString(InputStream is) throws IOException{
52        StringBuilder sb = new StringBuilder();
53        while (true){
54            int i = is.read();
55            if (i == 0x0a || i == -1) // new line or EOF
56                return sb.toString();
57
58            sb.append((char)i);
59        }
60    }
61
62    private void flipScanline(byte[] scanline){
63        for (int i = 0; i < scanline.length; i+=4){
64            // flip first and fourth bytes
65            byte tmp = scanline[i+3];
66            scanline[i+3] = scanline[i+0];
67            scanline[i+0] = tmp;
68
69            // flip second and third bytes
70            tmp = scanline[i+2];
71            scanline[i+2] = scanline[i+1];
72            scanline[i+1] = tmp;
73        }
74    }
75
76    private Image load(InputStream in, boolean needYFlip) throws IOException{
77        Format format = null;
78
79        String fmtStr = readString(in);
80        if (fmtStr.equals("PF")){
81            format = Format.RGB32F;
82        }else if (fmtStr.equals("Pf")){
83            format = Format.Luminance32F;
84        }else{
85            throw new IOException("File is not PFM format");
86        }
87
88        String sizeStr = readString(in);
89        int spaceIdx = sizeStr.indexOf(" ");
90        if (spaceIdx <= 0 || spaceIdx >= sizeStr.length() - 1)
91            throw new IOException("Invalid size syntax in PFM file");
92
93        int width = Integer.parseInt(sizeStr.substring(0,spaceIdx));
94        int height = Integer.parseInt(sizeStr.substring(spaceIdx+1));
95
96        if (width <= 0 || height <= 0)
97            throw new IOException("Invalid size specified in PFM file");
98
99        String scaleStr = readString(in);
100        float scale = Float.parseFloat(scaleStr);
101        ByteOrder order = scale < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
102        boolean needEndienFlip = order != ByteOrder.nativeOrder();
103
104        // make sure all unneccessary stuff gets deleted from heap
105        // before allocating large amount of memory
106        System.gc();
107
108        int bytesPerPixel = format.getBitsPerPixel() / 8;
109        int scanLineBytes = bytesPerPixel * width;
110
111        ByteBuffer imageData = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
112        byte[] scanline = new byte[width * bytesPerPixel];
113
114        for (int y = height - 1; y >= 0; y--) {
115            if (!needYFlip)
116                imageData.position(scanLineBytes * y);
117
118            int read = 0;
119            int off = 0;
120            do {
121                read = in.read(scanline, off, scanline.length - off);
122                off += read;
123            } while (read > 0);
124
125            if (needEndienFlip){
126                flipScanline(scanline);
127            }
128
129            imageData.put(scanline);
130        }
131        imageData.rewind();
132
133        return new Image(format, width, height, imageData);
134    }
135
136    public Object load(AssetInfo info) throws IOException {
137        if (!(info.getKey() instanceof TextureKey))
138            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
139
140        InputStream in = null;
141        try {
142            in = info.openStream();
143            return load(in, ((TextureKey)info.getKey()).isFlipY());
144        } finally {
145            if (in != null){
146                in.close();
147            }
148        }
149
150    }
151
152}
153