Home | History | Annotate | Download | only in transforms
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.gltrace.state.transforms;
     18 
     19 import com.android.ide.eclipse.gltrace.FileUtils;
     20 import com.android.ide.eclipse.gltrace.GLEnum;
     21 import com.android.ide.eclipse.gltrace.state.GLStringProperty;
     22 import com.android.ide.eclipse.gltrace.state.IGLProperty;
     23 import com.google.common.io.Files;
     24 import com.google.common.primitives.UnsignedBytes;
     25 
     26 import java.awt.image.BufferedImage;
     27 import java.io.File;
     28 import java.io.IOException;
     29 
     30 import javax.imageio.ImageIO;
     31 
     32 /**
     33  * {@link TexImageTransform} transforms the state to reflect the effect of a
     34  * glTexImage2D or glTexSubImage2D GL call.
     35  */
     36 public class TexImageTransform implements IStateTransform {
     37     private static final String PNG_IMAGE_FORMAT = "PNG";
     38     private static final String TEXTURE_FILE_PREFIX = "tex";
     39     private static final String TEXTURE_FILE_SUFFIX = ".png";
     40 
     41     private final IGLPropertyAccessor mAccessor;
     42     private final File mTextureDataFile;
     43 
     44     private final int mxOffset;
     45     private final int myOffset;
     46     private final int mWidth;
     47     private final int mHeight;
     48 
     49     private String mOldValue;
     50     private String mNewValue;
     51     private GLEnum mFormat;
     52     private GLEnum mType;
     53 
     54     /**
     55      * Construct a texture image transformation.
     56      * @param accessor accessor to obtain the GL state variable to modify
     57      * @param textureData texture data passed in by the call. Could be null.
     58      * @param format format of the source texture data
     59      * @param xOffset x offset for the source data (used only in glTexSubImage2D)
     60      * @param yOffset y offset for the source data (used only in glTexSubImage2D)
     61      * @param width width of the texture
     62      * @param height height of the texture
     63      */
     64     public TexImageTransform(IGLPropertyAccessor accessor, File textureData, GLEnum format,
     65             GLEnum type, int xOffset, int yOffset, int width, int height) {
     66         mAccessor = accessor;
     67         mTextureDataFile = textureData;
     68         mFormat = format;
     69         mType = type;
     70 
     71         mxOffset = xOffset;
     72         myOffset = yOffset;
     73         mWidth = width;
     74         mHeight = height;
     75     }
     76 
     77     @Override
     78     public void apply(IGLProperty currentState) {
     79         assert mOldValue == null : "Transform cannot be applied multiple times"; //$NON-NLS-1$
     80 
     81         IGLProperty property = mAccessor.getProperty(currentState);
     82         if (!(property instanceof GLStringProperty)) {
     83             return;
     84         }
     85 
     86         GLStringProperty prop = (GLStringProperty) property;
     87         mOldValue = prop.getStringValue();
     88 
     89         // Applying texture transformations is a heavy weight process. So we perform
     90         // it only once and save the result in a temporary file. The property is actually
     91         // the path to the file.
     92         if (mNewValue == null) {
     93             try {
     94                 if (mOldValue == null) {
     95                     mNewValue = createTexture(mTextureDataFile, mWidth, mHeight);
     96                 } else {
     97                     mNewValue = updateTextureData(mOldValue, mTextureDataFile, mxOffset, myOffset,
     98                             mWidth, mHeight);
     99                 }
    100             } catch (IOException e) {
    101                 throw new RuntimeException(e);
    102             }
    103         }
    104 
    105         prop.setValue(mNewValue);
    106     }
    107 
    108     @Override
    109     public void revert(IGLProperty state) {
    110         if (mOldValue != null) {
    111             IGLProperty property = mAccessor.getProperty(state);
    112             property.setValue(mOldValue);
    113             mOldValue = null;
    114         }
    115     }
    116 
    117     @Override
    118     public IGLProperty getChangedProperty(IGLProperty state) {
    119         return mAccessor.getProperty(state);
    120     }
    121 
    122     /**
    123      * Creates a texture of provided width and height. If the texture data file is provided,
    124      * then the texture is initialized with the contents of that file, otherwise an empty
    125      * image is created.
    126      * @param textureDataFile path to texture data, could be null.
    127      * @param width width of texture
    128      * @param height height of texture
    129      * @return path to cached texture
    130      */
    131     private String createTexture(File textureDataFile, int width, int height) throws IOException {
    132         File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX);
    133 
    134         BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
    135 
    136         if (textureDataFile != null) {
    137             byte[] initialData = Files.toByteArray(textureDataFile);
    138             img.getRaster().setDataElements(0, 0, width, height,
    139                     formatSourceData(initialData, width, height));
    140         }
    141 
    142         ImageIO.write(img, PNG_IMAGE_FORMAT, f);
    143 
    144         return f.getAbsolutePath();
    145     }
    146 
    147     /**
    148      * Update part of an existing texture.
    149      * @param currentImagePath current texture image.
    150      * @param textureDataFile new data to update the current texture with
    151      * @param xOffset x offset for the update region
    152      * @param yOffset y offset for the update region
    153      * @param width width of the update region
    154      * @param height height of the update region
    155      * @return path to the updated texture
    156      */
    157     private String updateTextureData(String currentImagePath, File textureDataFile,
    158             int xOffset, int yOffset, int width, int height) throws IOException {
    159         assert currentImagePath != null : "Attempt to update a null texture";
    160 
    161         if (textureDataFile == null) {
    162             // Do not perform any updates if we don't have the actual data.
    163             return currentImagePath;
    164         }
    165 
    166         File f = FileUtils.createTempFile(TEXTURE_FILE_PREFIX, TEXTURE_FILE_SUFFIX);
    167         BufferedImage image = null;
    168         image = ImageIO.read(new File(currentImagePath));
    169 
    170         byte[] subImageData = Files.toByteArray(textureDataFile);
    171         image.getRaster().setDataElements(xOffset, yOffset, width, height,
    172                 formatSourceData(subImageData, width, height));
    173         ImageIO.write(image, PNG_IMAGE_FORMAT, f);
    174 
    175         return f.getAbsolutePath();
    176     }
    177 
    178     private byte[] formatSourceData(byte[] subImageData, int width, int height) {
    179         if (mType != GLEnum.GL_UNSIGNED_BYTE) {
    180             subImageData = unpackData(subImageData, mType);
    181         }
    182 
    183         switch (mFormat) {
    184             case GL_RGBA:
    185                 // no conversions necessary
    186                 return subImageData;
    187             case GL_RGB:
    188                 return addAlphaChannel(subImageData, width, height);
    189             case GL_ALPHA:
    190                 return addRGBChannels(subImageData, width, height);
    191             case GL_LUMINANCE:
    192                 return createRGBAFromLuminance(subImageData, width, height);
    193             case GL_LUMINANCE_ALPHA:
    194                 return createRGBAFromLuminanceAlpha(subImageData, width, height);
    195             default:
    196                 throw new RuntimeException();
    197         }
    198     }
    199 
    200     private byte[] unpackData(byte[] data, GLEnum type) {
    201         switch (type) {
    202             case GL_UNSIGNED_BYTE:
    203                 return data;
    204             case GL_UNSIGNED_SHORT_4_4_4_4:
    205                 return convertShortToUnsigned(data, 0xf000, 12, 0x0f00, 8, 0x00f0, 4, 0x000f, 0,
    206                         true);
    207             case GL_UNSIGNED_SHORT_5_6_5:
    208                 return convertShortToUnsigned(data, 0xf800, 11, 0x07e0, 5, 0x001f, 0, 0, 0,
    209                         false);
    210             case GL_UNSIGNED_SHORT_5_5_5_1:
    211                 return convertShortToUnsigned(data, 0xf800, 11, 0x07c0, 6, 0x003e, 1, 0x1, 0,
    212                         true);
    213             default:
    214                 return data;
    215         }
    216     }
    217 
    218    private byte[] convertShortToUnsigned(byte[] shortData,
    219            int rmask, int rshift,
    220            int gmask, int gshift,
    221            int bmask, int bshift,
    222            int amask, int ashift,
    223            boolean includeAlpha) {
    224        int numChannels = includeAlpha ? 4 : 3;
    225        byte[] unsignedData = new byte[(shortData.length/2) * numChannels];
    226 
    227        for (int i = 0; i < (shortData.length / 2); i++) {
    228            int hi = UnsignedBytes.toInt(shortData[i*2 + 0]);
    229            int lo = UnsignedBytes.toInt(shortData[i*2 + 1]);
    230 
    231            int x = hi << 8 | lo;
    232 
    233            int r = (x & rmask) >>> rshift;
    234            int g = (x & gmask) >>> gshift;
    235            int b = (x & bmask) >>> bshift;
    236            int a = (x & amask) >>> ashift;
    237 
    238            unsignedData[i * numChannels + 0] = UnsignedBytes.checkedCast(r);
    239            unsignedData[i * numChannels + 1] = UnsignedBytes.checkedCast(g);
    240            unsignedData[i * numChannels + 2] = UnsignedBytes.checkedCast(b);
    241 
    242            if (includeAlpha) {
    243                unsignedData[i * numChannels + 3] = UnsignedBytes.checkedCast(a);
    244            }
    245        }
    246 
    247        return unsignedData;
    248    }
    249 
    250    private byte[] addAlphaChannel(byte[] sourceData, int width, int height) {
    251         assert sourceData.length == 3 * width * height; // should have R, G & B channels
    252 
    253         byte[] data = new byte[4 * width * height];
    254 
    255         for (int src = 0, dst = 0; src < sourceData.length; src += 3, dst += 4) {
    256             data[dst + 0] = sourceData[src + 0]; // copy R byte
    257             data[dst + 1] = sourceData[src + 1]; // copy G byte
    258             data[dst + 2] = sourceData[src + 2]; // copy B byte
    259             data[dst + 3] = 1; // add alpha = 1
    260         }
    261 
    262         return data;
    263     }
    264 
    265     private byte[] addRGBChannels(byte[] sourceData, int width, int height) {
    266         assert sourceData.length == width * height; // should have a single alpha channel
    267 
    268         byte[] data = new byte[4 * width * height];
    269 
    270         for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) {
    271             data[dst + 0] = data[dst + 1] = data[dst + 2] = 0; // set R = G = B = 0
    272             data[dst + 3] = sourceData[src];                 // copy over alpha
    273         }
    274 
    275         return data;
    276     }
    277 
    278     private byte[] createRGBAFromLuminance(byte[] sourceData, int width, int height) {
    279         assert sourceData.length == width * height; // should have a single luminance channel
    280 
    281         byte[] data = new byte[4 * width * height];
    282 
    283         for (int src = 0, dst = 0; src < sourceData.length; src++, dst += 4) {
    284             int l = sourceData[src] * 3;
    285             if (l > 255) { // clamp to 255
    286                 l = 255;
    287             }
    288 
    289             data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3
    290             data[dst + 3] = 1;                                        // set alpha = 1
    291         }
    292 
    293         return data;
    294     }
    295 
    296     private byte[] createRGBAFromLuminanceAlpha(byte[] sourceData, int width, int height) {
    297         assert sourceData.length == 2 * width * height; // should have luminance & alpha channels
    298 
    299         byte[] data = new byte[4 * width * height];
    300 
    301         for (int src = 0, dst = 0; src < sourceData.length; src += 2, dst += 4) {
    302             int l = sourceData[src] * 3;
    303             if (l > 255) { // clamp to 255
    304                 l = 255;
    305             }
    306 
    307             data[dst + 0] = data[dst + 1] = data[dst + 2] = (byte) l; // set R = G = B = L * 3
    308             data[dst + 3] = sourceData[src + 1];                    // copy over alpha
    309         }
    310 
    311         return data;
    312     }
    313 }
    314